update breez

This commit is contained in:
Kukks
2024-03-21 15:52:36 +01:00
parent 1c64038245
commit efd72fdae9
15 changed files with 280 additions and 94 deletions

View File

@@ -8,7 +8,7 @@
<!-- Plugin specific properties --> <!-- Plugin specific properties -->
<PropertyGroup> <PropertyGroup>
<Product>Breez / Greenlight</Product> <Product>Breez / Greenlight</Product>
<Description>Lightwight lightning baby!</Description> <Description>Lightweight lightning baby!</Description>
<Version>1.0.0</Version> <Version>1.0.0</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup> </PropertyGroup>
@@ -34,7 +34,7 @@
<ProjectReference Include="..\..\submodules\btcpayserver\BTCPayServer\BTCPayServer.csproj" /> <ProjectReference Include="..\..\submodules\btcpayserver\BTCPayServer\BTCPayServer.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Breez.Sdk" Version="0.2.10" /> <PackageReference Include="Breez.Sdk" Version="0.3.6" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@@ -1,12 +1,18 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Threading.Tasks; 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.Data;
using BTCPayServer.Lightning; using BTCPayServer.Lightning;
using BTCPayServer.Models; using BTCPayServer.Models;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets; using BTCPayServer.Services.Wallets;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -17,21 +23,22 @@ using NBXplorer.DerivationStrategy;
namespace BTCPayServer.Plugins.Breez; namespace BTCPayServer.Plugins.Breez;
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)] [Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Route("plugins/{storeId}/Breez")] [Route("plugins/{storeId}/Breez")]
public class BreezController : Controller public class BreezController : Controller
{ {
private readonly BTCPayNetworkProvider _btcPayNetworkProvider; private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly BreezService _breezService; private readonly BreezService _breezService;
private readonly BTCPayWalletProvider _btcWalletProvider; private readonly BTCPayWalletProvider _btcWalletProvider;
private readonly StoreRepository _storeRepository;
public BreezController(BTCPayNetworkProvider btcPayNetworkProvider, public BreezController(BTCPayNetworkProvider btcPayNetworkProvider,
BreezService breezService, BreezService breezService,
BTCPayWalletProvider btcWalletProvider) BTCPayWalletProvider btcWalletProvider, StoreRepository storeRepository)
{ {
_btcPayNetworkProvider = btcPayNetworkProvider; _btcPayNetworkProvider = btcPayNetworkProvider;
_breezService = breezService; _breezService = breezService;
_btcWalletProvider = btcWalletProvider; _btcWalletProvider = btcWalletProvider;
_storeRepository = storeRepository;
} }
@@ -43,6 +50,7 @@ public class BreezController : Controller
} }
[HttpGet("swapin")] [HttpGet("swapin")]
[Authorize(Policy = Policies.CanCreateInvoice, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> SwapIn(string storeId) public async Task<IActionResult> SwapIn(string storeId)
{ {
var client = _breezService.GetClient(storeId); var client = _breezService.GetClient(storeId);
@@ -55,6 +63,7 @@ public class BreezController : Controller
} }
[HttpGet("info")] [HttpGet("info")]
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Info(string storeId) public async Task<IActionResult> Info(string storeId)
{ {
var client = _breezService.GetClient(storeId); var client = _breezService.GetClient(storeId);
@@ -67,6 +76,7 @@ public class BreezController : Controller
} }
[HttpGet("sweep")] [HttpGet("sweep")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Sweep(string storeId) public async Task<IActionResult> Sweep(string storeId)
{ {
var client = _breezService.GetClient(storeId); var client = _breezService.GetClient(storeId);
@@ -79,6 +89,7 @@ public class BreezController : Controller
} }
[HttpPost("sweep")] [HttpPost("sweep")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Sweep(string storeId, string address, uint satPerByte) public async Task<IActionResult> Sweep(string storeId, string address, uint satPerByte)
{ {
var client = _breezService.GetClient(storeId); var client = _breezService.GetClient(storeId);
@@ -98,7 +109,7 @@ public class BreezController : Controller
try try
{ {
var response = client.Sdk.Sweep(new SweepRequest(address, satPerByte)); var response = client.Sdk.RedeemOnchainFunds(new RedeemOnchainFundsRequest(address, satPerByte));
TempData[WellKnownTempData.SuccessMessage] = $"sweep successful: {response.txid}"; TempData[WellKnownTempData.SuccessMessage] = $"sweep successful: {response.txid}";
} }
@@ -112,6 +123,7 @@ public class BreezController : Controller
} }
[HttpGet("send")] [HttpGet("send")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Send(string storeId) public async Task<IActionResult> Send(string storeId)
{ {
var client = _breezService.GetClient(storeId); var client = _breezService.GetClient(storeId);
@@ -122,6 +134,7 @@ public class BreezController : Controller
return View((object) storeId); return View((object) storeId);
} }
[Authorize(Policy = Policies.CanCreateInvoice, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[Route("receive")] [Route("receive")]
public async Task<IActionResult> Receive(string storeId, ulong? amount) public async Task<IActionResult> Receive(string storeId, ulong? amount)
{ {
@@ -130,6 +143,9 @@ public class BreezController : Controller
{ {
return RedirectToAction(nameof(Configure), new {storeId}); return RedirectToAction(nameof(Configure), new {storeId});
} }
try
{
if (amount is not null) if (amount is not null)
{ {
var invoice = await client.CreateInvoice(LightMoney.FromUnit(amount.Value, LightMoneyUnit.Satoshi).MilliSatoshi, null, TimeSpan.Zero); var invoice = await client.CreateInvoice(LightMoney.FromUnit(amount.Value, LightMoneyUnit.Satoshi).MilliSatoshi, null, TimeSpan.Zero);
@@ -137,11 +153,18 @@ public class BreezController : Controller
return RedirectToAction("Payments", "Breez", new {storeId }); return RedirectToAction("Payments", "Breez", new {storeId });
} }
}
catch (Exception e)
{
TempData[WellKnownTempData.ErrorMessage] = $"{e.Message}";
}
return View((object) storeId); return View((object) storeId);
} }
[HttpPost("send")] [HttpPost("send")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Send(string storeId, string address, ulong? amount) public async Task<IActionResult> Send(string storeId, string address, ulong? amount)
{ {
var client = _breezService.GetClient(storeId); var client = _breezService.GetClient(storeId);
@@ -203,6 +226,7 @@ public class BreezController : Controller
[HttpGet("swapout")] [HttpGet("swapout")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> SwapOut(string storeId) public async Task<IActionResult> SwapOut(string storeId)
{ {
var client = _breezService.GetClient(storeId); var client = _breezService.GetClient(storeId);
@@ -215,6 +239,7 @@ public class BreezController : Controller
} }
[HttpPost("swapout")] [HttpPost("swapout")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> SwapOut(string storeId, string address, ulong amount, uint satPerByte, public async Task<IActionResult> SwapOut(string storeId, string address, ulong amount, uint satPerByte,
string feesHash) string feesHash)
{ {
@@ -247,6 +272,7 @@ public class BreezController : Controller
} }
[HttpGet("swapin/{address}/refund")] [HttpGet("swapin/{address}/refund")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> SwapInRefund(string storeId, string address) public async Task<IActionResult> SwapInRefund(string storeId, string address)
{ {
var client = _breezService.GetClient(storeId); var client = _breezService.GetClient(storeId);
@@ -259,6 +285,7 @@ public class BreezController : Controller
} }
[HttpPost("swapin/{address}/refund")] [HttpPost("swapin/{address}/refund")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> SwapInRefund(string storeId, string address, string refundAddress, uint satPerByte) public async Task<IActionResult> SwapInRefund(string storeId, string address, string refundAddress, uint satPerByte)
{ {
var client = _breezService.GetClient(storeId); var client = _breezService.GetClient(storeId);
@@ -280,42 +307,128 @@ public class BreezController : Controller
return RedirectToAction(nameof(SwapIn), new {storeId}); return RedirectToAction(nameof(SwapIn), new {storeId});
} }
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[HttpGet("configure")] [HttpGet("configure")]
public async Task<IActionResult> Configure(string storeId) public async Task<IActionResult> Configure(string storeId)
{ {
return View(await _breezService.Get(storeId)); return View(await _breezService.Get(storeId));
} }
private static async Task<byte[]> ReadAsByteArrayAsync( Stream source)
{
// Optimization
if (source is MemoryStream memorySource)
return memorySource.ToArray();
using var memoryStream = new MemoryStream();
await source.CopyToAsync(memoryStream);
return memoryStream.ToArray();
}
[HttpPost("configure")] [HttpPost("configure")]
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Configure(string storeId, string command, BreezSettings settings) public async Task<IActionResult> Configure(string storeId, string command, BreezSettings settings)
{ {
var store = HttpContext.GetStoreData();
var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider).OfType<LightningSupportedPaymentMethod>()
.FirstOrDefault(method =>
method.PaymentId.PaymentType == LightningPaymentType.Instance &&
method.PaymentId.CryptoCode == "BTC");
if (command == "clear") if (command == "clear")
{ {
await _breezService.Set(storeId, null); await _breezService.Set(storeId, null);
TempData[WellKnownTempData.SuccessMessage] = "Settings cleared successfully"; TempData[WellKnownTempData.SuccessMessage] = "Settings cleared successfully";
var client = _breezService.GetClient(storeId);
var isStoreSetToThisMicro = existing?.GetExternalLightningUrl() == client?.ToString();
if (client is not null && isStoreSetToThisMicro)
{
store.SetSupportedPaymentMethod(existing.PaymentId, null);
await _storeRepository.UpdateStore(store);
}
return RedirectToAction(nameof(Configure), new {storeId}); return RedirectToAction(nameof(Configure), new {storeId});
} }
if (command == "save") if (command == "save")
{ {
try try
{ {
if (string.IsNullOrEmpty(settings.Mnemonic))
{
ModelState.AddModelError(nameof(settings.Mnemonic), "Mnemonic is required");
return View(settings);
}
else
{
try
{
new Mnemonic(settings.Mnemonic);
}
catch (Exception e)
{
ModelState.AddModelError(nameof(settings.Mnemonic), "Invalid mnemonic");
return View(settings);
}
}
if (settings.GreenlightCredentials is not null)
{
await using var stream = settings.GreenlightCredentials .OpenReadStream();
using var archive = new ZipArchive(stream);
var deviceClientArchiveEntry = archive.GetEntry("client.crt");
var deviceKeyArchiveEntry = archive.GetEntry("client-key.pem");
if(deviceClientArchiveEntry is null || deviceKeyArchiveEntry is null)
{
ModelState.AddModelError(nameof(settings.GreenlightCredentials), "Invalid zip file (does not have client.crt or client-key.pem)");
return View(settings);
}
else
{
var deviceClient = await ReadAsByteArrayAsync(deviceClientArchiveEntry.Open());
var deviceKey = await ReadAsByteArrayAsync(deviceKeyArchiveEntry.Open());
var dir = _breezService.GetWorkDir(storeId);
Directory.CreateDirectory(dir);
await System.IO.File.WriteAllBytesAsync(Path.Combine(dir, "client.crt"), deviceClient);
await System.IO.File.WriteAllBytesAsync(Path.Combine(dir, "client-key.pem"), deviceKey);
await _breezService.Set(storeId, settings); await _breezService.Set(storeId, settings);
} }
}
else
{
await _breezService.Set(storeId, settings);
}
}
catch (Exception e) catch (Exception e)
{ {
TempData[WellKnownTempData.ErrorMessage] = $"Couldnt use provided settings: {e.Message}"; TempData[WellKnownTempData.ErrorMessage] = $"Couldnt use provided settings: {e.Message}";
return View(settings); return View(settings);
} }
if(existing is null)
{
existing = new LightningSupportedPaymentMethod()
{
CryptoCode = "BTC"
};
var client = _breezService.GetClient(storeId);
existing.SetLightningUrl(client);
store.SetSupportedPaymentMethod(existing);
await _storeRepository.UpdateStore(store);
}
TempData[WellKnownTempData.SuccessMessage] = "Settings saved successfully"; TempData[WellKnownTempData.SuccessMessage] = "Settings saved successfully";
return RedirectToAction(nameof(Configure), new {storeId}); return RedirectToAction(nameof(Info), new {storeId});
} }
return NotFound(); return NotFound();
} }
[Route("payments")] [Route("payments")]
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
public async Task<IActionResult> Payments(string storeId, PaymentsViewModel viewModel) public async Task<IActionResult> Payments(string storeId, PaymentsViewModel viewModel)
{ {
var client = _breezService.GetClient(storeId); var client = _breezService.GetClient(storeId);
@@ -326,7 +439,7 @@ public class BreezController : Controller
viewModel ??= new PaymentsViewModel(); viewModel ??= new PaymentsViewModel();
viewModel.Payments = client.Sdk.ListPayments(new ListPaymentsRequest(null, null, null, true, viewModel.Payments = client.Sdk.ListPayments(new ListPaymentsRequest(null, null, null,null,true,
(uint?) viewModel.Skip, (uint?) viewModel.Count)); (uint?) viewModel.Skip, (uint?) viewModel.Count));
return View(viewModel); return View(viewModel);

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -22,12 +23,21 @@ public class BreezLightningClient : ILightningClient, IDisposable, EventListener
public readonly string PaymentKey; 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 paymentKey) Mnemonic mnemonic, string paymentKey)
{ {
apiKey??= "99010c6f84541bf582899db6728f6098ba98ca95ea569f4c63f2c2c9205ace57";
_network = network; _network = network;
PaymentKey = paymentKey; PaymentKey = paymentKey;
GreenlightCredentials glCreds = null;
if (File.Exists(Path.Combine(workingDir, "client.crt")) && File.Exists(Path.Combine(workingDir, "client-key.pem")))
{
var deviceCert = File.ReadAllBytes(Path.Combine(workingDir, "client.crt"));
var deviceKey = File.ReadAllBytes(Path.Combine(workingDir, "client-key.pem"));
glCreds = new GreenlightCredentials(deviceKey.ToList(), deviceCert.ToList());
}
var nodeConfig = new NodeConfig.Greenlight( var nodeConfig = new NodeConfig.Greenlight(
new GreenlightNodeConfig(null, inviteCode) new GreenlightNodeConfig(glCreds, 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,
@@ -40,8 +50,8 @@ public class BreezLightningClient : ILightningClient, IDisposable, EventListener
network == NBitcoin.Network.TestNet ? Network.TESTNET : network == NBitcoin.Network.TestNet ? Network.TESTNET :
network == NBitcoin.Network.RegTest ? Network.REGTEST : Network.SIGNET network == NBitcoin.Network.RegTest ? Network.REGTEST : Network.SIGNET
}; };
var seed = BreezSdkMethods.MnemonicToSeed(mnemonic); var seed = mnemonic.DeriveSeed();
Sdk = BreezSdkMethods.Connect(config, seed, this); Sdk = BreezSdkMethods.Connect(config, seed.ToList(), this);
} }
public BlockingBreezServices Sdk { get; } public BlockingBreezServices Sdk { get; }
@@ -137,7 +147,7 @@ public class BreezLightningClient : ILightningClient, IDisposable, EventListener
CancellationToken cancellation = default) CancellationToken cancellation = default)
{ {
return Sdk.ListPayments(new ListPaymentsRequest(new List<PaymentTypeFilter>(){PaymentTypeFilter.RECEIVED}, null, null, return Sdk.ListPayments(new ListPaymentsRequest(new List<PaymentTypeFilter>(){PaymentTypeFilter.RECEIVED}, null, null,
request?.PendingOnly is not true, (uint?) request?.OffsetIndex, null)) null, request?.PendingOnly is not true, (uint?) request?.OffsetIndex, null))
.Select(FromPayment).ToArray(); .Select(FromPayment).ToArray();
} }
@@ -155,7 +165,7 @@ public class BreezLightningClient : ILightningClient, IDisposable, EventListener
CancellationToken cancellation = default) CancellationToken cancellation = default)
{ {
return Sdk.ListPayments(new ListPaymentsRequest(new List<PaymentTypeFilter>(){PaymentTypeFilter.RECEIVED}, null, null, null, return Sdk.ListPayments(new ListPaymentsRequest(new List<PaymentTypeFilter>(){PaymentTypeFilter.RECEIVED}, null, null, null,
(uint?) request?.OffsetIndex, null)) null, (uint?) request?.OffsetIndex, null))
.Select(ToLightningPayment).ToArray(); .Select(ToLightningPayment).ToArray();
} }
@@ -164,6 +174,7 @@ public class BreezLightningClient : ILightningClient, IDisposable, EventListener
CancellationToken cancellation = default) 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);
description??= "Invoice";
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 FromPR(p); return FromPR(p);
@@ -207,10 +218,6 @@ public class BreezLightningClient : ILightningClient, IDisposable, EventListener
{ {
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
BlockHeight = (int) ni.blockHeight BlockHeight = (int) ni.blockHeight
}; };
} }

View File

@@ -20,6 +20,7 @@ public class BreezLightningConnectionStringHandler : ILightningConnectionStringH
return null; return null;
} }
if (!kv.TryGetValue("key", out var key)) if (!kv.TryGetValue("key", out var key))
{ {
error = $"The key 'key' is mandatory for breez connection strings"; error = $"The key 'key' is mandatory for breez connection strings";

View File

@@ -56,10 +56,10 @@ public class BreezService:EventHostedServiceBase
await base.ProcessEvent(evt, cancellationToken); await base.ProcessEvent(evt, cancellationToken);
} }
private string GetWorkDir() public string GetWorkDir(string storeId)
{ {
var dir = _dataDirectories.Value.DataDir; var dir = _dataDirectories.Value.DataDir;
return Path.Combine(dir, "Plugins", "Breez"); return Path.Combine(dir, "Plugins", "Breez",storeId);
} }
TaskCompletionSource tcs = new(); TaskCompletionSource tcs = new();
@@ -103,9 +103,11 @@ public class BreezService:EventHostedServiceBase
try try
{ {
var network = Network.Main; // _btcPayNetworkProvider.BTC.NBitcoinNetwork; var network = Network.Main; // _btcPayNetworkProvider.BTC.NBitcoinNetwork;
Directory.CreateDirectory(GetWorkDir()); var dir = GetWorkDir(storeId);
var client = new BreezLightningClient(settings.InviteCode, settings.ApiKey, GetWorkDir(), Directory.CreateDirectory(dir);
network, settings.Mnemonic, settings.PaymentKey); settings.PaymentKey ??= Guid.NewGuid().ToString();
var client = new BreezLightningClient(settings.InviteCode, settings.ApiKey, dir,
network, new Mnemonic(settings.Mnemonic), settings.PaymentKey);
if (storeId is not null) if (storeId is not null)
{ {
_clients.AddOrReplace(storeId, client); _clients.AddOrReplace(storeId, client);
@@ -140,6 +142,7 @@ public class BreezService:EventHostedServiceBase
data.SetSupportedPaymentMethod(new PaymentMethodId("BTC", LightningPaymentType.Instance), null ); data.SetSupportedPaymentMethod(new PaymentMethodId("BTC", LightningPaymentType.Instance), null );
await _storeRepository.UpdateStore(data); await _storeRepository.UpdateStore(data);
} }
Directory.Delete(GetWorkDir(storeId), true);
} }
else if(result is not null ) else if(result is not null )

View File

@@ -1,13 +1,20 @@
#nullable enable #nullable enable
using System; using System;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json;
namespace BTCPayServer.Plugins.Breez; namespace BTCPayServer.Plugins.Breez;
public class BreezSettings public class BreezSettings
{ {
public string InviteCode { get; set; } public string? InviteCode { get; set; }
public string Mnemonic { get; set; } public string? Mnemonic { get; set; }
public string ApiKey { get; set; } public string? ApiKey { get; set; }
public string PaymentKey { get; set; } = Guid.NewGuid().ToString(); public string PaymentKey { get; set; } = Guid.NewGuid().ToString();
[JsonIgnore]
public IFormFile GreenlightCredentials { get; set; }
} }

View File

@@ -6,8 +6,10 @@
@{ @{
ViewData.SetActivePage("Breez", "Configure", "Configure"); ViewData.SetActivePage("Breez", "Configure", "Configure");
var storeId = Context.GetCurrentStoreId(); var storeId = Context.GetCurrentStoreId();
var showAdvancedOptions = !string.IsNullOrEmpty(Model?.ApiKey) || !string.IsNullOrEmpty(Model?.InviteCode);
var active = (await BreezService.Get(storeId)) is not null;
} }
<form method="post" asp-action="Configure" asp-controller="Breez" asp-route-storeId="@storeId"> <form method="post" asp-action="Configure" asp-controller="Breez" asp-route-storeId="@storeId" enctype="multipart/form-data">
<div class="row mb-4"> <div class="row mb-4">
<div class="col-12"> <div class="col-12">
<div class="d-flex align-items-center justify-content-between mb-3"> <div class="d-flex align-items-center justify-content-between mb-3">
@@ -16,7 +18,7 @@
</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 name="command" type="submit" value="save" class="btn btn-primary">Save</button> <button name="command" type="submit" value="save" class="btn btn-primary">Save</button>
@if (await BreezService.Get(storeId) is not null) @if (active)
{ {
<button name="command" type="submit" value="clear" class="btn btn-danger">Clear</button> <button name="command" type="submit" value="clear" class="btn btn-danger">Clear</button>
} }
@@ -25,22 +27,56 @@
<div class="form-group"> <div class="form-group">
<label asp-for="Mnemonic" class="form-label">Mnemonic</label> <label asp-for="Mnemonic" class="form-label">Mnemonic</label>
<input asp-for="Mnemonic" class="form-control"/> <input value="@Model?.Mnemonic" asp-for="Mnemonic" class="form-control" type="password" disabled="@active"/>
<span asp-validation-for="Mnemonic" class="text-danger"></span> <span asp-validation-for="Mnemonic" class="text-danger"></span>
<span class="text-muted">A Bitcoin 12-word mnemonic seed phrase.<strong>BACK THIS UP SAFELY! GENERATE IT RANDOMLY! SERVER ADMINS HAVE ACCESS TO THIS!</strong></span>
</div> </div>
@if (!active)
{
<div class="row">
<div class="col-6">
<div class="form-group"> <div class="form-group">
<label asp-for="ApiKey" class="form-label">ApiKey</label> <label asp-for="GreenlightCredentials" class="form-label">Greenlight credentials</label>
<input asp-for="GreenlightCredentials" type="file" class="form-control">
<span asp-validation-for="GreenlightCredentials" class="text-danger w-100 d-block"></span>
<a href="https://greenlight.blockstream.com/" target="_blank">Get Greenlight credentials directly from Blockstream</a>
</div>
</div>
<div class="col-6">
<div class="form-group">
<label asp-for="InviteCode" class="form-label">Invite Code</label>
<input asp-for="InviteCode" class="form-control"/>
<span asp-validation-for="InviteCode" class="text-danger"></span>
<span class="text-muted">Alternatively, you can use an invite code.</span>
</div>
</div>
</div>
}
<button class="d-inline-flex align-items-center btn btn-link text-primary fw-semibold p-0 mb-3" type="button" id="AdvancedSettingsButton" data-bs-toggle="collapse" data-bs-target="#AdvancedSettings" aria-expanded="false" aria-controls="AdvancedSettings">
<vc:icon symbol="caret-down"/>
<span class="ms-1">Advanced settings</span>
</button>
<div id="AdvancedSettings" class="collapse @(showAdvancedOptions ? "show" : "")">
<div class="form-group">
<label asp-for="ApiKey" class="form-label">Breez API Key</label>
<input asp-for="ApiKey" class="form-control"/> <input asp-for="ApiKey" class="form-control"/>
<span asp-validation-for="ApiKey" class="text-danger"></span> <span asp-validation-for="ApiKey" class="text-danger"></span>
</div> </div>
@if (active)
{
<div class="form-group"> <div class="form-group">
<label asp-for="InviteCode" class="form-label">Invite Code</label> <label asp-for="InviteCode" class="form-label">Invite Code</label>
<input asp-for="InviteCode" class="form-control"/> <input asp-for="InviteCode" class="form-control"/>
<span asp-validation-for="InviteCode" class="text-danger"></span> <span asp-validation-for="InviteCode" class="text-danger"></span>
</div> </div>
}
</div>
<input type="hidden" asp-for="PaymentKey"/> <input type="hidden" asp-for="PaymentKey"/>
</div> </div>

View File

@@ -1,6 +1,6 @@
@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Models.StoreViewModels
@using BTCPayServer.Models.StoreViewModels
@using BTCPayServer.Security @using BTCPayServer.Security
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model object @model object
@{ @{
var storeId = Model switch var storeId = Model switch
@@ -9,7 +9,6 @@
StoreDashboardViewModel dashboardModel => dashboardModel.StoreId, StoreDashboardViewModel dashboardModel => dashboardModel.StoreId,
_ => Context.GetImplicitStoreId() _ => Context.GetImplicitStoreId()
}; };
ViewData.SetActivePage("Breez", "Create Swapin Refund", "Info"); ViewData.SetActivePage("Breez", "Breez node info", "Info");
} }
<partial name="Breez/BreezNodeInfo" model="@storeId"/> <partial name="Breez/BreezNodeInfo" model="@storeId"/>

View File

@@ -1,6 +1,10 @@
@using BTCPayServer.Components @using BTCPayServer.Components
@using BTCPayServer @using BTCPayServer
@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Client
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using BTCPayServer.Components.QRCode
@using BTCPayServer.Components.TruncateCenter
@model BTCPayServer.Plugins.Breez.PaymentsViewModel @model BTCPayServer.Plugins.Breez.PaymentsViewModel
@{ @{
var storeId = Context.GetCurrentStoreId(); var storeId = Context.GetCurrentStoreId();
@@ -17,8 +21,8 @@
</h3> </h3>
<div class="d-flex gap-3 mt-3 mt-sm-0"> <div class="d-flex gap-3 mt-3 mt-sm-0">
<a asp-action="Send" asp-controller="Breez" asp-route-storeId="@storeId" type="submit" class="btn btn-primary">Send</a> <a permission="@Policies.CanModifyStoreSettings" 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> <a permission="@Policies.CanCreateInvoice" asp-action="Receive" asp-controller="Breez" asp-route-storeId="@storeId" type="submit" class="btn btn-primary">Receive</a>
</div> </div>
</div> </div>

View File

@@ -2,6 +2,7 @@
@using BTCPayServer @using BTCPayServer
@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Abstractions.Contracts @using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Client
@using BTCPayServer.Components.QRCode @using BTCPayServer.Components.QRCode
@using BTCPayServer.Components.TruncateCenter @using BTCPayServer.Components.TruncateCenter
@using BTCPayServer.Models.StoreViewModels @using BTCPayServer.Models.StoreViewModels
@@ -9,6 +10,7 @@
@using BTCPayServer.Plugins.Breez @using BTCPayServer.Plugins.Breez
@using BTCPayServer.Security @using BTCPayServer.Security
@using BTCPayServer.Services @using BTCPayServer.Services
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using NBitcoin @using NBitcoin
@inject BreezService BreezService @inject BreezService BreezService
@inject BTCPayNetworkProvider BtcPayNetworkProvider @inject BTCPayNetworkProvider BtcPayNetworkProvider
@@ -34,13 +36,12 @@
catch (Exception e) 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();
var f = sdk.RecommendedFees(); var f = sdk.RecommendedFees();
var pmi = new PaymentMethodId("BTC", PaymentTypes.BTCLike); var pmi = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
} }
<datalist id="fees"> <datalist id="fees">
@@ -70,11 +71,12 @@
<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>
<div>
<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> <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)
{ {
var wallet = new WalletId(storeId, "BTC"); 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> <a class="btn btn-link w-100" permission="@Policies.CanModifyStoreSettings" 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;
@@ -87,17 +89,17 @@
</form> </form>
</div> </div>
} }
} }
</div>
</div> </div>
@if (inProgressSwap.unconfirmedSats + inProgressSwap.confirmedSats + inProgressSwap.paidSats > 0) @if (inProgressSwap.unconfirmedSats + inProgressSwap.confirmedSats + (inProgressSwap.paidMsat * 1000) > 0)
{ {
<div class="card truncate-center-id"> <div class="card truncate-center-id">
<span class="text-nowrap">@inProgressSwap.unconfirmedSats sats unconfirmed</span> <span class="text-nowrap">@inProgressSwap.unconfirmedSats sats unconfirmed</span>
<span class="text-nowrap">@inProgressSwap.confirmedSats sats confirmed</span> <span class="text-nowrap">@inProgressSwap.confirmedSats sats confirmed</span>
<span class="text-nowrap">@inProgressSwap.paidSats sats paid</span> <span class="text-nowrap">@(inProgressSwap.paidMsat * 1000) sats paid</span>
</div> </div>
} }
@if (inProgressSwap.unconfirmedTxIds.Any()) @if (inProgressSwap.unconfirmedTxIds.Any())
@@ -144,12 +146,12 @@
<td>@DateTimeOffset.FromUnixTimeSeconds(refund.createdAt)</td> <td>@DateTimeOffset.FromUnixTimeSeconds(refund.createdAt)</td>
<td>@refund.bitcoinAddress</td> <td>@refund.bitcoinAddress</td>
<td> <td>
@if (refund.unconfirmedSats + refund.confirmedSats + refund.paidSats > 0) @if (refund.unconfirmedSats + refund.confirmedSats + refund.paidMsat > 0)
{ {
<div class="card truncate-center-id"> <div class="card truncate-center-id">
<span class="text-nowrap">@refund.unconfirmedSats sats unconfirmed</span> <span class="text-nowrap">@refund.unconfirmedSats sats unconfirmed</span>
<span class="text-nowrap">@refund.confirmedSats sats confirmed</span> <span class="text-nowrap">@refund.confirmedSats sats confirmed</span>
<span class="text-nowrap">@refund.paidSats sats paid</span> <span class="text-nowrap">@(refund.paidMsat * 1000) sats paid</span>
</div> </div>
} }
@if (refund.unconfirmedTxIds.Any()) @if (refund.unconfirmedTxIds.Any())

View File

@@ -1,7 +1,9 @@
@using BTCPayServer.Abstractions.Extensions @using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Client
@using BTCPayServer.Models.StoreViewModels @using BTCPayServer.Models.StoreViewModels
@using BTCPayServer.Plugins.Breez @using BTCPayServer.Plugins.Breez
@using BTCPayServer.Security @using BTCPayServer.Security
@using Microsoft.AspNetCore.Mvc.TagHelpers
@inject BreezService BreezService @inject BreezService BreezService
@{ @{
@@ -22,12 +24,12 @@
<div class="nav"> <div class="nav">
@if (sdk is not null) @if (sdk is not null)
{ {
<a asp-action="Info" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Info")">Info</a> <a permission="@Policies.CanViewStoreSettings" 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 permission="@Policies.CanViewStoreSettings" 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 In</a> <a permission="@Policies.CanCreateInvoice" 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 permission="@Policies.CanModifyStoreSettings" 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 permission="@Policies.CanModifyStoreSettings" asp-action="Configure" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Configure")">Configuration</a>
</div> </div>
</nav> </nav>
</div> </div>

View File

@@ -9,7 +9,7 @@
@if (!string.IsNullOrEmpty(storeId)) @if (!string.IsNullOrEmpty(storeId))
{ {
<li class="nav-item"> <li class="nav-item">
<a permission="@Policies.CanModifyStoreSettings" asp-controller="Breez" asp-action="Index" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez")" id="Nav-Breez"> <a permission="@Policies.CanViewStoreSettings" asp-controller="Breez" asp-action="Index" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez")" id="Nav-Breez">
<svg style="width: 15px; margin-left: 3px; margin-right: 7px;" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <svg style="width: 15px; margin-left: 3px; margin-right: 7px;" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="favicon-64-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="favicon-64-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">

View File

@@ -2,6 +2,7 @@
@using BTCPayServer.Models.StoreViewModels @using BTCPayServer.Models.StoreViewModels
@using BTCPayServer.Plugins.Breez @using BTCPayServer.Plugins.Breez
@using BTCPayServer.Security @using BTCPayServer.Security
@using BTCPayServer.Client
@inject BreezService BreezService @inject BreezService BreezService
@{ @{
string storeId = null; string storeId = null;
@@ -42,7 +43,7 @@
{ {
<div class="d-flex w-100 align-items-center justify-content-start gap-3"> <div class="d-flex w-100 align-items-center justify-content-start gap-3">
<span class="btcpay-status btcpay-status--enabled"></span> <span class="btcpay-status btcpay-status--enabled"></span>
<h6 class="text-truncate">@lspInfo.name connected</h6> <h6 class="text-truncate">@lspInfo.name LSP connected</h6>
</div> </div>
} }
<div class="store-number"> <div class="store-number">
@@ -50,7 +51,7 @@
<h6>On-Chain Balance</h6> <h6>On-Chain Balance</h6>
@if (Model is StoreDashboardViewModel && nodeState.onchainBalanceMsat > 0) @if (Model is StoreDashboardViewModel && nodeState.onchainBalanceMsat > 0)
{ {
<div> <div permission="@Policies.CanModifyStoreSettings">
<a asp-action="Sweep" asp-controller="Breez" asp-route-storeId="@storeId">Sweep</a> <a asp-action="Sweep" asp-controller="Breez" asp-route-storeId="@storeId">Sweep</a>
</div> </div>
} }
@@ -66,7 +67,7 @@
<h6>Lightning Balance</h6> <h6>Lightning Balance</h6>
@if (Model is StoreDashboardViewModel) @if (Model is StoreDashboardViewModel)
{ {
<div> <div permission="@Policies.CanModifyStoreSettings">
<a asp-action="SwapIn" asp-controller="Breez" asp-route-storeId="@storeId">Swap in</a> <a asp-action="SwapIn" asp-controller="Breez" asp-route-storeId="@storeId">Swap in</a>
@if (nodeState.channelsBalanceMsat > 0) @if (nodeState.channelsBalanceMsat > 0)
{ {

View File

@@ -15,7 +15,7 @@
if (sdk is null) if (sdk is null)
return; return;
data = sdk.ListPayments(new ListPaymentsRequest(null, null, null, null, 0, 10)); data = sdk.ListPayments(new ListPaymentsRequest(null, null, null, null, true, 0, 10));
} }
var isDashboard = Model is StoreDashboardViewModel; var isDashboard = Model is StoreDashboardViewModel;
@@ -47,7 +47,7 @@
<th class="w-125px">Id</th> <th class="w-125px">Id</th>
<th class="w-125px">Timestamp</th> <th class="w-125px">Timestamp</th>
<th class="w-125px">Type</th> <th class="w-125px">Type</th>
<th class="w-125px">Amount</th> <th class="w-125px">Amount</th>a
<th class="text-nowrap">Fee</th> <th class="text-nowrap">Fee</th>
<th class="text-nowrap">Status</th> <th class="text-nowrap">Status</th>
<th class="text-nowrap">Description</th> <th class="text-nowrap">Description</th>
@@ -57,25 +57,25 @@
@foreach (var payment in data) @foreach (var payment in data)
{ {
<tr> <tr>
<td> <td class="smMaxWidth text-truncate">
<span class="text-break">@payment.id</span> <span >@payment.id</span>
</td> </td>
<td> <td>
<span class="text-break">@DateTimeOffset.FromUnixTimeSeconds(payment.paymentTime).ToTimeAgo()</span> <span >@DateTimeOffset.FromUnixTimeSeconds(payment.paymentTime).ToTimeAgo()</span>
</td> </td>
<td> <td>
<span class="text-break">>@payment.paymentType.ToString().ToLowerInvariant().Replace("_", " ")</span> <span >@payment.paymentType.ToString().ToLowerInvariant().Replace("_", " ")</span>
</td> </td>
<td> <td>
<span class="text-break">>@LightMoney.MilliSatoshis(payment.amountMsat).ToDecimal(LightMoneyUnit.BTC) BTC</span> <span >@LightMoney.MilliSatoshis(payment.amountMsat).ToDecimal(LightMoneyUnit.BTC) BTC</span>
</td> </td>
<td> <td>
<span class="text-break">>@LightMoney.MilliSatoshis(payment.feeMsat).ToDecimal(LightMoneyUnit.BTC) BTC</span> <span >@LightMoney.MilliSatoshis(payment.feeMsat).ToDecimal(LightMoneyUnit.BTC) BTC</span>
</td> </td>
<td> <td>
<span class="text-break">@payment.status.ToString().ToLowerInvariant()</span> <span >@payment.status.ToString().ToLowerInvariant()</span>
</td> </td>
<td> <td>
<span class="text-break">@payment.description</span> <span class="text-break">@payment.description</span>

View File

@@ -1,5 +1,6 @@
@inject BreezService BreezService; @inject BreezService BreezService;
@using BTCPayServer.Plugins.Breez @using BTCPayServer.Plugins.Breez
@using BTCPayServer.Client
@model BTCPayServer.Models.StoreViewModels.LightningNodeViewModel @model BTCPayServer.Models.StoreViewModels.LightningNodeViewModel
@if (Model.CryptoCode != "BTC") @if (Model.CryptoCode != "BTC")
@@ -9,5 +10,15 @@
@{ @{
var client = BreezService.GetClient(Model.StoreId); var client = BreezService.GetClient(Model.StoreId);
} }
@if (client is null)
{
<a asp-action="Configure" asp-controller="Breez" permission="@Policies.CanModifyStoreSettings" asp-route-storeId="@Model.StoreId" value="Custom" type="radio" id="LightningNodeType-Breez" role="tab" aria-controls="BreezSetup" aria-selected="false" name="LightningNodeType">
<label for="LightningNodeType-Breez">Configure Breez</label>
</a>
}
else
{
<input value="Custom" type="radio" id="LightningNodeType-Breez" data-bs-toggle="pill" data-bs-target="#BreezSetup" role="tab" aria-controls="BreezSetup" aria-selected="false" name="LightningNodeType" disabled="@(client is null)"> <input value="Custom" type="radio" id="LightningNodeType-Breez" data-bs-toggle="pill" data-bs-target="#BreezSetup" role="tab" aria-controls="BreezSetup" aria-selected="false" name="LightningNodeType" disabled="@(client is null)">
<label for="LightningNodeType-Breez">Use Breez wallet</label> <label for="LightningNodeType-Breez">Use Breez wallet</label>
}