This commit is contained in:
Kukks
2024-10-10 11:32:00 +02:00
parent c768d7000b
commit 9b90e10b66
108 changed files with 757 additions and 1350 deletions

View File

@@ -10,7 +10,7 @@
<PropertyGroup>
<Product>Bitcoin Whitepaper</Product>
<Description>This makes the Bitcoin whitepaper available on your BTCPay Server.</Description>
<Version>1.0.4</Version>
<Version>1.0.5</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<!-- Plugin development properties -->

View File

@@ -7,7 +7,7 @@ namespace BTCPayServer.Plugins.BitcoinWhitepaper
{
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{
new() { Identifier = nameof(BTCPayServer), Condition = ">=1.12.0" }
new() { Identifier = nameof(BTCPayServer), Condition = ">=2.0.0" }
};
}
}

View File

@@ -9,7 +9,7 @@
<PropertyGroup>
<Product>Blink</Product>
<Description>Blink Lightning support</Description>
<Version>1.0.8</Version>
<Version>1.0.9</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<RootNamespace>BTCPayServer.Plugins.Blink</RootNamespace>
</PropertyGroup>

View File

@@ -12,7 +12,7 @@ namespace BTCPayServer.Plugins.Blink
{
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{
new() {Identifier = nameof(BTCPayServer), Condition = ">=1.12.0"}
new() { Identifier = nameof(BTCPayServer), Condition = ">=2.0.0" }
};

View File

@@ -9,7 +9,7 @@
<PropertyGroup>
<Product>Breez / Greenlight</Product>
<Description>Lightweight lightning baby!</Description>
<Version>1.0.8</Version>
<Version>1.0.9</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<!-- Plugin development properties -->
@@ -34,7 +34,7 @@
<ProjectReference Include="..\..\submodules\btcpayserver\BTCPayServer\BTCPayServer.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Breez.Sdk" Version="0.5.2" />
<PackageReference Include="Breez.Sdk" Version="0.6.1" />
</ItemGroup>
</Project>

View File

@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;
using Breez.Sdk;
using BTCPayServer.Abstractions.Constants;
@@ -12,13 +11,13 @@ using BTCPayServer.Lightning;
using BTCPayServer.Models;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using NBitcoin;
using NBitcoin.DataEncoders;
using NBXplorer.DerivationStrategy;
namespace BTCPayServer.Plugins.Breez;
@@ -26,15 +25,19 @@ namespace BTCPayServer.Plugins.Breez;
[Route("plugins/{storeId}/Breez")]
public class BreezController : Controller
{
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly BreezService _breezService;
private readonly BTCPayWalletProvider _btcWalletProvider;
private readonly StoreRepository _storeRepository;
public BreezController(BTCPayNetworkProvider btcPayNetworkProvider,
public BreezController(
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
BTCPayNetworkProvider btcPayNetworkProvider,
BreezService breezService,
BTCPayWalletProvider btcWalletProvider, StoreRepository storeRepository)
{
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_btcPayNetworkProvider = btcPayNetworkProvider;
_breezService = breezService;
_btcWalletProvider = btcWalletProvider;
@@ -113,7 +116,7 @@ public class BreezController : Controller
if (address.Equals("store", StringComparison.InvariantCultureIgnoreCase))
{
var store = ControllerContext.HttpContext.GetStoreData()
.GetDerivationSchemeSettings(_btcPayNetworkProvider, "BTC");
.GetDerivationSchemeSettings(_paymentMethodHandlerDictionary, "BTC");
var res = await _btcWalletProvider.GetWallet(storeId)
.ReserveAddressAsync(storeId, store.AccountDerivation, "Breez");
address = res.Address.ToString();
@@ -264,7 +267,7 @@ public class BreezController : Controller
if (address.Equals("store", StringComparison.InvariantCultureIgnoreCase))
{
var store = ControllerContext.HttpContext.GetStoreData()
.GetDerivationSchemeSettings(_btcPayNetworkProvider, "BTC");
.GetDerivationSchemeSettings(_paymentMethodHandlerDictionary, "BTC");
var res = await _btcWalletProvider.GetWallet(storeId)
.ReserveAddressAsync(storeId, store.AccountDerivation, "Breez");
address = res.Address.ToString();
@@ -342,10 +345,8 @@ public class BreezController : Controller
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");
var pmi = PaymentTypes.LN.GetPaymentMethodId("BTC");
var existing = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, _paymentMethodHandlerDictionary);
if (command == "clear")
{
@@ -355,7 +356,7 @@ public class BreezController : Controller
var isStoreSetToThisMicro = existing?.GetExternalLightningUrl() == client?.ToString();
if (client is not null && isStoreSetToThisMicro)
{
store.SetSupportedPaymentMethod(existing.PaymentId, null);
store.SetPaymentMethodConfig(_paymentMethodHandlerDictionary[pmi], null);
await _storeRepository.UpdateStore(store);
}
return RedirectToAction(nameof(Configure), new {storeId});
@@ -422,18 +423,13 @@ public class BreezController : Controller
if(existing is null)
{
existing = new LightningSupportedPaymentMethod()
{
CryptoCode = "BTC"
};
existing = new LightningPaymentMethodConfig();
var client = _breezService.GetClient(storeId);
existing.SetLightningUrl(client);
store.SetSupportedPaymentMethod(existing);
var lnurl = new LNURLPaySupportedPaymentMethod()
{
CryptoCode = "BTC",
};
store.SetSupportedPaymentMethod(lnurl);
store.SetPaymentMethodConfig(_paymentMethodHandlerDictionary[pmi], existing);
var lnurlPMI = PaymentTypes.LNURL.GetPaymentMethodId("BTC");
store.SetPaymentMethodConfig(_paymentMethodHandlerDictionary[lnurlPMI], new LNURLPaymentMethodConfig());
await _storeRepository.UpdateStore(store);
}

View File

@@ -252,7 +252,7 @@ public class BreezLightningClient : ILightningClient, IDisposable, EventListener
OffchainBalance = new OffchainBalance()
{
Local = LightMoney.MilliSatoshis(ni.channelsBalanceMsat),
Remote = LightMoney.MilliSatoshis(ni.inboundLiquidityMsats),
Remote = LightMoney.MilliSatoshis(ni.totalInboundLiquidityMsats),
}
};
}
@@ -275,7 +275,7 @@ public class BreezLightningClient : ILightningClient, IDisposable, EventListener
}
else
{
result = Sdk.SendPayment(new SendPaymentRequest(bolt11, (ulong?) payParams.Amount?.MilliSatoshi));
result = Sdk.SendPayment(new SendPaymentRequest(bolt11,false, (ulong?) payParams.Amount?.MilliSatoshi));
}
var details = result.payment.details as PaymentDetails.Ln;

View File

@@ -12,7 +12,7 @@ namespace BTCPayServer.Plugins.Breez
{
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{
new() { Identifier = nameof(BTCPayServer), Condition = ">=1.13.2" }
new() { Identifier = nameof(BTCPayServer), Condition = ">=2.0.0" }
};
public override void Execute(IServiceCollection applicationBuilder)

View File

@@ -11,7 +11,9 @@ using BTCPayServer.Events;
using BTCPayServer.HostedServices;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NBitcoin;
@@ -22,7 +24,8 @@ public class BreezService:EventHostedServiceBase
{
private readonly StoreRepository _storeRepository;
private readonly IOptions<DataDirectories> _dataDirectories;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly IServiceProvider _serviceProvider;
private PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary => _serviceProvider.GetRequiredService<PaymentMethodHandlerDictionary>();
private readonly ILogger _logger;
private Dictionary<string, BreezSettings> _settings;
private Dictionary<string, BreezLightningClient> _clients = new();
@@ -31,12 +34,12 @@ public class BreezService:EventHostedServiceBase
EventAggregator eventAggregator,
StoreRepository storeRepository,
IOptions<DataDirectories> dataDirectories,
BTCPayNetworkProvider btcPayNetworkProvider,
IServiceProvider serviceProvider,
ILogger<BreezService> logger) : base(eventAggregator, logger)
{
_storeRepository = storeRepository;
_dataDirectories = dataDirectories;
_btcPayNetworkProvider = btcPayNetworkProvider;
_serviceProvider = serviceProvider;
_logger = logger;
}
@@ -133,13 +136,13 @@ public class BreezService:EventHostedServiceBase
{
_settings.Remove(storeId, out var oldSettings );
var data = await _storeRepository.FindStore(storeId);
var existing = data?.GetSupportedPaymentMethods(_btcPayNetworkProvider)
.OfType<LightningSupportedPaymentMethod>().FirstOrDefault(method =>
method.CryptoCode == "BTC" && method.PaymentId.PaymentType == LightningPaymentType.Instance);
var pmi = PaymentTypes.LN.GetPaymentMethodId("BTC");
var existing =
data?.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, _paymentMethodHandlerDictionary);
var isBreez = existing?.GetExternalLightningUrl() == $"type=breez;key={oldSettings.PaymentKey}";
if (isBreez)
{
data.SetSupportedPaymentMethod(new PaymentMethodId("BTC", LightningPaymentType.Instance), null );
data.SetPaymentMethodConfig(_paymentMethodHandlerDictionary[pmi], null );
await _storeRepository.UpdateStore(data);
}
Directory.Delete(GetWorkDir(storeId), true);

View File

@@ -1,7 +1,5 @@
@using Breez.Sdk
@using BTCPayServer
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Client
@using BTCPayServer.Components.QRCode
@using BTCPayServer.Components.TruncateCenter
@@ -10,13 +8,15 @@
@using BTCPayServer.Plugins.Breez
@using BTCPayServer.Security
@using BTCPayServer.Services
@using BTCPayServer.Services.Invoices
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using NBitcoin
@inject BreezService BreezService
@inject BTCPayNetworkProvider BtcPayNetworkProvider
@inject TransactionLinkProviders TransactionLinkProviders
@inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
@{
ViewData.SetActivePage("Breez", "Swap In", "SwapIn");
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
string storeId = Model switch
{
string s => s,
@@ -38,10 +38,9 @@
}
var refundables = sdk.ListRefundables();
var deriv = Context.GetStoreData().GetDerivationSchemeSettings(BtcPayNetworkProvider, "BTC");
var deriv = Context.GetStoreData().GetDerivationSchemeSettings(PaymentMethodHandlerDictionary, "BTC");
var ni = sdk.NodeInfo();
var f = sdk.RecommendedFees();
var pmi = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
}
<datalist id="fees">

View File

@@ -1,18 +1,18 @@
@using BTCPayServer
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Plugins.Breez
@using BTCPayServer.Security
@using BTCPayServer.Services.Invoices
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using Microsoft.AspNetCore.Routing
@model string
@inject BreezService BreezService
@inject BTCPayNetworkProvider BtcPayNetworkProvider
@inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
@{
var storeId = Context.GetImplicitStoreId();
var address = Context.GetRouteValue("address").ToString();
ViewData.SetActivePage("Breez", "Create Swapin Refund", "SwapIn");
var deriv = Context.GetStoreData().GetDerivationSchemeSettings(BtcPayNetworkProvider, "BTC");
var deriv = Context.GetStoreData().GetDerivationSchemeSettings(PaymentMethodHandlerDictionary, "BTC");
var sdk = BreezService.GetClient(storeId)?.Sdk;
var f = sdk.RecommendedFees();
}

View File

@@ -1,11 +1,12 @@
@using Breez.Sdk
@using BTCPayServer
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Models.StoreViewModels
@using BTCPayServer.Plugins.Breez
@using BTCPayServer.Security
@using BTCPayServer.Services.Invoices
@using Microsoft.AspNetCore.Mvc.TagHelpers
@inject BreezService BreezService
@inject BTCPayNetworkProvider BtcPayNetworkProvider
@inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
@{
@@ -29,7 +30,7 @@
if (sdk is null)
return;
var inProgressSwaps = sdk.InProgressReverseSwaps();
var deriv = Context.GetStoreData().GetDerivationSchemeSettings(BtcPayNetworkProvider, "BTC");
var deriv = Context.GetStoreData().GetDerivationSchemeSettings(PaymentMethodHandlerDictionary, "BTC");
var f = sdk.RecommendedFees();
var swapOutRec = sdk.FetchReverseSwapFees(new ReverseSwapFeesRequest());
}

View File

@@ -2,9 +2,10 @@
@using BTCPayServer.Models.StoreViewModels
@using BTCPayServer.Plugins.Breez
@using BTCPayServer.Security
@using BTCPayServer.Services.Invoices
@using Microsoft.AspNetCore.Mvc.TagHelpers
@inject BreezService BreezService
@inject BTCPayNetworkProvider BtcPayNetworkProvider
@inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
@{
Layout = "_Layout";
ViewData.SetActivePage("Breez", "Sweep", "Sweep");
@@ -24,7 +25,7 @@
var sdk = BreezService.GetClient(storeId)?.Sdk;
if (sdk is null)
return;
var deriv = Context.GetStoreData().GetDerivationSchemeSettings(BtcPayNetworkProvider, "BTC");
var deriv = Context.GetStoreData().GetDerivationSchemeSettings(PaymentMethodHandlerDictionary, "BTC");
var f = sdk.RecommendedFees();
}
<datalist id="fees">

View File

@@ -1,10 +0,0 @@
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData["NavPartialName"] = "_Nav";
}
<style>
#mainContent > section {
padding: 3rem !important;
}
</style>
@RenderBody()

View File

@@ -1,41 +0,0 @@
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Client
@using BTCPayServer.Models.StoreViewModels
@using BTCPayServer.Plugins.Breez
@using BTCPayServer.Security
@using Microsoft.AspNetCore.Mvc.TagHelpers
@inject BreezService BreezService
@{
var storeId = Model switch
{
string s => s,
StoreDashboardViewModel dashboardModel => dashboardModel.StoreId,
_ => Context.GetImplicitStoreId()
};
var client = BreezService.GetClient(storeId);
var sdk = client?.Sdk;
}
<div class="sticky-header-setup"></div>
<div class="sticky-header mb-l">
<h2 class="mt-1 mb-2 mb-lg-4">Breez / Greenlight</h2>
@if (sdk is not null)
{
<nav id="SectionNav">
<div class="nav">
<a permission="@Policies.CanViewStoreSettings" asp-action="Info" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Info")">Info</a>
<a permission="@Policies.CanViewStoreSettings" asp-action="Payments" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Payments")">Payments</a>
<a permission="@Policies.CanCreateInvoice" asp-action="SwapIn" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "SwapIn")">Swap In</a>
<a permission="@Policies.CanModifyStoreSettings" asp-action="SwapOut" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "SwapOut")">Swap Out</a>
<a permission="@Policies.CanModifyStoreSettings" asp-action="Configure" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Configure")">Configuration</a>
@if (client.Events.Any())
{
<a permission="@Policies.CanViewStoreSettings" asp-action="Logs" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Logs")">Logs</a>
}
</div>
</nav>
}
</div>

View File

@@ -1,27 +1,67 @@
@using Breez.Sdk
@using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Client
@using BTCPayServer.Models.StoreViewModels
@using BTCPayServer.Plugins.Breez
@using BTCPayServer.Security
@using Microsoft.AspNetCore.Mvc.TagHelpers
@inject IScopeProvider ScopeProvider
@inject BreezService BreezService
@{
var storeId = ScopeProvider.GetCurrentStoreId();
var storeId = Model switch
{
string s => s,
StoreDashboardViewModel dashboardModel => dashboardModel.StoreId,
_ => Context.GetImplicitStoreId()
};
var active = @ViewData.IsActivePage("Breez");
var client = string.IsNullOrEmpty(active) ? null : BreezService.GetClient(storeId);
var sdk = client?.Sdk;
}
@if (!string.IsNullOrEmpty(storeId))
{
<li class="nav-item">
<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">
<g id="favicon-64-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group" transform="translate(3.000000, 2.000000)" fill-rule="nonzero">
<path d="M35.4723926,39.8214286 C35.4723926,37.9541367 33.9340883,36.4285714 32.0236136,36.4285714 L10.3260796,36.4285714 C8.42801059,36.4285714 6.87730061,37.9419322 6.87730061,39.8214286 C6.87730061,41.6887205 8.41560491,43.2142857 10.3260796,43.2142857 L32.0236136,43.2142857 C33.9216827,43.2020812 35.4723926,41.6887205 35.4723926,39.8214286 Z" id="Shape" fill="currentColor"></path>
<path d="M49.7395693,25.9068425 C51.3370507,23.7180544 52.2855553,21.0346249 52.2855553,18.1162407 C52.2855553,10.7460841 46.257559,4.77328937 38.819286,4.77328937 L36.9721981,4.77328937 L36.9721981,3.32646331 C36.9721981,1.48392415 35.462079,0 33.614991,0 C31.7554228,0 30.257784,1.49629019 30.257784,3.32646331 L30.257784,4.77328937 L26.8381753,4.77328937 L26.8381753,4.7856554 L24.6790793,4.7856554 C22.8819126,4.7856554 21.4341951,6.23248145 21.4341951,8.0008244 C21.4341951,9.78153339 22.894393,11.2159934 24.6790793,11.2159934 L26.8381753,11.2159934 L26.8381753,11.2283594 L38.7693647,11.2283594 C42.5009815,11.2283594 45.5212199,14.2209398 45.5212199,17.9183842 L45.5212199,17.9307502 C45.5212199,21.5292663 42.6507454,24.5342127 39.0439319,24.6084089 C38.3824747,24.6207749 36.3856229,24.6084089 36.3856229,24.6084089 C36.3731426,24.6084089 36.3481819,24.6084089 36.3357016,24.6084089 L36.310741,24.6084089 C34.3263695,24.657873 32.7413684,26.3520198 32.9535339,28.3800495 C33.1407387,30.1483924 34.7382202,31.4468261 36.5353868,31.4468261 L43.3496435,31.4468261 C44.0485417,31.4468261 44.7599201,31.4962902 45.4338576,31.6694147 C49.0905925,32.6215993 51.7988227,35.9109646 51.7988227,39.8433636 C51.7988227,44.5053586 47.9798437,48.2893652 43.2747616,48.2893652 L22.7071881,48.2893652 L18.8382878,48.2893652 C17.0161605,48.2893652 15.3937184,49.6125309 15.2189939,51.4056059 C15.0193087,53.4583677 16.6542311,55.1896125 18.6885239,55.1896125 L30.257784,55.1896125 L30.257784,56.6735367 C30.257784,58.5160758 31.7679031,60 33.614991,60 C35.4745593,60 36.9721981,58.5037098 36.9721981,56.6735367 L36.9721981,55.1896125 L39.8925939,55.1896125 C39.8925939,55.1896125 39.8925939,55.1896125 39.9050742,55.1896125 L43.5742894,55.1896125 C52.0983505,55.1896125 58.9999695,48.3388293 58.9999695,39.9051937 C59.0124498,33.6479802 55.1934708,28.2563891 49.7395693,25.9068425 Z" id="Shape" fill="currentColor"></path>
<path d="M22.4417178,28.0357143 C22.4417178,26.1684224 20.8948734,24.6428571 18.9737925,24.6428571 L3.46792526,24.6428571 C1.55931891,24.6428571 0,26.1562179 0,28.0357143 C0,29.9030062 1.54684436,31.4285714 3.46792526,31.4285714 L18.9737925,31.4285714 C20.8948734,31.4285714 22.4417178,29.9030062 22.4417178,28.0357143 Z" id="Shape" fill="currentColor"></path>
<path d="M15.7746737,21.4285714 L31.280541,21.4285714 C33.1891473,21.4285714 34.7484663,19.9152107 34.7484663,18.0357143 C34.7484663,16.1684224 33.2016219,14.6428571 31.280541,14.6428571 L15.7746737,14.6428571 C13.8660674,14.6428571 12.3067485,16.1562179 12.3067485,18.0357143 C12.3067485,19.9030062 13.8535928,21.4285714 15.7746737,21.4285714 Z" id="Shape" fill="currentColor"></path>
</g>
</g>
</svg>
<a permission="@Policies.CanViewStoreSettings" asp-controller="Breez" asp-action="Index" asp-route-storeId="@storeId" class="nav-link @active" 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">
<g id="favicon-64-2" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Group" transform="translate(3.000000, 2.000000)" fill-rule="nonzero">
<path d="M35.4723926,39.8214286 C35.4723926,37.9541367 33.9340883,36.4285714 32.0236136,36.4285714 L10.3260796,36.4285714 C8.42801059,36.4285714 6.87730061,37.9419322 6.87730061,39.8214286 C6.87730061,41.6887205 8.41560491,43.2142857 10.3260796,43.2142857 L32.0236136,43.2142857 C33.9216827,43.2020812 35.4723926,41.6887205 35.4723926,39.8214286 Z" id="Shape" fill="currentColor"></path>
<path d="M49.7395693,25.9068425 C51.3370507,23.7180544 52.2855553,21.0346249 52.2855553,18.1162407 C52.2855553,10.7460841 46.257559,4.77328937 38.819286,4.77328937 L36.9721981,4.77328937 L36.9721981,3.32646331 C36.9721981,1.48392415 35.462079,0 33.614991,0 C31.7554228,0 30.257784,1.49629019 30.257784,3.32646331 L30.257784,4.77328937 L26.8381753,4.77328937 L26.8381753,4.7856554 L24.6790793,4.7856554 C22.8819126,4.7856554 21.4341951,6.23248145 21.4341951,8.0008244 C21.4341951,9.78153339 22.894393,11.2159934 24.6790793,11.2159934 L26.8381753,11.2159934 L26.8381753,11.2283594 L38.7693647,11.2283594 C42.5009815,11.2283594 45.5212199,14.2209398 45.5212199,17.9183842 L45.5212199,17.9307502 C45.5212199,21.5292663 42.6507454,24.5342127 39.0439319,24.6084089 C38.3824747,24.6207749 36.3856229,24.6084089 36.3856229,24.6084089 C36.3731426,24.6084089 36.3481819,24.6084089 36.3357016,24.6084089 L36.310741,24.6084089 C34.3263695,24.657873 32.7413684,26.3520198 32.9535339,28.3800495 C33.1407387,30.1483924 34.7382202,31.4468261 36.5353868,31.4468261 L43.3496435,31.4468261 C44.0485417,31.4468261 44.7599201,31.4962902 45.4338576,31.6694147 C49.0905925,32.6215993 51.7988227,35.9109646 51.7988227,39.8433636 C51.7988227,44.5053586 47.9798437,48.2893652 43.2747616,48.2893652 L22.7071881,48.2893652 L18.8382878,48.2893652 C17.0161605,48.2893652 15.3937184,49.6125309 15.2189939,51.4056059 C15.0193087,53.4583677 16.6542311,55.1896125 18.6885239,55.1896125 L30.257784,55.1896125 L30.257784,56.6735367 C30.257784,58.5160758 31.7679031,60 33.614991,60 C35.4745593,60 36.9721981,58.5037098 36.9721981,56.6735367 L36.9721981,55.1896125 L39.8925939,55.1896125 C39.8925939,55.1896125 39.8925939,55.1896125 39.9050742,55.1896125 L43.5742894,55.1896125 C52.0983505,55.1896125 58.9999695,48.3388293 58.9999695,39.9051937 C59.0124498,33.6479802 55.1934708,28.2563891 49.7395693,25.9068425 Z" id="Shape" fill="currentColor"></path>
<path d="M22.4417178,28.0357143 C22.4417178,26.1684224 20.8948734,24.6428571 18.9737925,24.6428571 L3.46792526,24.6428571 C1.55931891,24.6428571 0,26.1562179 0,28.0357143 C0,29.9030062 1.54684436,31.4285714 3.46792526,31.4285714 L18.9737925,31.4285714 C20.8948734,31.4285714 22.4417178,29.9030062 22.4417178,28.0357143 Z" id="Shape" fill="currentColor"></path>
<path d="M15.7746737,21.4285714 L31.280541,21.4285714 C33.1891473,21.4285714 34.7484663,19.9152107 34.7484663,18.0357143 C34.7484663,16.1684224 33.2016219,14.6428571 31.280541,14.6428571 L15.7746737,14.6428571 C13.8660674,14.6428571 12.3067485,16.1562179 12.3067485,18.0357143 C12.3067485,19.9030062 13.8535928,21.4285714 15.7746737,21.4285714 Z" id="Shape" fill="currentColor"></path>
</g>
</g>
</svg>
<span>Breez</span>
</a>
</li>
}
@if (sdk is not null)
{
<li class="nav-item nav-item-sub">
<a permission="@Policies.CanViewStoreSettings" asp-action="Info" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Info")">Info</a>
</li>
<li class="nav-item nav-item-sub">
<a permission="@Policies.CanViewStoreSettings" asp-action="Payments" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Payments")">Payments</a>
</li>
<li class="nav-item nav-item-sub">
<a permission="@Policies.CanCreateInvoice" asp-action="SwapIn" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "SwapIn")">Swap In</a>
</li>
<li class="nav-item nav-item-sub">
<a permission="@Policies.CanModifyStoreSettings" asp-action="SwapOut" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "SwapOut")">Swap Out</a>
</li>
<li class="nav-item nav-item-sub">
<a permission="@Policies.CanModifyStoreSettings" asp-action="Configure" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Configure")">Configuration</a>
</li>
@if (client.Events.Any())
{
<li class="nav-item nav-item-sub">
<a permission="@Policies.CanViewStoreSettings" asp-action="Logs" asp-route-storeId="@storeId" class="nav-link @ViewData.IsActivePage("Breez", null, "Logs")">Logs</a>
</li>
}
}
}

View File

@@ -103,7 +103,7 @@
<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).ToUnit(LightMoneyUnit.BTC)" data-sensitive>@LightMoney.MilliSatoshis(nodeState.inboundLiquidityMsats).ToUnit(LightMoneyUnit.BTC)</h3>
<h3 class="d-inline-block me-1" data-balance="@LightMoney.MilliSatoshis(nodeState.totalInboundLiquidityMsats).ToUnit(LightMoneyUnit.BTC)" data-sensitive>@LightMoney.MilliSatoshis(nodeState.totalInboundLiquidityMsats).ToUnit(LightMoneyUnit.BTC)</h3>
<span class="text-secondary fw-semibold currency">BTC inbound liquidity</span>
</div>
<div class="balance d-flex align-items-baseline gap-1">

View File

@@ -9,7 +9,7 @@
<PropertyGroup>
<Product>Bringin</Product>
<Description>Euro Offramp</Description>
<Version>1.0.1</Version>
<Version>1.0.2</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<!-- Plugin development properties -->
@@ -41,6 +41,6 @@
<ItemGroup>
<PackageReference Include="AsyncKeyedLock" Version="6.3.4" />
<PackageReference Include="AsyncKeyedLock" Version="7.0.1" />
</ItemGroup>
</Project>

View File

@@ -11,7 +11,10 @@ public class BringinPlugin : BaseBTCPayServerPlugin
{
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{
new() {Identifier = nameof(BTCPayServer), Condition = ">=1.12.0"}
new()
{
Identifier = nameof(BTCPayServer), Condition = ">=2.0.0"
}
};
public override void Execute(IServiceCollection applicationBuilder)

View File

@@ -17,6 +17,7 @@ using BTCPayServer.Events;
using BTCPayServer.HostedServices;
using BTCPayServer.Lightning;
using BTCPayServer.Payments;
using BTCPayServer.Payouts;
using BTCPayServer.Services;
using BTCPayServer.Services.Stores;
using Microsoft.Extensions.Logging;
@@ -139,13 +140,15 @@ public class BringinService : EventHostedServiceBase
invoiceEvent when bringinStoreSettings.Enabled:
var pmPayments = invoiceEvent.Invoice.GetPayments("BTC", true)
.GroupBy(payment => payment.GetPaymentMethodId());
.GroupBy(payment => payment.PaymentMethodId);
var update = false;
foreach (var pmPayment in pmPayments)
{
var methodId = pmPayment.Key;
if (methodId.PaymentType == PaymentTypes.LNURLPay)
methodId = new PaymentMethodId(methodId.CryptoCode, PaymentTypes.LightningLike);
if (methodId == PaymentTypes.LNURL.GetPaymentMethodId("BTC"))
{
methodId = PaymentTypes.LN.GetPaymentMethodId("BTC");
}
if (!bringinStoreSettings.MethodSettings.TryGetValue(methodId.ToString(),
out var methodSettings))
{
@@ -153,7 +156,7 @@ public class BringinService : EventHostedServiceBase
}
methodSettings.CurrentBalance +=
pmPayment.Sum(payment => payment.GetCryptoPaymentData().GetValue());
pmPayment.Sum(payment => payment.Value);
update = true;
}
@@ -190,14 +193,14 @@ public class BringinService : EventHostedServiceBase
foreach (var methodSetting in bringinStoreSetting.MethodSettings)
{
var pmi = PaymentMethodId.TryParse(methodSetting.Key);
var pmi = PayoutMethodId.TryParse(methodSetting.Key);
if (pmi is null)
{
continue;
}
var isOnChain = PayoutTypes.CHAIN.GetPayoutMethodId("BTC") == pmi;
// if there is a pending payout, try and cancel it if this is onchain as we want to save needless tx fees
if (methodSetting.Value.PendingPayouts.Count > 0 && pmi.PaymentType == BitcoinPaymentType.Instance)
if (methodSetting.Value.PendingPayouts.Count > 0 && isOnChain)
{
var cancelResult = await _pullPaymentHostedService.Cancel(
new PullPaymentHostedService.CancelRequest(methodSetting.Value.PendingPayouts.ToArray(),
@@ -252,16 +255,16 @@ public class BringinService : EventHostedServiceBase
return result;
}
public async Task<string?> CreateOrder(string storeId, PaymentMethodId paymentMethodId, Money amountBtc, bool payout)
public async Task<string?> CreateOrder(string storeId, PayoutMethodId paymentMethodId, Money amountBtc, bool payout)
{
if (SupportedMethods.All(supportedMethod => supportedMethod.PaymentMethod != paymentMethodId))
if (SupportedMethods.All(supportedMethod => supportedMethod.PayoutMethod != paymentMethodId))
{
throw new NotSupportedException($"{paymentMethodId.ToPrettyString()} Payment method not supported");
throw new NotSupportedException($"{paymentMethodId} Payment method not supported");
}
var settings = _settings[storeId];
var bringinClient = settings.CreateClient(_httpClientFactory, _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode).NBitcoinNetwork);
var bringinClient = settings.CreateClient(_httpClientFactory, _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBitcoinNetwork);
var host = await Dns.GetHostEntryAsync(Dns.GetHostName(), CancellationToken.None);
@@ -270,7 +273,7 @@ public class BringinService : EventHostedServiceBase
var supportedMethod = SupportedMethods.First(supportedMethod => supportedMethod.PaymentMethod == paymentMethodId);
var supportedMethod = SupportedMethods.First(supportedMethod => supportedMethod.PayoutMethod == paymentMethodId);
//check if amount is enough
if (supportedMethod.FiatMinimumAmount > 0)
{
@@ -297,13 +300,13 @@ public class BringinService : EventHostedServiceBase
{
return order.Invoice?? order.DepositAddress;
}
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
var destination = !string.IsNullOrEmpty(order.Invoice)? (IClaimDestination) new BoltInvoiceClaimDestination(order.Invoice, BOLT11PaymentRequest.Parse(order.Invoice, network.NBitcoinNetwork)):
new AddressClaimDestination(BitcoinAddress.Create(order.DepositAddress, network.NBitcoinNetwork));
var claim = await _pullPaymentHostedService.Claim(new ClaimRequest()
{
PaymentMethodId = paymentMethodId,
PayoutMethodId = paymentMethodId,
StoreId = storeId,
Destination = destination,
Value = orderMoney.ToUnit(MoneyUnit.BTC),
@@ -363,17 +366,17 @@ public class BringinService : EventHostedServiceBase
{
case PayoutState.Completed:
// remove from pending payouts list in a setting
return _settings[payout.StoreDataId].MethodSettings[payout.GetPaymentMethodId().ToString()]
return _settings[payout.StoreDataId].MethodSettings[payout.GetPayoutMethodId().ToString()]
.PendingPayouts.Remove(payout.Id);
case PayoutState.Cancelled:
// remove from pending payouts list in a setting and add to a balance
var result = _settings[payout.StoreDataId].MethodSettings[payout.GetPaymentMethodId().ToString()]
var result = _settings[payout.StoreDataId].MethodSettings[payout.GetPayoutMethodId().ToString()]
.PendingPayouts.Remove(payout.Id);
var pmi = payout.GetPaymentMethodId();
var pmi = payout.GetPayoutMethodId();
if (_settings[payout.StoreDataId].MethodSettings
.TryGetValue(pmi.ToString(), out var methodSettings))
{
methodSettings.CurrentBalance += payout.GetBlob(_btcPayNetworkJsonSerializerSettings).Amount;
methodSettings.CurrentBalance += payout.Amount ?? payout.OriginalAmount;
result = true;
}
@@ -389,12 +392,12 @@ public class BringinService : EventHostedServiceBase
return _settings.TryGetValue(storeId, out var bringinStoreSettings) ? bringinStoreSettings : null;
}
public record SupportedMethodOptions(PaymentMethodId PaymentMethod, bool FiatMinimum, decimal FiatMinimumAmount, string bringinMethod);
public record SupportedMethodOptions(PayoutMethodId PayoutMethod, bool FiatMinimum, decimal FiatMinimumAmount, string bringinMethod);
public static readonly SupportedMethodOptions[] SupportedMethods = new[]
{
new SupportedMethodOptions(new PaymentMethodId("BTC", LightningPaymentType.Instance), true, 22, "LIGHTNING"),
new SupportedMethodOptions(new PaymentMethodId("BTC", BitcoinPaymentType.Instance), true, 22, "ON_CHAIN"),
new SupportedMethodOptions(PayoutTypes.LN.GetPayoutMethodId("BTC"), true, 22, "LIGHTNING"),
new SupportedMethodOptions(PayoutTypes.CHAIN.GetPayoutMethodId("BTC"), true, 22, "ON_CHAIN"),
};
private ConcurrentDictionary<string, (IDisposable, BringinStoreSettings, DateTimeOffset Expiry)> _editModes = new();
@@ -412,9 +415,9 @@ public class BringinService : EventHostedServiceBase
// add or remove any missing methods in result
foreach (var supportedMethod in SupportedMethods)
{
if (!result.MethodSettings.ContainsKey(supportedMethod.PaymentMethod.ToString()))
if (!result.MethodSettings.ContainsKey(supportedMethod.PayoutMethod.ToString()))
{
result.MethodSettings.Add(supportedMethod.PaymentMethod.ToString(),
result.MethodSettings.Add(supportedMethod.PayoutMethod.ToString(),
new BringinStoreSettings.PaymentMethodSettings()
{
FiatThreshold = supportedMethod.FiatMinimum,
@@ -424,7 +427,7 @@ public class BringinService : EventHostedServiceBase
}
result.MethodSettings = result.MethodSettings.Where(pair =>
SupportedMethods.Any(supportedMethod => supportedMethod.PaymentMethod.ToString() == pair.Key))
SupportedMethods.Any(supportedMethod => supportedMethod.PayoutMethod.ToString() == pair.Key))
.ToDictionary(pair => pair.Key, pair => pair.Value);
isNew = true;
return (storeLock, result, DateTimeOffset.Now.AddMinutes(5));

View File

@@ -4,7 +4,9 @@
@using BTCPayServer.Data
@using BTCPayServer.Payments
@using BTCPayServer.PayoutProcessors
@using BTCPayServer.Payouts
@using BTCPayServer.Services
@using BTCPayServer.Services.Invoices
@using BTCPayServer.Services.Stores
@using Microsoft.AspNetCore.Http
@using Microsoft.AspNetCore.Routing
@@ -25,6 +27,7 @@
[Inject] private IHttpClientFactory HttpClientFactory { get; set; }
[Inject] private PayoutProcessorService PayoutProcessorService { get; set; }
[Inject] private IAuthorizationService AuthorizationService { get; set; }
[Inject] private PayoutMethodHandlerDictionary PayoutMethodHandlerDictionary { get; set; }
[Parameter] public string StoreId { get; set; }
private decimal? LastFiatBalance { get; set; }
private DateTimeOffset? LastDataFetch { get; set; }
@@ -79,12 +82,13 @@
PmiLink = $"A payout processor has not been configured for this payment method. Payouts generated by Bringin will not be automatically handled. <a href=\"{LinkGenerator.GetUriByAction(HttpContextAccessor.HttpContext, "ConfigureStorePayoutProcessors", "UIPayoutProcessors", new {StoreId})}\">Configure now</a>";
_callbackLink = LinkGenerator.GetUriByAction(HttpContextAccessor.HttpContext, "Callback", "Bringin", new {StoreId});
_settings = BringinService.IsInEditMode(StoreId) ? await BringinService.Update(StoreId) : await BringinService.Get(StoreId);
_pms = (await StoreRepository.FindStore(StoreId)).GetSupportedPaymentMethods(BTCPayNetworkProvider).Select(method => method.PaymentId).Where(id => id.CryptoCode == "BTC").ToArray();
var store = await StoreRepository.FindStore(StoreId);
_pms = PayoutMethodHandlerDictionary.GetSupportedPayoutMethods(store);
_pps = (await PayoutProcessorService.GetProcessors(new PayoutProcessorService.PayoutProcessorQuery()
{
Stores = new[] {StoreId},
PaymentMethods = _pms.Select(p => p.ToString()).ToArray()
})).Select(data => PaymentMethodId.TryParse(data.PaymentMethod)).Where(id => id is not null).ToArray();
PayoutMethods = _pms.Select(p => p).ToArray()
})).Select(data => PayoutMethodId.TryParse(data.PayoutMethodId)).Where(id => id is not null).ToArray();
EditMode = BringinService.IsInEditMode(StoreId);
IsLoaded = true;
_ = FetchBalanceAndRate();
@@ -148,8 +152,8 @@
}
private bool _saving;
private PaymentMethodId[] _pms;
private PaymentMethodId[] _pps;
private HashSet<PayoutMethodId> _pms;
private PayoutMethodId[] _pps;
private bool _apiKeyError;
private async Task Save()
@@ -269,7 +273,7 @@
_saving = true;
await InvokeAsync(StateHasChanged);
var pm = PaymentMethodId.TryParse(ManualOrderPaymentMethod);
var pm = PayoutMethodId.TryParse(ManualOrderPaymentMethod);
if (pm is null)
{
SaveError = "Invalid payment method";
@@ -391,8 +395,8 @@
}
else if (_manualOrder)
{
var items = new List<PaymentMethodId>();
items.AddRange(BringinService.SupportedMethods.Where(s => _pms.Contains(s.PaymentMethod)).Select(s => s.PaymentMethod));
var items = new List<PayoutMethodId>();
items.AddRange(BringinService.SupportedMethods.Where(s => _pms.Contains(s.PayoutMethod)).Select(s => s.PayoutMethod));
<div class="row">
@@ -422,7 +426,7 @@
<option value="">Select a payment method</option>
@foreach (var opt in items)
{
<option value="@opt.ToString()">@opt.ToPrettyString()</option>
<option value="@opt.ToString()">@opt</option>
}
</select>
</div>
@@ -472,7 +476,7 @@
@foreach (var method in _settings.MethodSettings)
{
var pmi = PaymentMethodId.TryParse(method.Key);
var pmi = PayoutMethodId.TryParse(method.Key);
if (pmi is null)
continue;
if (!_pms.Contains(pmi))
@@ -480,7 +484,7 @@
<hr class=""/>
<div class="store-number">
<header>
<h6>@pmi.ToPrettyString()</h6>
<h6>@pmi</h6>
</header>
@if (LastFiatRate is null || !method.Value.FiatThreshold)
@@ -504,7 +508,7 @@
<span class="text-secondary"> (@DisplayFormatter.Currency(balanceInFiat.Value, "EUR", DisplayFormatter.CurrencyFormat.Code)) pending to forward once @DisplayFormatter.Currency(thresholdinBtc.Value, "BTC", DisplayFormatter.CurrencyFormat.Code) (@DisplayFormatter.Currency(method.Value.Threshold, "EUR")) is reached.</span>
@if (method.Value.CurrentBalance > 0)
{
<button class="btn btn-link" @onclick="() => ResetBalance(pmi)" disabled="@_saving">Reset balance</button>
<button class="btn btn-link" @onclick="() => ResetBalance(PaymentMethodId.Parse(pmi.ToString()))" disabled="@_saving">Reset balance</button>
//Clear balance
}
</div>
@@ -579,9 +583,9 @@
var pmId = PaymentMethodId.TryParse(method.Key);
if (pmId is null)
continue;
var supportedMethod = BringinService.SupportedMethods.FirstOrDefault(s => s.PaymentMethod.ToString() == method.Key);
var supportedMethod = BringinService.SupportedMethods.FirstOrDefault(s => s.PayoutMethod.ToString() == method.Key);
<div class="col-xxl-constrain col-12 @(_settings.MethodSettings.Count > 1 ? $"col-xl-6 {(i == 0 ? "border-end" : "")}" : "")">
<h5 class=" border-bottom-0 text-muted mb-4">@pmId.ToPrettyString()</h5>
<h5 class=" border-bottom-0 text-muted mb-4">@pmId</h5>
<div class="card-body">
<div class="form-group">
<label class="form-label">Percentage</label>
@@ -644,15 +648,16 @@
<tr>
<td>@tx.CreatedAt.ToTimeAgo()</td>
<td>@tx.SubType.ToHumanReadable()</td>
<td>
@tx.Status.ToHumanReadable()
</td>
<td>@tx.Status.ToHumanReadable()</td>
<td class="amount-col">
<span data-sensitive>@(tx.SourceCurrency == "BTC" ? Money.Satoshis(tx.SourceAmount).ToDecimal(MoneyUnit.BTC): tx.SourceAmount)@tx.SourceCurrency -> @tx.DestinationAmount @tx.DestinationCurrency </span>
<span data-sensitive>
@(tx.SourceCurrency == "BTC" ? Money.Satoshis(tx.SourceAmount).ToDecimal(MoneyUnit.BTC) : tx.SourceAmount) @tx.SourceCurrency
-> @tx.DestinationAmount @tx.DestinationCurrency
</span>
</td>
</tr>
}
</tbody>
</tbody>s
</table>
</div>
}

View File

@@ -9,7 +9,7 @@
<PropertyGroup>
<Product>Data Erasure</Product>
<Description>Allows you to erase user data from invoices after a period of time.</Description>
<Version>1.0.2</Version>
<Version>1.0.3</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<!-- Plugin development properties -->

View File

@@ -12,6 +12,7 @@ namespace BTCPayServer.Plugins.DataErasure
public class DataErasureController : Controller
{
private readonly DataErasureService _dataErasureService;
public DataErasureController(DataErasureService dataErasureService)
{
_dataErasureService = dataErasureService;
@@ -31,10 +32,11 @@ namespace BTCPayServer.Plugins.DataErasure
{
if (_dataErasureService.IsRunning)
{
TempData["ErrorMessage"] = "Data erasure is currently running and cannot be changed. Please try again later.";
TempData["ErrorMessage"] =
"Data erasure is currently running and cannot be changed. Please try again later.";
}
if (vm.Enabled)
{
if (!ModelState.IsValid)
@@ -46,7 +48,11 @@ namespace BTCPayServer.Plugins.DataErasure
switch (command)
{
case "cleardate":
await _dataErasureService.Set(storeId, vm, true);
TempData["SuccessMessage"] = "Data erasure settings modified and date cleared";
return RedirectToAction(nameof(Update), new {storeId});
case "save":
await _dataErasureService.Set(storeId, vm);
TempData["SuccessMessage"] = "Data erasure settings modified";
@@ -57,4 +63,4 @@ namespace BTCPayServer.Plugins.DataErasure
}
}
}
}
}

View File

@@ -9,7 +9,7 @@ namespace BTCPayServer.Plugins.DataErasure
{
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{
new() { Identifier = nameof(BTCPayServer), Condition = ">=1.12.0" }
new() { Identifier = nameof(BTCPayServer), Condition = ">=2.0.0" }
};
public override void Execute(IServiceCollection applicationBuilder)
{

View File

@@ -3,6 +3,7 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Data;
using BTCPayServer.Services.Invoices;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@@ -14,13 +15,15 @@ namespace BTCPayServer.Plugins.DataErasure
private readonly IStoreRepository _storeRepository;
private readonly ILogger<DataErasureService> _logger;
private readonly InvoiceRepository _invoiceRepository;
private readonly ApplicationDbContextFactory _dbContextFactory;
public DataErasureService(IStoreRepository storeRepository, ILogger<DataErasureService> logger,
InvoiceRepository invoiceRepository)
InvoiceRepository invoiceRepository, ApplicationDbContextFactory dbContextFactory)
{
_storeRepository = storeRepository;
_logger = logger;
_invoiceRepository = invoiceRepository;
_dbContextFactory = dbContextFactory;
}
public async Task<DataErasureSettings> Get(string storeId)
@@ -29,13 +32,16 @@ namespace BTCPayServer.Plugins.DataErasure
nameof(DataErasureSettings));
}
public async Task Set(string storeId, DataErasureSettings settings)
public async Task Set(string storeId, DataErasureSettings settings, bool clearDate = false)
{
_cts?.Cancel();
await _runningLock.WaitAsync();
var existing = await Get(storeId);
settings.LastRunCutoff = existing?.LastRunCutoff;
settings.LastRunCutoff = clearDate? null: existing?.LastRunCutoff;
await SetCore(storeId, settings);
_runningLock.Release();
_cts = new CancellationTokenSource();
_ = Run();
}
private async Task SetCore(string storeId, DataErasureSettings settings)
@@ -46,11 +52,11 @@ namespace BTCPayServer.Plugins.DataErasure
public bool IsRunning { get; private set; }
private readonly SemaphoreSlim _runningLock = new(1, 1);
private async Task Run(CancellationToken cancellationToken)
private async Task Run()
{
while (!cancellationToken.IsCancellationRequested)
while (!_cts.IsCancellationRequested)
{
await _runningLock.WaitAsync(cancellationToken);
await _runningLock.WaitAsync(_cts.Token);
IsRunning = true;
@@ -58,53 +64,69 @@ namespace BTCPayServer.Plugins.DataErasure
await _storeRepository.GetSettingsAsync<DataErasureSettings>(nameof(DataErasureSettings));
foreach (var setting in settings.Where(setting => setting.Value.Enabled))
{
var skip = 0;
var count = 0;
var cutoffDate = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromDays(setting.Value.DaysToKeep));
while (true)
if (setting.Value.EntirelyEraseInvoice)
{
var invoices = await _invoiceRepository.GetInvoices(new InvoiceQuery()
{
StartDate = setting.Value.LastRunCutoff,
EndDate = cutoffDate,
StoreId = new[] {setting.Key},
Skip = skip,
Take = 100
});
foreach (var invoice in invoices)
{
//replace all buyer info with "erased"
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerAddress1))
invoice.Metadata.BuyerAddress1 = "erased";
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerAddress2))
invoice.Metadata.BuyerAddress2 = "erased";
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerCity))
invoice.Metadata.BuyerCity = "erased";
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerCountry))
invoice.Metadata.BuyerCountry = "erased";
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerEmail))
invoice.Metadata.BuyerEmail = "erased";
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerName))
invoice.Metadata.BuyerName = "erased";
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerPhone))
invoice.Metadata.BuyerPhone = "erased";
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerState))
invoice.Metadata.BuyerState = "erased";
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerZip))
invoice.Metadata.BuyerZip = "erased";
await _invoiceRepository.UpdateInvoiceMetadata(invoice.Id, invoice.StoreId,
invoice.Metadata.ToJObject());
count++;
}
if (invoices.Length < 100)
{
break;
}
skip += 100;
await using var db = _dbContextFactory.CreateContext();
db.Invoices.RemoveRange(db.Invoices.Where(i => i.StoreDataId == setting.Key && i.Created < cutoffDate && (setting.Value.LastRunCutoff == null || i.Created > setting.Value.LastRunCutoff)));
count = await db.SaveChangesAsync(_cts.Token);
}
if(count > 0)
else
{
var skip = 0;
while (true)
{
var invoices = await _invoiceRepository.GetInvoices(new InvoiceQuery()
{
StartDate = setting.Value.LastRunCutoff,
EndDate = cutoffDate,
StoreId = new[] {setting.Key},
Skip = skip,
Take = 100
}, _cts.Token);
foreach (var invoice in invoices)
{
//replace all buyer info with "erased"
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerAddress1))
invoice.Metadata.BuyerAddress1 = "erased";
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerAddress2))
invoice.Metadata.BuyerAddress2 = "erased";
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerCity))
invoice.Metadata.BuyerCity = "erased";
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerCountry))
invoice.Metadata.BuyerCountry = "erased";
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerEmail))
invoice.Metadata.BuyerEmail = "erased";
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerName))
invoice.Metadata.BuyerName = "erased";
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerPhone))
invoice.Metadata.BuyerPhone = "erased";
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerState))
invoice.Metadata.BuyerState = "erased";
if (!string.IsNullOrEmpty(invoice.Metadata.BuyerZip))
invoice.Metadata.BuyerZip = "erased";
await _invoiceRepository.UpdateInvoiceMetadata(invoice.Id, invoice.StoreId,
invoice.Metadata.ToJObject());
count++;
}
if (invoices.Length < 100)
{
break;
}
skip += 100;
}
}
if (count > 0)
_logger.LogInformation($"Erased {count} invoice data for store {setting.Key}");
setting.Value.LastRunCutoff = cutoffDate;
await SetCore(setting.Key, setting.Value);
@@ -114,18 +136,30 @@ namespace BTCPayServer.Plugins.DataErasure
_runningLock.Release();
await Task.Delay(TimeSpan.FromDays(1), cancellationToken);
await Task.Delay(TimeSpan.FromHours(1), _cts.Token);
}
try
{
_runningLock.Release();
}
catch (Exception e)
{
}
}
private CancellationTokenSource _cts;
public Task StartAsync(CancellationToken cancellationToken)
{
_ = Run(cancellationToken);
_cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_ = Run();
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_cts?.Cancel();
return Task.CompletedTask;
}
}

View File

@@ -7,5 +7,7 @@ namespace BTCPayServer.Plugins.DataErasure
public bool Enabled { get; set; }
public int DaysToKeep { get; set; }
public DateTimeOffset? LastRunCutoff { get; set; }
public bool EntirelyEraseInvoice { get; set; }
}
}

View File

@@ -36,9 +36,24 @@
<input asp-for="DaysToKeep" type="number" class="form-control"/>
</div>
<div class="form-group">
<div class="d-flex align-items-center">
<input asp-for="EntirelyEraseInvoice" type="checkbox" class="btcpay-toggle me-2"/>
<label asp-for="EntirelyEraseInvoice" class="form-label mb-0 me-1">Remove entire invoice from records (instead of just buyer data)</label>
</div>
<div class="alert alert-warning">
<p>
Deleting entire invoices may cause issues with integrations and accounting. Only use this option if you are sure you want to remove the invoice entirely.
</p>
</div>
</div>
@if (Model.LastRunCutoff != null)
{
<div>Cleared data up to @Model.LastRunCutoff.Value.ToString("g")</div>
<div class="form-group">
<button name="command" type="submit" value="cleardate" class="btn btn-danger">Start over</button>
</div>
}
</div>

View File

@@ -9,7 +9,7 @@
<PropertyGroup>
<Product>Dynamic Rate Limit</Product>
<Description>Allows you to override the default rate limiting.</Description>
<Version>1.0.1</Version>
<Version>1.0.2</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<!-- Plugin development properties -->

View File

@@ -10,7 +10,7 @@ public class DynamicRateLimitsPlugin : BaseBTCPayServerPlugin
{
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{
new() { Identifier = nameof(BTCPayServer), Condition = ">=1.12.0" }
new() { Identifier = nameof(BTCPayServer), Condition = ">=2.0.0" }
};
public override void Execute(IServiceCollection applicationBuilder)
{

View File

@@ -1,11 +1,6 @@
@using BTCPayServer.Plugins.DynamicRateLimits
@model DynamicRateLimitSettings
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData["NavPartialName"] = "../UIServer/_Nav";
}
<h2 class="mb-4">Rate limit configuration</h2>
<form method="post">

View File

@@ -1,7 +1,13 @@
@using BTCPayServer.Client
@using BTCPayServer.Plugins.DynamicRateLimits
@using Microsoft.AspNetCore.Mvc.TagHelpers
@{
var isActive = ViewContext.RouteData.Values.TryGetValue("Controller", out var controller) && controller is not null &&
var isActive = ViewContext.RouteData.Values.TryGetValue("Controller", out var controller) && controller is not null &&
nameof(DynamicRatesLimiterController).StartsWith(controller?.ToString(), StringComparison.InvariantCultureIgnoreCase);
}
<a class="nav-link @(isActive ? "active" : string.Empty)" asp-action="Update" asp-controller="DynamicRatesLimiter">Rate Limits</a>
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyServerSettings">
<a class="nav-link @(isActive ? "active" : string.Empty)" asp-action="Update" asp-controller="DynamicRatesLimiter">Rate Limits</a>
</li>

View File

@@ -9,7 +9,7 @@
<PropertyGroup>
<Product>Dynamic Reports</Product>
<Description>Allows you to create custom reports using SQL.</Description>
<Version>1.0.1</Version>
<Version>1.0.2</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<!-- Plugin development properties -->

View File

@@ -10,7 +10,7 @@ public class DynamicReportsPlugin : BaseBTCPayServerPlugin
{
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{
new() { Identifier = nameof(BTCPayServer), Condition = ">=1.13.0" }
new() { Identifier = nameof(BTCPayServer), Condition = ">=2.0.0" }
};
public override void Execute(IServiceCollection applicationBuilder)
{

View File

@@ -21,19 +21,15 @@ public class PostgresReportProvider : ReportProvider
public DynamicReportsSettings.DynamicReportSetting Setting { get; set; }
private readonly ApplicationDbContextFactory _dbContextFactory;
private readonly IOptions<DatabaseOptions> _options;
private readonly IHttpContextAccessor _httpContextAccessor;
public PostgresReportProvider( ApplicationDbContextFactory dbContextFactory,
IOptions<DatabaseOptions> options, IHttpContextAccessor httpContextAccessor)
public PostgresReportProvider( ApplicationDbContextFactory dbContextFactory, IHttpContextAccessor httpContextAccessor)
{
_dbContextFactory = dbContextFactory;
_options = options;
_httpContextAccessor = httpContextAccessor;
}
public override bool IsAvailable()
{
return _options.Value.DatabaseType == DatabaseType.Postgres &&
Setting.AllowForNonAdmins || _httpContextAccessor.HttpContext?.User.IsInRole(Roles.ServerAdmin) is true;
return Setting.AllowForNonAdmins || _httpContextAccessor.HttpContext?.User.IsInRole(Roles.ServerAdmin) is true;
}
public override async Task Query(QueryContext queryContext, CancellationToken cancellation)
{

View File

@@ -10,8 +10,6 @@
@inject DynamicReportService DynamicReportService
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData["NavPartialName"] = "../UIServer/_Nav";
var storeId = ScopeProvider.GetCurrentStoreId();
var reportName = Context.Request.Query["reportName"].ToString();
reportName = string.IsNullOrEmpty(reportName) ? null : reportName;

View File

@@ -1,8 +1,15 @@
@using BTCPayServer.Client
@using BTCPayServer.Plugins.DynamicReports
@using Microsoft.AspNetCore.Mvc.TagHelpers
@{
var isActive = ViewContext.RouteData.Values.TryGetValue("Controller", out var controller) && controller is not null &&
nameof(DynamicReportsController).StartsWith(controller?.ToString(), StringComparison.InvariantCultureIgnoreCase);
}
<a class="nav-link @(isActive ? "active" : string.Empty)" asp-action="Update" asp-controller="DynamicReports">Dynamic Reports</a>
<li class="nav-item nav-item-sub" permission="@Policies.CanModifyServerSettings">
<a class="nav-link @(isActive ? "active" : string.Empty)" asp-action="Update" asp-controller="DynamicReports">Dynamic Reports</a>
</li>

View File

@@ -9,7 +9,7 @@
<PropertyGroup>
<Product>File Seller</Product>
<Description>Allows you to sell files through the point of sale/crowdfund apps.</Description>
<Version>1.0.4</Version>
<Version>1.0.5</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<!-- Plugin development properties -->

View File

@@ -9,8 +9,9 @@ public class FileSellerPlugin : BaseBTCPayServerPlugin
{
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{
new() { Identifier = nameof(BTCPayServer), Condition = ">=1.12.0" }
new() {Identifier = nameof(BTCPayServer), Condition = ">=2.0.0"}
};
public override void Execute(IServiceCollection applicationBuilder)
{
applicationBuilder.AddHostedService<FileSellerService>();
@@ -20,7 +21,7 @@ public class FileSellerPlugin : BaseBTCPayServerPlugin
"checkout-end"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("FileSeller/FileSellerTemplateEditorItemDetail",
"app-template-editor-item-detail"));
base.Execute(applicationBuilder);
}

View File

@@ -6,16 +6,16 @@
@inject UserManager<ApplicationUser> UserManager
@inject UserService UserService
@{
var userId = UserManager.GetUserId(User);
var user = await UserManager.GetUserAsync(User);
var files = (await StoredFileRepository.GetFiles(new StoredFileRepository.FilesQuery()
{
UserIds = await UserService.IsAdminUser(userId) ? Array.Empty<string>() : new[] {userId},
UserIds = await UserService.IsAdminUser(user) ? Array.Empty<string>() : new[] {user.Id},
})).Select(file => new SelectListItem(file.FileName, file.Id)).Prepend(new SelectListItem("No file", ""));
}
<template v-if="editingItem">
<div class="form-group">
<label class="form-label">Downloadable file</label>
<select :value="editingItem['file'] || ''" asp-items="files" class="form-select w-auto" v-on:change=" if(event.target.value) editingItem['file']= event.target.value; else delete editingItem['file'];"></select>
<select :value="editingItem['file'] || ''" asp-items="files" class="form-select w-auto" v-on:change="if(event.target.value) Vue.set(editingItem, 'file', event.target.value); else Vue.delete(editingItem, 'file');"></select>
<span class="form-text">If a file is selected, when a user buys this item, a download link is generated in the payment receipt once the invoice is settled. <a target="_blank" asp-action="Files" asp-controller="UIServer">Upload files here</a></span>
</div>
</template>

View File

@@ -9,7 +9,7 @@
<PropertyGroup>
<Product>FixedFloat</Product>
<Description>Allows you to embed a FixedFloat conversion screen to allow customers to pay with altcoins.</Description>
<Version>1.1.7</Version>
<Version>1.1.8</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<!-- Plugin development properties -->

View File

@@ -9,29 +9,16 @@ namespace BTCPayServer.Plugins.FixedFloat
{
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{
new() { Identifier = nameof(BTCPayServer), Condition = ">=1.12.0" }
new() { Identifier = nameof(BTCPayServer), Condition = ">=2.0.0" }
};
public override void Execute(IServiceCollection applicationBuilder)
{
applicationBuilder.AddSingleton<FixedFloatService>();
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("FixedFloat/FixedFloatNav",
"store-integrations-nav"));
// Checkout v2
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("FixedFloat/CheckoutPaymentMethodExtension",
"checkout-payment-method"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("FixedFloat/CheckoutPaymentExtension",
"checkout-payment"));
// Checkout Classic
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("FixedFloat/CheckoutContentExtension",
"checkout-bitcoin-post-content"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("FixedFloat/CheckoutContentExtension",
"checkout-lightning-post-content"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("FixedFloat/CheckoutTabExtension",
"checkout-bitcoin-post-tabs"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("FixedFloat/CheckoutTabExtension",
"checkout-lightning-post-tabs"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("FixedFloat/CheckoutEnd",
"checkout-end"));
applicationBuilder.AddUIExtension("store-integrations-nav", "FixedFloat/FixedFloatNav");
applicationBuilder.AddUIExtension("checkout-payment-method", "FixedFloat/CheckoutPaymentMethodExtension");
applicationBuilder.AddUIExtension("checkout-payment", "FixedFloat/CheckoutPaymentExtension");
base.Execute(applicationBuilder);
}
}

View File

@@ -20,56 +20,70 @@ namespace BTCPayServer.Plugins.FixedFloat
{
{"AAVEETH", "Aave (ERC20)"},
{"ADA", "Cardano"},
{"ADABSC", "Cardano (BEP20)"},
{"APT", "Aptos"},
{"ATOM", "Cosmos"},
{"AVAX", "Avalanche (C-Chain)"},
{"BAT", "Basic Attention (ERC20)"},
{"BCH", "Bitcoin Cash"},
{"BNB", "BNB Beacon Chain (BEP2)"},
{"BSC", "BNB Smart Chain (BEP20)"},
{"WBNBBSC", "Wrapped BNB (BEP20)"},
{"BTC", "Bitcoin"},
{"BTCBSC", "Bitcoin (BEP20)"},
{"BTCLN", "Bitcoin (Lightning)"},
{"BTT", "BitTorrent"},
{"BUSD", "Binance USD (BEP2)"},
{"BUSDBSC", "Binance USD (BEP20)"},
{"BUSDETH", "Binance USD (ERC20)"},
{"CAKE", "PancakeSwap (BEP20)"},
{"DAIBSC", "DAI (BEP20)"},
{"DAIETH", "DAI (ERC20)"},
{"DAIMATIC", "DAI (Polygon)"},
{"DASH", "Dash"},
{"DOGE", "Dogecoin"},
{"DOT", "Polkadot"},
{"EOS", "EOS"},
{"ETC", "Ethereum Classic"},
{"ETH", "Ethereum"},
{"ETHARBITRUM", "Ethereum (Arbitrum)"},
{"ETHBASE", "Ethereum (Base)"},
{"ETHZKSYNC", "Ethereum (ZkSync)"},
{"WETHARBITRUM", "Wrapped ETH (Arbitrum)"},
{"WETHETH", "Wrapped ETH (ERC20)"},
{"FTM", "Fantom"},
{"KCS", "KuCoin Token"},
{"LINK", "Chainlink (ERC20)"},
{"LTC", "Litecoin"},
{"MANAETH", "Decentraland (ERC20)"},
{"MATIC", "Polygon"},
{"MATICETH", "Polygon (ERC20)"},
{"MKR", "Maker (ERC20)"},
{"PAXGETH", "PAX Gold (ERC20)"},
{"POL", "Polygon"},
{"POLETH", "Polygon (ERC20)"},
{"SHIB", "SHIBA INU (ERC20)"},
{"SHIBBSC", "SHIBA INU (BEP20)"},
{"SOL", "Solana"},
{"WSOL", "Wrapped SOL (Solana)"},
{"TON", "Toncoin"},
{"TRX", "Tron"},
{"TUSD", "TrueUSD (ERC20)"},
{"TWT", "Trust Wallet Token (BEP2)"},
{"TWTBSC", "Trust Wallet Token (BEP20)"},
{"USDCARBITRUM", "USD Coin (Arbitrum)"},
{"USDCBSC", "USD Coin (BEP20)"},
{"USDCETH", "USD Coin (ERC20)"},
{"USDCMATIC", "USD Coin (Polygon)"},
{"USDCSOL", "USD Coin (Solana)"},
{"USDCTRC", "USD Coin (TRC20)"},
{"USDP", "Pax Dollar (ERC20)"},
{"USDT", "Tether (ERC20)"},
{"USDTBSC", "Tether (BEP20)"},
{"USDTMATIC", "Tether (Polygon)"},
{"USDTSOL", "Tether (Solana)"},
{"USDTTRC", "Tether (TRC20)"},
{"VET", "VeChain"},
{"XLM", "Stellar Lumens"},
{"XMR", "Monero"},
{"XRP", "Ripple"},
{"XTZ", "Tezos"},
{"ZEC", "Zcash"},
{"ZRX", "0x (ERC20)"}
};
public static List<SelectListItem> AllowedSendingOptionsList => AllowedSendingOptions.Select(o => new SelectListItem(o.Value, o.Key)).ToList();
public static List<SelectListItem> AllowedSendingOptionsList =>
AllowedSendingOptions.Select(o => new SelectListItem(o.Value, o.Key)).ToList();
}
}
}

View File

@@ -2,15 +2,17 @@
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Data
@using BTCPayServer.Plugins.FixedFloat
@using BTCPayServer.Services.Invoices
@using BTCPayServer.Services.Stores
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model BTCPayServer.Plugins.FixedFloat.FixedFloatSettings
@inject BTCPayNetworkProvider BTCPayNetworkProvider
@inject PaymentMethodHandlerDictionary PaymentMethodHandlerDictionary
@{
ViewData.SetActivePage("FixedFloat", "FixedFloat", "FixedFloat");
var store = Context.GetStoreData();
var allowedPaymentMethods = store.GetEnabledPaymentIds(BTCPayNetworkProvider)
.Select(pmi => new SelectListItem(pmi.ToPrettyString(), pmi.ToString()))
var allowedPaymentMethods = store.GetEnabledPaymentIds()
.Select(pmi => new SelectListItem(pmi.ToString(), pmi.ToString()))
.Prepend(new SelectListItem("Any", ""));
}

View File

@@ -1,19 +0,0 @@
@using BTCPayServer.Plugins.FixedFloat
@model BTCPayServer.Models.InvoicingModels.PaymentModel
@inject FixedFloatService FixedFloatService
@{
var storeId = Model.StoreId;
var settings = await FixedFloatService.GetFixedFloatForStore(storeId);
if (settings?.Enabled is true)
{
<div id="FixedFloat" class="bp-view payment manual-flow" style="padding:0" :class="{ active: currentTab == 'undefined' || currentTab == 'FixedFloat' }">
<fixed-float inline-template
:to-currency="srvModel.paymentMethodId"
:to-currency-due="srvModel.btcDue * (1 + (@settings.AmountMarkupPercentage / 100)) "
:to-currency-address="srvModel.btcAddress">
<iframe :src="url" style="min-height:600px;width:100%;border:none" allowtransparency="true"></iframe>
</fixed-float>
</div>
}
}

View File

@@ -1,12 +0,0 @@
@using BTCPayServer.Plugins.FixedFloat
@using Newtonsoft.Json
@using Newtonsoft.Json.Linq
@inject FixedFloatService FixedFloatService
@{
var storeId = ((JObject)JObject.Parse(JsonConvert.SerializeObject(Model)))["StoreId"].Value<string>();
var settings = await FixedFloatService.GetFixedFloatForStore(storeId);
if (settings?.Enabled is true)
{
<script src="~/Resources/js/fixedFloatComponent.js"></script>
}
}

View File

@@ -1,10 +1,15 @@
@using BTCPayServer.Abstractions.TagHelpers
@using BTCPayServer.Payments
@using BTCPayServer.Plugins.FixedFloat
@inject FixedFloatService FixedFloatService
@model BTCPayServer.Models.InvoicingModels.PaymentModel
@model BTCPayServer.Models.InvoicingModels.CheckoutModel
@{
var storeId = Model.StoreId;
var settings = await FixedFloatService.GetFixedFloatForStore(storeId);
var preferredTargetPaymentMethodId = string.IsNullOrEmpty(settings?.PreferredTargetPaymentMethodId) ? null : Model.AvailableCryptos.Any(crypto => crypto.PaymentMethodId == settings.PreferredTargetPaymentMethodId) ? settings.PreferredTargetPaymentMethodId : null;
var preferredTargetPaymentMethodId =
string.IsNullOrEmpty(settings?.PreferredTargetPaymentMethodId) ? null :
Model.AvailablePaymentMethods.Any(crypto => crypto.PaymentMethodId.ToString() == PaymentMethodId.TryParse(settings.PreferredTargetPaymentMethodId)?.ToString()) ?
settings.PreferredTargetPaymentMethodId : null;
}
@if (settings?.Enabled is true)
{
@@ -41,7 +46,7 @@
const result = this.$parent.paymentMethodId === "FixedFloat";
if(this.preferredToCurrency && this.model.paymentMethodId !== this.preferredToCurrency){
if (this.model.onChainWithLnInvoiceFallback && this.model.paymentMethodId === "BTC"){
if (this.model.onChainWithLnInvoiceFallback && this.model.paymentMethodId === "BTC-CHAIN"){
return result;
}
this.$parent.paymentMethodId = this.preferredToCurrency;
@@ -53,7 +58,7 @@
return result;
},
lightning () {
if (!this.model.onChainWithLnInvoiceFallback || this.model.paymentMethodId !== "BTC"){
if (!this.model.onChainWithLnInvoiceFallback || this.model.paymentMethodId !== "BTC-CHAIN"){
return null;
}
const index = this.model.invoiceBitcoinUrl.indexOf("lightning=");
@@ -63,9 +68,9 @@
return this.model.invoiceBitcoinUrl.slice(index + "lightning=".length);
},
url () {
const address= this.lightning || this.model.btcAddress;
return "https://widget.fixedfloat.com/?" +
debugger;
const address= this.lightning || this.model.address;
return "https://widget.ff.io/?" +
`to=${this.settleMethodId}` +
"&lockReceive=true&ref=fkbyt39c" +
`&address=${address}` +
@@ -76,9 +81,9 @@
return this.model.paymentMethodId;
},
settleMethodId () {
return this.currency.endsWith('LightningLike') || this.currency.endsWith('LNURLPay') || this.lightning
return this.currency.endsWith('LN') || this.currency.endsWith('LNURL') || this.lightning
? 'BTCLN'
: this.currency.replace('_BTCLike', '').replace('_MoneroLike', '').replace('_ZcashLike', '').toUpperCase();
: this.currency.replace('-CHAIN', '').replace('_CHAIN', '').toUpperCase();
},
explicitQuery (){
const isExplicit = !!this.explicitId;
@@ -91,7 +96,7 @@
: `&lockType=true&hideType=true&lockAmount=true&toAmount=${this.amountDue}`;
},
amountDue () {
return this.model.btcDue * (1 + (markupPercentage / 100));
return this.model.due * (1 + (markupPercentage / 100));
}
}
});

View File

@@ -1,5 +1,5 @@
@using BTCPayServer.Plugins.FixedFloat
@model BTCPayServer.Models.InvoicingModels.PaymentModel
@model BTCPayServer.Models.InvoicingModels.CheckoutModel
@inject FixedFloatService FixedFloatService
@{
const string id = "FixedFloat";

View File

@@ -1,13 +0,0 @@
@using BTCPayServer.Plugins.FixedFloat
@model BTCPayServer.Models.InvoicingModels.PaymentModel
@inject FixedFloatService FixedFloatService
@{
var storeId = Model.StoreId;
var settings = await FixedFloatService.GetFixedFloatForStore(storeId);
if (settings?.Enabled is true)
{
<div class="payment-tabs__tab py-0" id="FixedFloat-tab" v-on:click="switchTab('FixedFloat')" v-bind:class="{ 'active': currentTab == 'FixedFloat'}">
<span>{{$t("Altcoins (FixedFloat)")}}</span>
</div>
}
}

View File

@@ -1,43 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>net8.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>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</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

@@ -1,232 +0,0 @@
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();
}
}

View File

@@ -12,7 +12,7 @@
<PropertyGroup>
<Product>Liquid+</Product>
<Description>Enhanced support for the liquid network.</Description>
<Version>1.1.4</Version>
<Version>1.1.5</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>

View File

@@ -10,7 +10,10 @@ using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client;
using BTCPayServer.Common;
using BTCPayServer.Data;
using BTCPayServer.Plugins.Altcoins;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
@@ -25,20 +28,24 @@ namespace BTCPayServer.Plugins.LiquidPlus.Controllers
[AutoValidateAntiforgeryToken]
public class StoreLiquidController : Controller
{
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private readonly StoreRepository _storeRepository;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly BTCPayServerClient _client;
private readonly IExplorerClientProvider _explorerClientProvider;
public StoreLiquidController(BTCPayNetworkProvider btcPayNetworkProvider,
BTCPayServerClient client, IExplorerClientProvider explorerClientProvider)
public StoreLiquidController(PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
StoreRepository storeRepository, BTCPayNetworkProvider btcPayNetworkProvider,
IExplorerClientProvider explorerClientProvider)
{
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_storeRepository = storeRepository;
_btcPayNetworkProvider = btcPayNetworkProvider;
_client = client;
_explorerClientProvider = explorerClientProvider;
}
[HttpGet("stores/{storeId}/liquid")]
public async Task<IActionResult> GenerateLiquidScript(string storeId, Dictionary<string, BitcoinExtKey> bitcoinExtKeys = null)
public async Task<IActionResult> GenerateLiquidScript(string storeId,
Dictionary<string, BitcoinExtKey> bitcoinExtKeys = null)
{
Dictionary<string, string> generated = new Dictionary<string, string>();
var allNetworks = _btcPayNetworkProvider.GetAll().OfType<ElementsBTCPayNetwork>()
@@ -48,13 +55,20 @@ namespace BTCPayServer.Plugins.LiquidPlus.Controllers
.ToArray()
.Distinct();
Dictionary<string, BitcoinExtKey> privKeys = bitcoinExtKeys ?? new Dictionary<string, BitcoinExtKey>();
var paymentMethods = (await _client.GetStoreOnChainPaymentMethods(storeId))
.Where(settings => allNetworkCodes.Contains(settings.CryptoCode))
.GroupBy(data => _btcPayNetworkProvider.GetNetwork<ElementsBTCPayNetwork>(data.CryptoCode).NetworkCryptoCode);
if (paymentMethods.Any() is false)
var store = await _storeRepository.FindStore(storeId);
var pms = store
.GetPaymentMethodConfigs<DerivationSchemeSettings>(_paymentMethodHandlerDictionary)
.Select(pair => (PaymentMethodId: pair.Key, DerivationSchemeSettings: pair.Value,
CryptoCode: pair.Key.ToString().Split("-")[0])).ToArray();
var paymentMethodsGroupedByNetworkCode =
pms
.Where(settings => allNetworkCodes.Contains(settings.CryptoCode))
.GroupBy(data =>
_btcPayNetworkProvider.GetNetwork<ElementsBTCPayNetwork>(data.CryptoCode).NetworkCryptoCode);
if (paymentMethodsGroupedByNetworkCode.Any() is false)
{
TempData.SetStatusMessageModel(new StatusMessageModel()
{
@@ -63,12 +77,12 @@ namespace BTCPayServer.Plugins.LiquidPlus.Controllers
});
return View(new GenerateLiquidImportScripts());
}
foreach (var der in paymentMethods)
foreach (var der in paymentMethodsGroupedByNetworkCode)
{
var network = _btcPayNetworkProvider.GetNetwork<ElementsBTCPayNetwork>(der.Key);
var nbxnet = network.NBXplorerNetwork;
var sb = new StringBuilder();
var explorerClient = _explorerClientProvider.GetExplorerClient(der.Key);
@@ -79,12 +93,12 @@ namespace BTCPayServer.Plugins.LiquidPlus.Controllers
generated.Add(der.Key, sb.ToString());
continue;
}
var derivationSchemesForNetwork = der.GroupBy(data => data.DerivationScheme);
var derivationSchemesForNetwork = der.GroupBy(data => data.DerivationSchemeSettings);
foreach (var paymentMethodDerivationScheme in derivationSchemesForNetwork)
{
var derivatonScheme =
nbxnet.DerivationStrategyFactory.Parse(paymentMethodDerivationScheme.Key);
var derivatonScheme = paymentMethodDerivationScheme.Key.AccountDerivation;
var sameWalletCryptoCodes = paymentMethodDerivationScheme.Select(data => data.CryptoCode).ToArray();
var matchedExistingKey = privKeys.Where(pair => sameWalletCryptoCodes.Contains(pair.Key));
BitcoinExtKey key = null;
@@ -94,22 +108,20 @@ namespace BTCPayServer.Plugins.LiquidPlus.Controllers
}
else
{
key = await explorerClient.GetMetadataAsync<BitcoinExtKey>(derivatonScheme,
WellknownMetadataKeys.AccountHDKey);
}
if (key != null)
{
foreach (var paymentMethodData in paymentMethodDerivationScheme)
{
privKeys.TryAdd(paymentMethodData.CryptoCode, key);
}
{
privKeys.TryAdd(paymentMethodData.CryptoCode, key);
}
}
var utxos = await explorerClient.GetUTXOsAsync(derivatonScheme, CancellationToken.None);
foreach (var utxo in utxos.GetUnspentUTXOs())
{
var addr = nbxnet.CreateAddress(derivatonScheme, utxo.KeyPath, utxo.ScriptPubKey);
@@ -139,21 +151,21 @@ namespace BTCPayServer.Plugins.LiquidPlus.Controllers
{
sb.AppendLine("elements-cli stop");
sb.AppendLine("elementsd -rescan");
}
generated.Add(der.Key, sb.ToString());
}
return View(new GenerateLiquidImportScripts()
{
Wallets = paymentMethods.SelectMany(settings =>
settings.Select(data =>
new GenerateLiquidImportScripts.GenerateLiquidImportScriptWalletKeyVm()
{
CryptoCode = data.CryptoCode,
KeyPresent = privKeys.ContainsKey(data.CryptoCode),
ManualKey = null
}).ToArray()).ToArray(),
Wallets = paymentMethodsGroupedByNetworkCode.SelectMany(settings =>
settings.Select(data =>
new GenerateLiquidImportScripts.GenerateLiquidImportScriptWalletKeyVm()
{
CryptoCode = data.CryptoCode,
KeyPresent = privKeys.ContainsKey(data.CryptoCode),
ManualKey = null
}).ToArray()).ToArray(),
Scripts = generated
});
}
@@ -199,10 +211,9 @@ namespace BTCPayServer.Plugins.LiquidPlus.Controllers
continue;
}
var der = HttpContext.GetStoreData()
.GetDerivationSchemeSettings(_paymentMethodHandlerDictionary, wallet.CryptoCode).AccountDerivation;
var der = n.NBXplorerNetwork.DerivationStrategyFactory.Parse(
(await _client.GetStoreOnChainPaymentMethod(storeId, wallet.CryptoCode)).DerivationScheme);
if (der.GetExtPubKeys().Count() > 1)
{
vm.AddModelError(scripts => scripts.Wallets[index].ManualKey, "cannot handle multsig", this);
@@ -250,7 +261,8 @@ namespace BTCPayServer.Plugins.LiquidPlus.Controllers
return await GenerateLiquidScript(storeId, privKeys);
}
public class GenerateLiquidImportScripts
public class GenerateLiquidImportScripts
{
public class GenerateLiquidImportScriptWalletKeyVm
{
@@ -266,6 +278,7 @@ public class GenerateLiquidImportScripts
}
}
}
namespace XX
{
public static class ModelStateExtensions
@@ -275,9 +288,11 @@ namespace XX
string message,
ControllerBase controller)
{
var provider = (ModelExpressionProvider)controller.HttpContext.RequestServices.GetService(typeof(ModelExpressionProvider));
var provider =
(ModelExpressionProvider) controller.HttpContext.RequestServices.GetService(
typeof(ModelExpressionProvider));
var key = provider.GetExpressionText(ex);
controller.ModelState.AddModelError(key, message);
}
}
}
}

View File

@@ -22,7 +22,7 @@ namespace BTCPayServer.Plugins.LiquidPlus
{
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{
new() {Identifier = nameof(BTCPayServer), Condition = ">=1.12.0"}
new() {Identifier = nameof(BTCPayServer), Condition = ">=2.0.0"}
};
public override void Execute(IServiceCollection applicationBuilder)
@@ -31,11 +31,11 @@ namespace BTCPayServer.Plugins.LiquidPlus
if (services.BootstrapServices.GetRequiredService<NBXplorerNetworkProvider>()
.GetFromCryptoCode("LBTC") is null || !services.BootstrapServices.GetRequiredService<SelectedChains>().Contains("LBTC"))
return;
services.AddSingleton<IUIExtension>(new UIExtension("LiquidNav", "store-integrations-nav"));
services.AddSingleton<IUIExtension>(new UIExtension("OnChainWalletSetupLiquidExtension",
"onchain-wallet-setup-post-body"));
services.AddSingleton<IUIExtension>(new UIExtension("CustomLiquidAssetsNavExtension", "server-nav"));
services.AddSingleton<IUIExtension>(new UIExtension("StoreNavLiquidExtension", "store-nav"));
services.AddUIExtension("store-integrations-nav", "LiquidNav");
services.AddUIExtension("onchain-wallet-setup-post-body", "OnChainWalletSetupLiquidExtension");
services.AddUIExtension("server-nav", "CustomLiquidAssetsNavExtension");
services.AddUIExtension("store-nav", "StoreNavLiquidExtension");
services.AddSingleton<CustomLiquidAssetsRepository>();
@@ -53,12 +53,11 @@ namespace BTCPayServer.Plugins.LiquidPlus
CryptoCode: "LBTC"
})
.ImplementationInstance;
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId("LBTC");
var tlProvider = (TransactionLinkProviders.Entry) services.Single(descriptor =>
descriptor.ServiceType == typeof(TransactionLinkProviders.Entry) &&
descriptor.ImplementationInstance is TransactionLinkProviders.Entry
{
PaymentMethodId: {CryptoCode: "LBTC"}
})
descriptor.ImplementationInstance is TransactionLinkProviders.Entry entry && entry.PaymentMethodId == pmi)
.ImplementationInstance;
settings.Items.ForEach(configuration =>
@@ -80,7 +79,6 @@ namespace BTCPayServer.Plugins.LiquidPlus
NetworkCryptoCode = template.NetworkCryptoCode,
DefaultSettings = template.DefaultSettings,
ElectrumMapping = template.ElectrumMapping,
BlockExplorerLink = template.BlockExplorerLink,
ReadonlyWallet = template.ReadonlyWallet,
SupportLightning = false,
SupportPayJoin = false,
@@ -92,7 +90,7 @@ namespace BTCPayServer.Plugins.LiquidPlus
VaultSupported = template.VaultSupported,
MaxTrackedConfirmation = template.MaxTrackedConfirmation,
SupportRBF = template.SupportRBF
}).AddTransactionLinkProvider(new PaymentMethodId(code, PaymentTypes.BTCLike), tlProvider.Provider);
}).AddTransactionLinkProvider(code, tlProvider.Provider);
});
}
}

View File

@@ -34,7 +34,7 @@
<ItemGroup>
<EmbeddedResource Include="Resources\**" />
<ProjectReference Include="..\..\submodules\btcpayserver\BTCPayServer\BTCPayServer.csproj" />
<PackageReference Include="AsyncKeyedLock" Version="6.4.2" />
<PackageReference Include="AsyncKeyedLock" Version="7.0.1" />
<PackageReference Include="FlexLabs.EntityFrameworkCore.Upsert" Version="8.0.0" />
<PackageReference Include="Laraue.EfCoreTriggers.PostgreSql" Version="8.0.3" />
<PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.10.0" />

View File

@@ -1,23 +1,29 @@
using BTCPayServer.Abstractions.Contracts;
using System;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Models;
using Laraue.EfCoreTriggers.PostgreSql.Extensions;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure;
namespace BTCPayServer.Plugins.MicroNode;
public class MicroNodeContextFactory : BaseDbContextFactory<MicroNodeContext>
{
public MicroNodeContextFactory(IOptions<DatabaseOptions> options) : base(options, "BTCPayServer.Plugins.MicroNode")
private readonly ILoggerFactory _loggerFactory;
public MicroNodeContextFactory(IOptions<DatabaseOptions> options, ILoggerFactory loggerFactory) : base(options, "BTCPayServer.Plugins.MicroNode")
{
_loggerFactory = loggerFactory;
}
public override MicroNodeContext CreateContext()
public override MicroNodeContext CreateContext(Action<NpgsqlDbContextOptionsBuilder> npgsqlOptionsAction = null)
{
var builder = new DbContextOptionsBuilder<MicroNodeContext>();
ConfigureBuilder(builder);
builder.UsePostgreSqlTriggers();
builder.UseLoggerFactory(_loggerFactory);
builder.AddInterceptors(MigrationInterceptor.Instance);
ConfigureBuilder(builder, npgsqlOptionsAction);
return new MicroNodeContext(builder.Options);
}
}

View File

@@ -8,6 +8,7 @@ using BTCPayServer.Client;
using BTCPayServer.Data;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -17,14 +18,17 @@ namespace BTCPayServer.Plugins.MicroNode;
[Route("plugins/micronode")]
public class MicroNodeController : Controller
{
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private readonly MicroNodeService _microNodeService;
private readonly StoreRepository _storeRepository;
private readonly BTCPayNetworkProvider _networkProvider;
private readonly IAuthorizationService _authorizationService;
public MicroNodeController(MicroNodeService microNodeService, StoreRepository storeRepository,
public MicroNodeController(
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,MicroNodeService microNodeService, StoreRepository storeRepository,
BTCPayNetworkProvider networkProvider, IAuthorizationService authorizationService)
{
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_microNodeService = microNodeService;
_storeRepository = storeRepository;
_networkProvider = networkProvider;
@@ -66,11 +70,8 @@ public class MicroNodeController : Controller
ModelState.AddModelError("masterStoreId", "Master cannot be the same as this store");
return View(settings);
}
var existing = store.GetSupportedPaymentMethods(_networkProvider).OfType<LightningSupportedPaymentMethod>()
.FirstOrDefault(method =>
method.PaymentId.PaymentType == LightningPaymentType.Instance &&
method.PaymentId.CryptoCode == network.CryptoCode);
var pmi = PaymentTypes.LN.GetPaymentMethodId(network.CryptoCode);
var existing = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, _paymentMethodHandlerDictionary);
var isSet = settings?.Key is not null;
settings ??= new MicroNodeStoreSettings();
settings.Key ??= Guid.NewGuid().ToString();
@@ -118,12 +119,10 @@ public class MicroNodeController : Controller
}
existing ??= new LightningSupportedPaymentMethod()
{
CryptoCode = "BTC"
};
existing ??= new();
existing.SetLightningUrl(mlc);
store.SetSupportedPaymentMethod(existing);
store.SetPaymentMethodConfig(_paymentMethodHandlerDictionary[pmi], existing);
await _microNodeService.Set(storeId, settings, masterStoreId);
@@ -145,7 +144,7 @@ public class MicroNodeController : Controller
if (isStoreSetToThisMicro)
{
store.SetSupportedPaymentMethod(existing.PaymentId, null);
store.SetPaymentMethodConfig(_paymentMethodHandlerDictionary[pmi], null);
await _storeRepository.UpdateStore(store);
}

View File

@@ -11,7 +11,7 @@ public class MicroNodePlugin:BaseBTCPayServerPlugin
{
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{
new () { Identifier = nameof(BTCPayServer), Condition = ">=1.12.0" }
new () { Identifier = nameof(BTCPayServer), Condition = ">=2.0.0" }
};

View File

@@ -15,7 +15,9 @@ using BTCPayServer.HostedServices;
using BTCPayServer.Lightning;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Payouts;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
@@ -34,6 +36,7 @@ public class MicroNodeService : EventHostedServiceBase
private readonly ILogger<MicroNodeService> _logger;
private readonly PullPaymentHostedService _pullPaymentHostedService;
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private static readonly ConcurrentDictionary<string, long> ExpectedCounter = new();
private readonly TaskCompletionSource _init = new();
private Dictionary<string, MicroNodeSettings> _ownerSettings;
@@ -53,6 +56,7 @@ public class MicroNodeService : EventHostedServiceBase
EventAggregator eventAggregator,
PullPaymentHostedService pullPaymentHostedService,
IOptions<LightningNetworkOptions> lightningNetworkOptions,
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
IServiceProvider serviceProvider) : base(eventAggregator, logger)
{
_network = btcPayNetworkProvider.BTC;
@@ -63,6 +67,7 @@ public class MicroNodeService : EventHostedServiceBase
_logger = logger;
_pullPaymentHostedService = pullPaymentHostedService;
_lightningNetworkOptions = lightningNetworkOptions;
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_serviceProvider = serviceProvider;
}
@@ -124,17 +129,20 @@ public class MicroNodeService : EventHostedServiceBase
{
var b = payout.GetBlob(_btcPayNetworkJsonSerializerSettings);
List<MicroTransaction> res = new();
res.Add(new MicroTransaction()
{
Id = payout.Id,
AccountId = key,
Amount = -LightMoney.Coins(b.CryptoAmount.Value).MilliSatoshi,
Accounted = payout.State != PayoutState.Cancelled,
Active = payout.State is PayoutState.AwaitingApproval or PayoutState.AwaitingPayment
or PayoutState.InProgress,
Type = "Payout"
});
List<MicroTransaction> res =
[
new()
{
Id = payout.Id,
AccountId = key,
Amount = -LightMoney.Coins(payout.Amount!.Value).MilliSatoshi,
Accounted = payout.State != PayoutState.Cancelled,
Active = payout.State is PayoutState.AwaitingApproval or PayoutState.AwaitingPayment
or PayoutState.InProgress,
Type = "Payout"
}
];
if (b.Metadata?.TryGetValue("Fee", out var microNode) is true && microNode.Value<decimal>() is { } payoutFee)
{
@@ -230,7 +238,7 @@ public class MicroNodeService : EventHostedServiceBase
await using var ctx = _microNodeContextFactory.CreateContext();
for (int i = 0; i < 5; i++)
{
var account = await ctx.MicroAccounts.FindAsync(key);
var account = await ctx.MicroAccounts.FindAsync(key, cancellation);
if (account is null)
{
return null;
@@ -336,9 +344,9 @@ public class MicroNodeService : EventHostedServiceBase
return null;
}
var lightningConnectionString = store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
.OfType<LightningSupportedPaymentMethod>()
.FirstOrDefault(method => method.CryptoCode == _network.CryptoCode)?.CreateLightningClient(_network,
var pmi = PaymentTypes.LN.GetPaymentMethodId(_network.CryptoCode);
var lightningConnectionString = store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(pmi, _paymentMethodHandlerDictionary)
?.CreateLightningClient(_network,
_lightningNetworkOptions.Value, _serviceProvider.GetService<LightningClientFactoryService>());
return lightningConnectionString;
}
@@ -616,7 +624,7 @@ public class MicroNodeService : EventHostedServiceBase
StoreId = masterClients.Key,
Destination = new LNURLPayClaimDestinaton(destination),
PreApprove = true,
PaymentMethodId = new PaymentMethodId("BTC", LightningPaymentType.Instance),
PayoutMethodId = PayoutTypes.LN.GetPayoutMethodId("BTC"),
Metadata = JObject.FromObject(new
{
Source = $"MicroNode on store {storeId.Key}"
@@ -667,7 +675,7 @@ public class MicroNodeService : EventHostedServiceBase
await base.StopAsync(cancellationToken);
}
private ConcurrentDictionary<string, string> _keyToMasterStoreId = new();
private readonly ConcurrentDictionary<string, string> _keyToMasterStoreId = new();
public async Task Set(string storeId, MicroNodeStoreSettings? settings, string? masterStoreId = null)
{

View File

@@ -11,7 +11,7 @@
<PropertyGroup>
<Product>Nostr</Product>
<Description>NIP5 addresses, Zap support, Nostr Wallet Connect Lightning support</Description>
<Version>1.1.13</Version>
<Version>1.1.14</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<!-- Plugin development properties -->

View File

@@ -13,7 +13,7 @@ namespace BTCPayServer.Plugins.NIP05
{
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{
new() {Identifier = nameof(BTCPayServer), Condition = ">=1.13.0"}
new() {Identifier = nameof(BTCPayServer), Condition = ">=2.0.0"}
};
public override void Execute(IServiceCollection applicationBuilder)

View File

@@ -32,7 +32,7 @@
<div class="col-xl-8 col-xxl-constrain">
<div class="form-group">
<div class="form-group pt-2">
<label asp-for="Name (does not need to be same as for a lightning address)" class="form-label"></label>
<label asp-for="Name" class="form-label">Name (does not need to be same as for a lightning address)</label>
<div class="input-group">
<input asp-for="Name" class="form-control"/>
<span class="input-group-text">@@@Context.Request.Host.ToUriComponent()@Context.Request.PathBase</span>

View File

@@ -5,11 +5,9 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Events;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Payments;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
@@ -23,7 +21,8 @@ namespace BTCPayServer.Plugins.NIP05;
public class Zapper : IHostedService
{
record PendingZapEvent(string[] relays, NostrEvent nostrEvent);
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private readonly EventAggregator _eventAggregator;
private readonly Nip5Controller _nip5Controller;
private readonly IMemoryCache _memoryCache;
@@ -51,7 +50,9 @@ public class Zapper : IHostedService
}
public Zapper(EventAggregator eventAggregator,
public Zapper(
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
EventAggregator eventAggregator,
Nip5Controller nip5Controller,
IMemoryCache memoryCache,
ILogger<Zapper> logger,
@@ -59,6 +60,7 @@ public class Zapper : IHostedService
InvoiceRepository invoiceRepository,
NostrClientPool nostrClientPool)
{
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_eventAggregator = eventAggregator;
_nip5Controller = nip5Controller;
_memoryCache = memoryCache;
@@ -150,7 +152,8 @@ public class Zapper : IHostedService
{
if (arg.EventCode != InvoiceEventCode.Completed && arg.EventCode != InvoiceEventCode.MarkedCompleted)
return;
var pm = arg.Invoice.GetPaymentMethod(new PaymentMethodId("BTC", LNURLPayPaymentType.Instance));
var pmi = PaymentTypes.LNURL.GetPaymentMethodId("BTC");
var pm = arg.Invoice.GetPaymentPrompt(pmi);
if (pm is null)
{
return;
@@ -159,8 +162,7 @@ public class Zapper : IHostedService
{
return;
}
var pmd = (LNURLPayPaymentMethodDetails) pm.GetPaymentMethodDetails();
var settings = await GetSettings();
var zapRequestEvent = JsonSerializer.Deserialize<NostrEvent>(zapRequest);
@@ -168,13 +170,13 @@ public class Zapper : IHostedService
var tags = zapRequestEvent.Tags.Where(a => a.TagIdentifier.Length == 1).ToList();
tags.AddRange(new[]
{
new NostrEventTag
{
TagIdentifier = "bolt11",
Data = new() {pmd.BOLT11}
Data = new() {pm.Destination}
},
new NostrEventTag()

View File

@@ -11,7 +11,7 @@
<PropertyGroup>
<Product>Prism</Product>
<Description>Automated value splits for Bitcoin.</Description>
<Version>1.2.8</Version>
<Version>1.2.9</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<!-- Plugin development properties -->

View File

@@ -4,6 +4,7 @@
@using BTCPayServer.HostedServices
@using BTCPayServer.Payments
@using BTCPayServer.PayoutProcessors
@using BTCPayServer.Payouts
@using Microsoft.AspNetCore.Http
@using Microsoft.AspNetCore.Routing
@using Microsoft.Extensions.Logging
@@ -28,7 +29,7 @@ else
{
<datalist id="users">
<option value="*">Catch-all lightning payments made against invoices in your store (excluding when other prisms are configured that capture those payments.)</option>
<option value="*@BitcoinPaymentType.Instance.ToStringNormalized()">Catch-all on-chain payments made against invoices in your store</option>
<option value="*@PaymentTypes.CHAIN.ToString()">Catch-all on-chain payments made against invoices in your store</option>
<option value="*All">Catch-all any payments made against invoices in your store</option>
@foreach (var user in Users)
@@ -201,8 +202,8 @@ else
public bool Loading { get; set; } = true;
public List<LightningAddressData> Users { get; set; } = new();
public PaymentMethodId pmi { get; set; } = new("BTC", LightningPaymentType.Instance);
public PaymentMethodId pmichain { get; set; } = new("BTC", PaymentTypes.BTCLike);
public PayoutMethodId pmi { get; set; } = PayoutTypes.LN.GetPayoutMethodId("BTC");
public PayoutMethodId pmichain { get; set; } = PayoutTypes.CHAIN.GetPayoutMethodId("BTC");
public bool NoPayoutProcessors { get; set; }
private string PrismEditButtonsFilter { get; set; }
@@ -224,7 +225,7 @@ else
var fetchProcessors = PayoutProcessorService.GetProcessors(new PayoutProcessorService.PayoutProcessorQuery()
{
Stores = new[] {StoreId},
PaymentMethods = new[] {pmi.ToString(), pmichain.ToString()}
PayoutMethods = new[] {pmi, pmichain}
});
var tasks = new Task[]
@@ -243,8 +244,8 @@ else
EditContext.OnFieldChanged += FieldChanged;
SatBreaker.PrismUpdated += SatBreakerOnPrismUpdated;
//set NoPayoutProcessors to true if there are no configured payout processores for pmi and pmichain
NoPayoutProcessors = PayoutProcessorFactories.Any(factory => factory.GetSupportedPaymentMethods().Contains(pmi)) && (await fetchProcessors).All(data =>
!new[] {pmi, pmichain}.Contains(data.GetPaymentMethodId()));
NoPayoutProcessors = PayoutProcessorFactories.Any(factory => factory.GetSupportedPayoutMethods().Contains(pmi)) && (await fetchProcessors).All(data =>
!new[] {pmi, pmichain}.Contains(data.GetPayoutMethodId()));
Loading = false;
await InvokeAsync(StateHasChanged);

View File

@@ -2,6 +2,7 @@
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Payments;
using BTCPayServer.Payouts;
namespace BTCPayServer.Plugins.Prism;
@@ -19,7 +20,7 @@ public class LNURLPrismDestinationValidator : IPluginHookFilter
return Task.FromResult<object>(new PrismDestinationValidationResult()
{
Success = true,
PaymentMethod = new PaymentMethodId("BTC", PaymentTypes.LNURLPay)
PayoutMethodId = PayoutTypes.LN.GetPayoutMethodId("BTC")
});
}
catch (Exception e)
@@ -30,7 +31,7 @@ public class LNURLPrismDestinationValidator : IPluginHookFilter
return Task.FromResult<object>(new PrismDestinationValidationResult()
{
Success = true,
PaymentMethod = new PaymentMethodId("BTC", PaymentTypes.LNURLPay)
PayoutMethodId =PayoutTypes.LN.GetPayoutMethodId("BTC")
});
}
catch (Exception)
@@ -45,5 +46,5 @@ public class LNURLPrismDestinationValidator : IPluginHookFilter
public class PrismDestinationValidationResult
{
public bool Success { get; set; }
public PaymentMethodId PaymentMethod { get; set; }
public PayoutMethodId PayoutMethodId { get; set; }
}

View File

@@ -1,10 +1,9 @@
using System;
using System.Collections;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.Payments;
using BTCPayServer.Payouts;
using NBitcoin;
using NBXplorer.DerivationStrategy;
@@ -35,7 +34,7 @@ public class OnChainPrismClaimCreate : IPluginHookFilter
{
claimRequest.Destination =
new AddressClaimDestination(BitcoinAddress.Create(destStr, network.NBitcoinNetwork));
claimRequest.PaymentMethodId = new PaymentMethodId("BTC", BitcoinPaymentType.Instance);
claimRequest.PayoutMethodId = PayoutTypes.CHAIN.GetPayoutMethodId("BTC");
return args;
}
catch (Exception)
@@ -48,7 +47,7 @@ public class OnChainPrismClaimCreate : IPluginHookFilter
claimRequest.Destination =
new AddressClaimDestination(add.Address);
claimRequest.PaymentMethodId = new PaymentMethodId("BTC", BitcoinPaymentType.Instance);
claimRequest.PayoutMethodId = PayoutTypes.CHAIN.GetPayoutMethodId("BTC");
}
catch (Exception exception)
{

View File

@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Data;
using BTCPayServer.Payments;
using BTCPayServer.Payouts;
using NBitcoin;
using NBXplorer;
@@ -34,7 +35,7 @@ public class OnChainPrismDestinationValidator : IPluginHookFilter
return Task.FromResult<object>(new PrismDestinationValidationResult()
{
Success = true,
PaymentMethod = new PaymentMethodId("BTC", PaymentTypes.BTCLike)
PayoutMethodId = PayoutTypes.CHAIN.GetPayoutMethodId("BTC")
});
}
catch (Exception e)
@@ -46,7 +47,7 @@ public class OnChainPrismDestinationValidator : IPluginHookFilter
return Task.FromResult<object>(new PrismDestinationValidationResult()
{
Success = true,
PaymentMethod = new PaymentMethodId("BTC", PaymentTypes.BTCLike)
PayoutMethodId =PayoutTypes.CHAIN.GetPayoutMethodId("BTC")
});
}
catch (Exception)

View File

@@ -5,6 +5,7 @@ using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Payments;
using BTCPayServer.Payouts;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Linq;
@@ -30,7 +31,7 @@ public class OpenSatsDestinationValidator : IPluginHookFilter
var parts = args1.ToLowerInvariant().Split(":", StringSplitOptions.RemoveEmptyEntries);
var project = "general_fund";
var paymentMethod = new PaymentMethodId("BTC", PaymentTypes.LightningLike);
var paymentMethod = PayoutTypes.LN.GetPayoutMethodId("BTC");
if (parts.Length > 1)
{
project = parts[1];
@@ -38,12 +39,11 @@ public class OpenSatsDestinationValidator : IPluginHookFilter
if (parts.Length > 2)
{
paymentMethod = PaymentMethodId.Parse(parts[2]);
paymentMethod = PayoutMethodId.Parse(parts[2]);
}
var handler = _serviceProvider.GetServices<IPayoutHandler>().FindPayoutHandler(paymentMethod);
if (handler is null)
if (_serviceProvider.GetService<PayoutMethodHandlerDictionary>().TryGetValue(paymentMethod, out var handler))
{
result.Success = false;
}
@@ -63,7 +63,7 @@ public class OpenSatsDestinationValidator : IPluginHookFilter
var invoiceBtcpayModel = JObject.Parse(await httpClient.GetStringAsync(invoiceUrl).ConfigureAwait(false));
var destination = invoiceBtcpayModel.Value<string>("btcAddress");
var claimDestination = await handler.ParseClaimDestination(paymentMethod,destination, CancellationToken.None);
var claimDestination = await handler.ParseClaimDestination(destination, CancellationToken.None);
if (claimDestination.destination is null)
{
@@ -72,7 +72,7 @@ public class OpenSatsDestinationValidator : IPluginHookFilter
result.Success = true;
result.PaymentMethod = paymentMethod;
result.PayoutMethodId = paymentMethod;
return result;
}
catch (Exception e)

View File

@@ -1,18 +1,12 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Custodians;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.Payments;
using Microsoft.EntityFrameworkCore;
using BTCPayServer.Payouts;
using Microsoft.Extensions.DependencyInjection;
using NBitcoin;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Plugins.Prism;
@@ -38,10 +32,10 @@ public class OpenSatsPrismClaimCreate : IPluginHookFilter
try
{
var parts = args1.Split(":", StringSplitOptions.RemoveEmptyEntries);
var project = "opensats";
var paymentMethod = new PaymentMethodId("BTC", PaymentTypes.LightningLike);
var paymentMethod = PayoutTypes.LN.GetPayoutMethodId("BTC");
if (parts.Length > 1)
{
project = parts[1];
@@ -49,11 +43,11 @@ public class OpenSatsPrismClaimCreate : IPluginHookFilter
if (parts.Length > 2)
{
paymentMethod = PaymentMethodId.Parse(parts[2]);
paymentMethod = PayoutMethodId.Parse(parts[2]);
}
var handler = _serviceProvider.GetServices<IPayoutHandler>().FindPayoutHandler(paymentMethod);
_serviceProvider.GetService<PayoutMethodHandlerDictionary>().TryGetValue(paymentMethod, out var handler);
if (handler is null)
{
return null;
@@ -75,7 +69,7 @@ public class OpenSatsPrismClaimCreate : IPluginHookFilter
var destination = invoiceBtcpayModel.Value<string>("btcAddress");
var receiptLink = invoiceBtcpayModel.Value<string>("receiptLink");
var claimDestination = await handler.ParseClaimDestination(paymentMethod,destination, CancellationToken.None);
var claimDestination = await handler.ParseClaimDestination(destination, CancellationToken.None);
if (claimDestination.destination is null)
{
@@ -88,7 +82,7 @@ public class OpenSatsPrismClaimCreate : IPluginHookFilter
});
claimRequest.Destination = claimDestination.destination;
claimRequest.PaymentMethodId = paymentMethod;
claimRequest.PayoutMethodId = paymentMethod;
return claimRequest;

View File

@@ -11,7 +11,7 @@ public class PrismPlugin : BaseBTCPayServerPlugin
{
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{
new() {Identifier = nameof(BTCPayServer), Condition = ">=1.13.0"}
new() {Identifier = nameof(BTCPayServer), Condition = ">=2.0.0"}
};
public override void Execute(IServiceCollection applicationBuilder)

View File

@@ -20,5 +20,5 @@ public class PrismDestination
public string Destination { get; set; }
public decimal? Reserve { get; set; }
public long? SatThreshold { get; set; }
public string? PaymentMethodId { get; set; }
public string? PayoutMethodId { get; set; }
}

View File

@@ -13,6 +13,7 @@ using BTCPayServer.HostedServices;
using BTCPayServer.Lightning;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Payouts;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores;
@@ -47,13 +48,13 @@ namespace BTCPayServer.Plugins.Prism
{
private readonly StoreRepository _storeRepository;
private readonly ILogger<SatBreaker> _logger;
private readonly LightningAddressService _lightningAddressService;
private readonly PullPaymentHostedService _pullPaymentHostedService;
private readonly LightningLikePayoutHandler _lightningLikePayoutHandler;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly LightningClientFactoryService _lightningClientFactoryService;
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings;
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private readonly PayoutMethodHandlerDictionary _payoutMethodHandlerDictionary;
private readonly IPluginHookService _pluginHookService;
private Dictionary<string, PrismSettings> _prismSettings;
@@ -62,24 +63,24 @@ namespace BTCPayServer.Plugins.Prism
public SatBreaker(StoreRepository storeRepository,
EventAggregator eventAggregator,
ILogger<SatBreaker> logger,
LightningAddressService lightningAddressService,
PullPaymentHostedService pullPaymentHostedService,
LightningLikePayoutHandler lightningLikePayoutHandler,
BTCPayNetworkProvider btcPayNetworkProvider,
LightningClientFactoryService lightningClientFactoryService,
IOptions<LightningNetworkOptions> lightningNetworkOptions,
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
PayoutMethodHandlerDictionary payoutMethodHandlerDictionary,
IPluginHookService pluginHookService) : base(eventAggregator, logger)
{
_storeRepository = storeRepository;
_logger = logger;
_lightningAddressService = lightningAddressService;
_pullPaymentHostedService = pullPaymentHostedService;
_lightningLikePayoutHandler = lightningLikePayoutHandler;
_btcPayNetworkProvider = btcPayNetworkProvider;
_lightningClientFactoryService = lightningClientFactoryService;
_lightningNetworkOptions = lightningNetworkOptions;
_btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings;
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_payoutMethodHandlerDictionary = payoutMethodHandlerDictionary;
_pluginHookService = pluginHookService;
}
@@ -145,20 +146,28 @@ namespace BTCPayServer.Plugins.Prism
{
continue;
}
foreach (var payout in storePayouts)
{
if (!pendingPayouts.TryGetValue(payout.Id, out var pendingPayout))
{
continue;
}
if(payout.GetPayoutMethodId() is not { } payoutMethodId)
continue;
if (!_payoutMethodHandlerDictionary.TryGetValue(payoutMethodId, out var handler))
{
continue;
}
long toCredit = 0;
switch (payout.State)
{
case PayoutState.Completed:
var proof = _lightningLikePayoutHandler.ParseProof(payout) as PayoutLightningBlob;
var proof = handler.ParseProof(payout) as PayoutLightningBlob;
long? feePaid = null;
if (!string.IsNullOrEmpty(proof?.PaymentHash))
@@ -168,10 +177,10 @@ namespace BTCPayServer.Plugins.Prism
var store = await _storeRepository.FindStore(payout.StoreDataId);
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
var id = new PaymentMethodId("BTC", LightningPaymentType.Instance);
var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
.OfType<LightningSupportedPaymentMethod>()
.FirstOrDefault(d => d.PaymentId == id);
var id = PaymentTypes.LN.GetPaymentMethodId("BTC");
var existing =
store.GetPaymentMethodConfig<LightningPaymentMethodConfig>(id,
_paymentMethodHandlerDictionary);
if (existing?.GetExternalLightningUrl() is { } connectionString)
{
lnClient = _lightningClientFactoryService.Create(connectionString,
@@ -313,29 +322,34 @@ namespace BTCPayServer.Plugins.Prism
private (Split, LightMoney)[] DetermineMatches(PrismSettings prismSettings, InvoiceEntity entity)
{
//first check the primary thing - ln address
var explicitPMI = new PaymentMethodId("BTC", LNURLPayPaymentType.Instance);
var pm = entity.GetPaymentMethod(explicitPMI);
var pmd = pm?.GetPaymentMethodDetails() as LNURLPayPaymentMethodDetails;
List<(Split, LightMoney)> result = new();
var explicitPMI = PaymentTypes.LNURL.GetPaymentMethodId("BTC");
var pm = entity.GetPaymentPrompt(explicitPMI);
var payments = entity.GetPayments(true).GroupBy(paymentEntity => paymentEntity.GetPaymentMethodId()).ToArray();
if (pmd?.ConsumedLightningAddress is not null)
var payments = entity.GetPayments(true).GroupBy(paymentEntity => paymentEntity.PaymentMethodId).ToArray();
List<(Split, LightMoney)> result = new();
if(_paymentMethodHandlerDictionary.TryGetValue(explicitPMI, out var handler) && pm is not null)
{
var address = pmd.ConsumedLightningAddress.Split("@")[0];
var matchedExplicit = prismSettings.Splits.FirstOrDefault(s =>
s.Source.Equals(address, StringComparison.InvariantCultureIgnoreCase));
if (matchedExplicit is not null)
var pmd = handler.ParsePaymentPromptDetails(pm.Details) as LNURLPayPaymentMethodDetails;
if (pmd?.ConsumedLightningAddress is not null)
{
var explicitPayments = payments.FirstOrDefault(grouping =>
grouping.Key == explicitPMI)?.Sum(paymentEntity => paymentEntity.PaidAmount.Net);
payments = payments.Where(grouping => grouping.Key != explicitPMI).ToArray();
var address = pmd.ConsumedLightningAddress.Split("@")[0];
var matchedExplicit = prismSettings.Splits.FirstOrDefault(s =>
s.Source.Equals(address, StringComparison.InvariantCultureIgnoreCase));
if (explicitPayments > 0)
{
result.Add((matchedExplicit, LightMoney.FromUnit(explicitPayments.Value, LightMoneyUnit.BTC)));
}
}
if (matchedExplicit is not null)
{
var explicitPayments = payments.FirstOrDefault(grouping =>
grouping.Key == explicitPMI)?.Sum(paymentEntity => paymentEntity.PaidAmount.Net);
payments = payments.Where(grouping => grouping.Key != explicitPMI).ToArray();
if (explicitPayments > 0)
{
result.Add((matchedExplicit, LightMoney.FromUnit(explicitPayments.Value, LightMoneyUnit.BTC)));
}
}
}
}
var catchAlls = prismSettings.Splits.Where(split => split.Source.StartsWith("*")).Select(split =>
@@ -346,20 +360,25 @@ namespace BTCPayServer.Plugins.Prism
switch (split.Source)
{
case "*":
pmi = new PaymentMethodId("BTC", PaymentTypes.LightningLike);
pmi = PaymentTypes.LN.GetPaymentMethodId("BTC");
break;
case "*All":
break;
case var s when PaymentTypes.TryParse(s.Substring(1), out var pType):
pmi = new PaymentMethodId("BTC", pType);
case var s when s.StartsWith("*") && s.Substring(1) ==PaymentTypes.CHAIN.ToString():
pmi = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
break;
case var s2 when s2.StartsWith("*") && s2.Substring(1) ==PaymentTypes.LN.ToString():
pmi = PaymentTypes.LN.GetPaymentMethodId("BTC");
break;
case var s3 when s3.StartsWith("*") && s3.Substring(1) ==PaymentTypes.LNURL.ToString():
pmi = PaymentTypes.LNURL.GetPaymentMethodId("BTC");
break;
case var s when !PaymentMethodId.TryParse(s.Substring(1), out pmi):
valid = false;
break;
}
if (pmi is not null && pmi.CryptoCode != "BTC")
if (pmi is not null && !pmi.ToString().StartsWith("BTC-"))
{
valid = false;
}
@@ -367,6 +386,7 @@ namespace BTCPayServer.Plugins.Prism
return (pmi, valid, split);
}).Where(tuple => tuple.valid).ToDictionary(split => split.pmi, split => split.split);
while(payments.Any() || catchAlls.Any())
{
decimal paymentSum;
@@ -511,9 +531,9 @@ namespace BTCPayServer.Plugins.Prism
continue;
}
var pmi = string.IsNullOrEmpty(destinationSettings?.PaymentMethodId) ||
!PaymentMethodId.TryParse(destinationSettings?.PaymentMethodId, out var pmi2)
? new PaymentMethodId("BTC", LightningPaymentType.Instance)
var pmi = string.IsNullOrEmpty(destinationSettings?.PayoutMethodId) ||
!PayoutMethodId.TryParse(destinationSettings?.PayoutMethodId, out var pmi2)
? PayoutTypes.LN.GetPayoutMethodId("BTC")
: pmi2;
var source = "Prism";
@@ -526,7 +546,7 @@ namespace BTCPayServer.Plugins.Prism
Destination = new PrismPlaceholderClaimDestination(destinationSettings?.Destination ?? destination),
PreApprove = true,
StoreId = storeId,
PaymentMethodId = pmi,
PayoutMethodId = pmi,
Value = Money.Satoshis(payoutAmount).ToDecimal(MoneyUnit.BTC),
Metadata = JObject.FromObject(new
{

View File

@@ -9,7 +9,7 @@
<PropertyGroup>
<Product>SideShift</Product>
<Description>Allows you to embed a SideShift conversion screen to allow customers to pay with altcoins.</Description>
<Version>1.1.13</Version>
<Version>1.1.14</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<!-- Plugin development properties -->

View File

@@ -1,84 +0,0 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Data.Payouts.LightningLike;
using BTCPayServer.HostedServices;
using BTCPayServer.Lightning;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Plugins.SideShift;
public class PrismClaimCreate : IPluginHookFilter
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly BTCPayNetworkProvider _networkProvider;
public string Hook => "prism-claim-create";
public PrismClaimCreate(IHttpClientFactory httpClientFactory, BTCPayNetworkProvider networkProvider)
{
_httpClientFactory = httpClientFactory;
_networkProvider = networkProvider;
}
public async Task<object> Execute(object args)
{
var network = _networkProvider.GetNetwork<BTCPayNetwork>("BTC");
if (args is not ClaimRequest claimRequest || network is null)
{
return args;
}
if (claimRequest.Destination?.ToString() is not { } args1 || !args1.StartsWith("sideshift:")) return args;
var request = JObject.Parse(args1.Substring("sideshift:".Length)).ToObject<PrismSideshiftDestination>();
if (!request.Valid())
{
return null;
}
var client = _httpClientFactory.CreateClient("sideshift");
var shiftResponse = await client.PostAsJsonAsync("https://sideshift.ai/api/v2/shifts/variable", new
{
settleAddress = request.ShiftDestination,
affiliateId = "qg0OrfHJV",
settleMemo = request.ShiftMemo,
depositCoin = "BTC",
depositNetwork = request.SourceNetwork?? "lightning",
settleCoin = request.ShiftCoin,
settleNetwork = request.ShiftNetwork,
}
);
if (!shiftResponse.IsSuccessStatusCode)
{
return null;
}
var shift = await shiftResponse.Content.ReadAsAsync<SideShiftController.ShiftResponse>();
try
{
LNURL.LNURL.Parse(shift.depositAddress, out _);
claimRequest.Destination = new LNURLPayClaimDestinaton(shift.depositAddress);
claimRequest.Metadata = JObject.FromObject(new
{
Source = $"Prism->Sideshift",
SourceLink = $"https://sideshift.ai/orders/{shift.id}?openSupport=true",
});
return claimRequest;
}
catch (Exception e)
{
if (BOLT11PaymentRequest.TryParse(shift.depositAddress, out var bolt11, network.NBitcoinNetwork))
{
claimRequest.Destination = new BoltInvoiceClaimDestination(shift.depositAddress, bolt11);
claimRequest.Metadata = JObject.FromObject(new
{
Source = $"Prism->Sideshift",
SourceLink = $"https://sideshift.ai/orders/{shift.id}?openSupport=true",
});
return claimRequest;
}
}
return null;
}
}

View File

@@ -1,17 +0,0 @@
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Plugins.SideShift;
public class PrismDestinationValidate : IPluginHookFilter
{
public string Hook => "prism-destination-validate";
public async Task<object> Execute(object args)
{
if (args is not string args1 || !args1.StartsWith("sideshift:")) return args;
var json = JObject.Parse(args1.Substring("sideshift:".Length)).ToObject<PrismSideshiftDestination>();
return json.Valid();
}
}

View File

@@ -1,15 +0,0 @@
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
namespace BTCPayServer.Plugins.SideShift;
public class PrismEditFilter : IPluginHookFilter
{
public string Hook => "prism-edit-buttons";
public Task<object> Execute(object args)
{
return Task.FromResult<object>(( args??"") + "<button type='button' class=\"btn btn-primary\" data-bs-toggle=\"modal\" data-bs-target=\"#sideshiftModal\">Generate SideShift destination</button>");
}
}

View File

@@ -1,16 +0,0 @@
namespace BTCPayServer.Plugins.SideShift;
public class PrismSideshiftDestination
{
public string ShiftCoin { get; set; }
public string ShiftNetwork { get; set; }
public string ShiftDestination { get; set; }
public string ShiftMemo { get; set; }
public string SourceNetwork { get; set; }
public bool Valid()
{
return !string.IsNullOrEmpty(ShiftCoin) && !string.IsNullOrEmpty(ShiftNetwork) &&
!string.IsNullOrEmpty(ShiftDestination);
}
}

View File

@@ -9,6 +9,7 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.Payments;
using BTCPayServer.Payouts;
using BTCPayServer.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -26,7 +27,7 @@ namespace BTCPayServer.Plugins.SideShift
{
private readonly SideShiftService _sideShiftService;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IEnumerable<IPayoutHandler> _payoutHandlers;
private readonly PayoutMethodHandlerDictionary _payoutMethodHandlerDictionary;
private readonly PullPaymentHostedService _pullPaymentHostedService;
private readonly BTCPayNetworkJsonSerializerSettings _serializerSettings;
private readonly ApplicationDbContextFactory _dbContextFactory;
@@ -34,13 +35,13 @@ namespace BTCPayServer.Plugins.SideShift
public SideShiftController(
SideShiftService sideShiftService,
IHttpClientFactory httpClientFactory,
IEnumerable<IPayoutHandler> payoutHandlers,
PayoutMethodHandlerDictionary payoutMethodHandlerDictionary,
PullPaymentHostedService pullPaymentHostedService,
BTCPayNetworkJsonSerializerSettings serializerSettings, ApplicationDbContextFactory dbContextFactory)
{
_sideShiftService = sideShiftService;
_httpClientFactory = httpClientFactory;
_payoutHandlers = payoutHandlers;
_payoutMethodHandlerDictionary = payoutMethodHandlerDictionary;
_pullPaymentHostedService = pullPaymentHostedService;
_serializerSettings = serializerSettings;
_dbContextFactory = dbContextFactory;
@@ -111,19 +112,23 @@ namespace BTCPayServer.Plugins.SideShift
ModelState.AddModelError(nameof(request.Amount), "Amount must be specified");
}
if (!PaymentMethodId.TryParse(request.PaymentMethod, out var pmi))
if (!PayoutMethodId.TryParse(request.PayoutMethodId, out var pmi))
{
ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method");
ModelState.AddModelError(nameof(request.PayoutMethodId), "Invalid payout method");
}
else
{
handler = _payoutHandlers.FindPayoutHandler(pmi);
if (handler == null)
if (!_payoutMethodHandlerDictionary.TryGetValue(pmi, out handler))
{
ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method");
ModelState.AddModelError(nameof(request.PayoutMethodId), "Invalid payment method");
}
}
var isLN = pmi.ToString().EndsWith("-" +PayoutTypes.LN.Id);
if (isLN)
{
ModelState.AddModelError(nameof(request.PayoutMethodId), "SideShift does not support Lightning payouts");
}
if (!ModelState.IsValid)
{
return this.CreateValidationError(ModelState);
@@ -166,13 +171,13 @@ namespace BTCPayServer.Plugins.SideShift
// shiftResponse.EnsureSuccessStatusCode();
// var shift = await shiftResponse.Content.ReadAsAsync<ShiftResponse>();
var cryptoCode = pmi.ToString().Split('-')[0];
var shiftResponse = await client.PostAsJsonAsync("https://sideshift.ai/api/v2/shifts/variable", new
{
settleAddress = request.Destination,
affiliateId = "qg0OrfHJV",
settleMemo = request.Memo,
depositCoin = pmi.CryptoCode,
depositNetwork = pmi.PaymentType == LightningPaymentType.Instance ? "lightning" : null,
depositCoin = cryptoCode,
settleCoin = request.ShiftCurrency,
settleNetwork = request.ShiftNetwork,
}
@@ -188,20 +193,20 @@ namespace BTCPayServer.Plugins.SideShift
var destination =
await handler.ParseAndValidateClaimDestination(pmi, shift.depositAddress, ppBlob,
await handler.ParseAndValidateClaimDestination(shift.depositAddress, ppBlob,
CancellationToken.None);
var claim = await _pullPaymentHostedService.Claim(new ClaimRequest()
{
PullPaymentId = pullPaymentId,
Destination = destination.destination,
PaymentMethodId = pmi,
PayoutMethodId = pmi,
Value = request.Amount
});
if (claim.Result == ClaimRequest.ClaimResult.Ok)
{
await using var ctx = _dbContextFactory.CreateContext();
ppBlob.Description += $"<br/>The payout of {claim.PayoutData.Destination} will be forwarded to SideShift.ai for further conversion. Please go to <a href=\"https://sideshift.ai/orders/{shift.id}?openSupport=true\">the order page</a> for support.";
ppBlob.Description += $"<br/>The payout of {destination.destination} will be forwarded to SideShift.ai for further conversion. Please go to <a href=\"https://sideshift.ai/orders/{shift.id}?openSupport=true\">the order page</a> for support.";
pp.SetBlob(ppBlob);
ctx.Attach(pp).State = EntityState.Modified;
await ctx.SaveChangesAsync();
@@ -242,19 +247,21 @@ namespace BTCPayServer.Plugins.SideShift
private Client.Models.PayoutData ToModel(Data.PayoutData p)
{
var blob = p.GetBlob(_serializerSettings);
var model = new Client.Models.PayoutData
var model = new Client.Models.PayoutData()
{
Id = p.Id,
PullPaymentId = p.PullPaymentDataId,
Date = p.Date,
Amount = blob.Amount,
PaymentMethodAmount = blob.CryptoAmount,
OriginalCurrency = p.OriginalCurrency,
OriginalAmount = p.OriginalAmount,
PayoutCurrency = p.Currency,
PayoutAmount = p.Amount,
Revision = blob.Revision,
State = p.State,
PayoutMethodId = p.PayoutMethodId,
PaymentProof = p.GetProofBlobJson(),
Destination = blob.Destination,
PaymentMethod = p.PaymentMethodId,
CryptoCode = p.GetPaymentMethodId().CryptoCode,
PaymentProof = p.GetProofBlobJson()
Metadata = blob.Metadata?? new JObject(),
};
return model;
}

View File

@@ -1,6 +1,5 @@
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Abstractions.Services;
using Microsoft.Extensions.DependencyInjection;
namespace BTCPayServer.Plugins.SideShift
@@ -8,41 +7,22 @@ namespace BTCPayServer.Plugins.SideShift
public class SideShiftPlugin : BaseBTCPayServerPlugin
{
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{
new() {Identifier = nameof(BTCPayServer), Condition = ">=1.12.0"}
{ new() { Identifier = nameof(BTCPayServer), Condition = ">=2.0.0" }
};
public override void Execute(IServiceCollection applicationBuilder)
{
applicationBuilder.AddSingleton<SideShiftService>();
applicationBuilder.AddHostedService(provider => provider.GetService<SideShiftService>());
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/SideShiftNav",
"store-integrations-nav"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/PullPaymentViewInsert",
"pullpayment-foot"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/StoreIntegrationSideShiftOption",
"store-integrations-list"));
applicationBuilder.AddUIExtension("store-integrations-nav","SideShift/SideShiftNav");
applicationBuilder.AddUIExtension("pullpayment-foot","SideShift/PullPaymentViewInsert");
applicationBuilder.AddUIExtension("store-integrations-list", "SideShift/StoreIntegrationSideShiftOption");
// Checkout v2
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/CheckoutPaymentMethodExtension",
"checkout-payment-method"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/CheckoutPaymentExtension",
"checkout-payment"));
// Checkout Classic
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/CheckoutContentExtension",
"checkout-bitcoin-post-content"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/CheckoutContentExtension",
"checkout-lightning-post-content"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/CheckoutTabExtension",
"checkout-bitcoin-post-tabs"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/CheckoutTabExtension",
"checkout-lightning-post-tabs"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/CheckoutEnd",
"checkout-end"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("SideShift/PrismEnhance",
"prism-edit"));
applicationBuilder.AddSingleton<IPluginHookFilter, PrismDestinationValidate>();
applicationBuilder.AddSingleton<IPluginHookFilter, PrismClaimCreate>();
applicationBuilder.AddSingleton<IPluginHookFilter, PrismEditFilter>();
applicationBuilder.AddUIExtension("checkout-payment-method", "SideShift/CheckoutPaymentMethodExtension");
applicationBuilder.AddUIExtension("checkout-payment","SideShift/CheckoutPaymentExtension");
base.Execute(applicationBuilder);
}
}

View File

@@ -1,26 +0,0 @@
@using BTCPayServer.Plugins.SideShift
@inject SideShiftService SideShiftService
@model BTCPayServer.Models.InvoicingModels.PaymentModel
@{
var settings = await SideShiftService.GetSideShiftForInvoice(Model.InvoiceId, Model.StoreId);
if (settings?.Enabled is true)
{
<div id="sideshift" class="bp-view payment manual-flow" :class="{ active: currentTab == 'undefined' || currentTab == 'sideshift' }">
<div class="manual__step-two__instructions">
<span>
{{$t("ConversionTab_BodyTop", srvModel)}}
<br/><br/>
{{$t("ConversionTab_BodyDesc", srvModel)}}
</span>
</div>
<side-shift inline-template
:to-currency="srvModel.paymentMethodId"
:to-currency-due="srvModel.btcDue * (1 + (@settings.AmountMarkupPercentage / 100)) "
:to-currency-address="srvModel.btcAddress">
<a v-on:click="openDialog($event)" href="#" class="action-button btn btn-secondary rounded-pill w-100 mt-4">{{$t("Pay with SideShift")}}</a>
</side-shift>
</div>
}
}

View File

@@ -1,14 +0,0 @@
@using BTCPayServer.Plugins.SideShift
@inject BTCPayServer.Security.ContentSecurityPolicies csp
@inject SideShiftService SideShiftService
@model BTCPayServer.Models.InvoicingModels.PaymentModel
@{
var settings = await SideShiftService.GetSideShiftForInvoice(Model.InvoiceId, Model.StoreId);
if (settings?.Enabled is true)
{
csp.Add("script-src", "https://sideshift.ai");
csp.Add("script-src", "*.sideshift.ai");
<script src="~/Resources/js/sideShiftComponent.js"></script>
<script src="https://sideshift.ai/static/js/main.js" defer></script>
}
}

View File

@@ -2,19 +2,18 @@
@using BTCPayServer.Payments
@inject BTCPayServer.Security.ContentSecurityPolicies csp
@inject SideShiftService SideShiftService
@model BTCPayServer.Models.InvoicingModels.PaymentModel
@model BTCPayServer.Models.InvoicingModels.CheckoutModel
@{
var settings = await SideShiftService.GetSideShiftForInvoice(Model.InvoiceId, Model.StoreId);
var preferredTargetPaymentMethodId = "";
PaymentMethodId preferredTargetPaymentMethodId = null;
if(!PaymentMethodId.TryParse(settings?.PreferredTargetPaymentMethodId, out var preferredPMI))
{
preferredTargetPaymentMethodId = null;
}
else
{
preferredTargetPaymentMethodId = Model.AvailableCryptos.FirstOrDefault(crypto =>
crypto.PaymentMethodId == settings.PreferredTargetPaymentMethodId ||
(crypto.CryptoCode == preferredPMI.CryptoCode && crypto.PaymentMethodId.EndsWith(LNURLPayPaymentType.Instance.GetId()) || crypto.PaymentMethodId.EndsWith(LightningPaymentType.Instance.GetId())))?.PaymentMethodId;
preferredTargetPaymentMethodId = Model.AvailablePaymentMethods.FirstOrDefault(crypto =>
crypto.PaymentMethodId == preferredPMI )?.PaymentMethodId;
}
}
@if (settings?.Enabled is true)
@@ -25,7 +24,8 @@
<template id="side-shift-checkout-template">
<div class="payment-box">
<p v-html="content"></p>
<button type="button" v-on:click="openDialog" class="btn btn-primary rounded-pill w-100">{{$t("Pay with SideShift")}}</button>
<p v-if="!settleMethodId" class="text-danger">Lightning is not supported via Sideshift. Select another payment method first, then come back.</p>
<button v-if="settleMethodId" type="button" v-on:click="openDialog" class="btn btn-primary rounded-pill w-100">{{$t("Pay with SideShift", {crryptoCode: settleMethodId})}}</button>
</div>
</template>
<script>
@@ -48,7 +48,7 @@
},200)
if(this.preferredToCurrency && this.model.paymentMethodId !== this.preferredToCurrency){
if (this.model.onChainWithLnInvoiceFallback && this.model.paymentMethodId === "BTC"){
if (this.model.onChainWithLnInvoiceFallback && this.model.paymentMethodId === "BTC-CHAIN"){
return;
}
this.$parent.paymentMethodId = this.preferredToCurrency;
@@ -58,16 +58,7 @@
},
computed: {
lightning () {
if (!this.model.onChainWithLnInvoiceFallback || this.model.paymentMethodId !== "BTC"){
return null;
}
const index = this.model.invoiceBitcoinUrl.indexOf("lightning=");
if (index === -1){
return null;
}
return this.model.invoiceBitcoinUrl.slice(index + "lightning=".length);
},
content () {
return this.$i18n.i18next.t("conversion_body", this.model).replace(/\n/ig, '<br>');
},
@@ -76,16 +67,16 @@
},
settleMethodId () {
const toCurrency = this.currency.toLowerCase();
const toCurrency = this.currency.toUpperCase();
if (toCurrency === "lbtc") {
return 'liquid';
} else if (toCurrency === "usdt") {
return "usdtla";
} else if (toCurrency.endsWith('lightninglike') || toCurrency.endsWith('lnurlpay') || this.lightning) {
return "ln";
} else if (toCurrency.endsWith('LN') || toCurrency.endsWith('LNURL')) {
return null;
} else {
return toCurrency.replace('_btclike', '').replace('_monerolike', '').replace('_zcashlike', '').toLowerCase();
return toCurrency.replace('-CHAIN', '').replace('_CHAIN', '').toLowerCase();
}
},
type () {
@@ -96,16 +87,19 @@
amountDue () {
return this.model.isUnsetTopUp
? undefined
: this.model.btcDue * (1 + (@settings.AmountMarkupPercentage / 100));
: this.model.due * (1 + (@settings.AmountMarkupPercentage / 100));
}
},
methods: {
openDialog () {
if (!this.settleMethodId){
return;
}
window.__SIDESHIFT__ = {
parentAffiliateId: "qg0OrfHJV",
defaultDepositMethodId: this.explicitId || undefined,
defaultSettleMethodId: this.settleMethodId,
settleAddress: this.lightning || this.model.btcAddress,
settleAddress: this.model.address,
settleAmount: this.amountDue,
type: this.type
};

View File

@@ -1,6 +1,6 @@
@using BTCPayServer.Plugins.SideShift
@inject SideShiftService SideShiftService
@model BTCPayServer.Models.InvoicingModels.PaymentModel
@model BTCPayServer.Models.InvoicingModels.CheckoutModel
@{
const string id = "SideShift";
var settings = await SideShiftService.GetSideShiftForInvoice(Model.InvoiceId, Model.StoreId);

View File

@@ -1,12 +0,0 @@
@using BTCPayServer.Plugins.SideShift
@inject SideShiftService SideShiftService
@model BTCPayServer.Models.InvoicingModels.PaymentModel
@{
var settings = await SideShiftService.GetSideShiftForInvoice(Model.InvoiceId, Model.StoreId);
if (settings?.Enabled is true)
{
<div class="payment-tabs__tab py-0" id="sideshift-tab" v-on:click="switchTab('sideshift')" v-bind:class="{ 'active': currentTab == 'sideshift'}" v-if="!srvModel.paymentMethodId.endsWith('LNURLPAY')">
<span>{{$t("Altcoins (SideShift)")}}</span>
</div>
}
}

View File

@@ -1,212 +0,0 @@
@using BTCPayServer.Abstractions.TagHelpers
@using BTCPayServer.Plugins.SideShift
@using Microsoft.AspNetCore.Mvc.TagHelpers
@inject SideShiftService SideShiftService
@{
var coins = await SideShiftService.GetSettleCoins();
coins = coins.Where(tuple => new[] {SideShiftService.CoinType.VariableOnly, SideShiftService.CoinType.Both}.Contains(tuple.Type)).ToList();
if(coins.Any() is not true)
{
return;
}
}
<script>
const ssAvailableCoins = @Json.Serialize(coins.ToDictionary(tuple=> $"{tuple.CryptoCode}_{tuple.Network}",tuple =>
new {
coin = tuple.DisplayName,
code = tuple.CryptoCode,
memo = tuple.HasMemo,
network = tuple.Network
}));
document.addEventListener('DOMContentLoaded', (event) => {
if (new URLSearchParams(window.location.search).has("hidejunk")) {
localStorage.setItem('hidejunk', 'true');
}
if(localStorage.getItem("hidejunk")) {
[...document.querySelectorAll("#sscoin option")].forEach(option => {
const text = option.innerText.toLowerCase();
// Check if the option matches the criteria
const isMatch = (text.includes("bitcoin") || text.includes("tether") || text.includes("usd")) &&
!text.includes("bitcoincash");
// If it matches, show it; otherwise, hide it
//remove it
if (!isMatch)
option.remove();
});
}
// const sideshiftDestinationButton = document.createElement("button");
// sideshiftDestinationButton.type= "button";
// sideshiftDestinationButton.className = "btn btn-primary btn-sm";
// sideshiftDestinationButton.innerText = "Generate SideShift destination";
// document.getElementById("add-prism").insertAdjacentElement("afterend", sideshiftDestinationButton);
// const modal = new bootstrap.Modal('#sideshiftModal');
// sideshiftDestinationButton.addEventListener("click", ev => modal.show());
const selectedSideShiftCoin = document.getElementById("sscoin");
const specifiedSideShiftDestination = document.getElementById("ssdest");
const specifiedSideShiftDepositNetwork = document.getElementById("ssdepositNetwork");
const specifiedSideShiftMemo= document.getElementById("ssmemo");
const shiftButton = document.getElementById("ssshift");
let selectedCoin = null;
const destinationContainer = document.getElementById("ss-dest-info");
specifiedSideShiftDestination.addEventListener("input", ev1 => {
document.getElementById("ss-result").style.display = "none";
if (isValid()){
shiftButton.removeAttribute("disabled");
}
});
specifiedSideShiftMemo.addEventListener("input", ev1 => {
if (isValid()){
shiftButton.removeAttribute("disabled");
}else{
shiftButton.setAttribute("disabled", "disabled");
}
});
isValid = ()=>{
return selectedCoin && specifiedSideShiftDestination.value &&
(!selectedCoin.memo || specifiedSideShiftMemo.value);
};
handleSelectChanges = ()=>{
if (selectedSideShiftCoin.value){
selectedCoin = ssAvailableCoins[selectedSideShiftCoin.value];
destinationContainer.style.display = "block";
if (selectedCoin){
specifiedSideShiftMemo.parentElement.style.display = selectedCoin.memo ? "block" : "none";
specifiedSideShiftMemo.value = selectedCoin.memo ? specifiedSideShiftMemo.value : "";
}
}else{
destinationContainer.style.display = "none";
}
};
selectedSideShiftCoin.addEventListener("change", ev1 => {
handleSelectChanges();
});
shiftButton.addEventListener("click", ev1 => {
document.getElementById("ss-server-errors").innerHTML = "";
document.getElementById("ss-result-txt").value = "";
document.getElementById("ss-result-additional-info").value = "";
if (isValid()){
shiftButton.setAttribute("disabled", "disabled");
const type = "permanent";
if (type ==="permanent"){
fetch("https://sideshift.ai/api/v2/shifts/variable",{
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
settleAddress: specifiedSideShiftDestination.value,
settleMemo: specifiedSideShiftMemo.value,
affiliateId: "qg0OrfHJV",
depositCoin : "BTC",
depositNetwork : specifiedSideShiftDepositNetwork.value,
settleCoin: selectedCoin.code,
settleNetwork: selectedCoin.network,
permanent: true
})})
.then(async response => {
if (!response.ok){
try {
document.getElementById("ss-server-errors").innerHTML = (await response.json())["error"]["message"];
}catch{
document.getElementById("ss-server-errors").innerHTML = JSON.stringify((await response.json()));
}
return;
}
const shift = await response.json();
document.getElementById("ss-result").style.display = "block";
document.getElementById("ss-result-txt").value = shift.depositAddress;
const link = `https://sideshift.ai/orders/${shift.id}`;
document.getElementById("ss-result-additional-info").innerHTML = "<b>IMPORTANT:</b> You must keep this link to be able to recover your funds in case of a problem. <a href='"+link+"' target='_blank'>"+link+"</a> ";
})
.catch(error => document.getElementById("ss-server-errors").innerHTML = error)
.finally(() => shiftButton.removeAttribute("disabled"));
}else{
document.getElementById("ss-result").style.display = "block";
document.getElementById("ss-result-txt").value = "sideshift:"+JSON.stringify({
shiftCoin:selectedCoin.code,
shiftNetwork: selectedCoin.network,
shiftDestination: specifiedSideShiftDestination.value,
shiftMemo: specifiedSideShiftMemo.value,
shiftDepositNetwork: specifiedSideShiftDepositNetwork.value
});
shiftButton.removeAttribute("disabled");
}
}
});
handleSelectChanges();
});
</script>
<div class="modal" tabindex="-1" id="sideshiftModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Generate SideShift destination</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div id="ss-server-errors" class="text-danger"></div>
<p>This will generate a piece of code based on Sideshift configuration that can work as a valid destination in prism. Prism will then generate a "shift" on Sideshift and send the funds through LN to it, and Sideshift will send you the conversion. </p>
<div class="form-group">
<label class="form-label">How do you want to send BTC to SideShift?</label>
<select id="ssdepositNetwork" class="form-select">
<option value="lightning">Lightning</option>
<option value="bitcoin">On-Chain</option>
</select>
</div><div class="form-group">
<label class="form-label">Which coin should Sideshift send you</label>
<select id="sscoin" class="form-select">
@foreach (var opt in coins)
{
<option value="@($"{opt.CryptoCode}_{opt.Network}")">@opt.ToString()</option>
}
</select>
</div>
<div id="ss-dest-info" style="display: none">
<div class="form-group">
<label class="form-label">Destination</label>
<input type="text" id="ssdest" class="form-control"/>
</div>
<div class="form-group">
<label class="form-label">Memo</label>
<input type="text" id="ssmemo" class="form-control"/>
</div>
<button type="button" class="btn btn-primary" id="ssshift" disabled="disabled">Generate code</button>
<div id="ss-result" class="form-group mt-4" style="display: none;">
<label class="form-label">Generated code</label>
<div class="input-group">
<input type="text" id="ss-result-txt" class="form-control" readonly="readonly"/>
<button type="button" class="btn btn-secondary" data-clipboard-target="#ss-result-txt">
<vc:icon symbol="copy"/>
</button>
</div>
<p id="ss-result-additional-info"></p>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,4 +1,5 @@
@using BTCPayServer.Plugins.SideShift
@using BTCPayServer.Payouts
@using BTCPayServer.Plugins.SideShift
@model BTCPayServer.Models.ViewPullPaymentModel
@inject SideShiftService SideShiftService
@{
@@ -14,7 +15,8 @@
return;
}
var potentialPaymentMethods = Model.PaymentMethods;//.Where(id => id.CryptoCode.Equals(Model.Currency, StringComparison.OrdinalIgnoreCase)).ToList();
var potentialPaymentMethods = Model.PayoutMethodIds.ToList();//.Where(id => id.CryptoCode.Equals(Model.Currency, StringComparison.OrdinalIgnoreCase)).ToList();
potentialPaymentMethods.Remove(PayoutTypes.LN.GetPayoutMethodId("BTC"));
if (Model.IsPending && potentialPaymentMethods.Any())
{
<script>
@@ -26,7 +28,7 @@
memo = tuple.HasMemo,
network = tuple.Network
}));
const ssPaymentMethods = @Json.Serialize(potentialPaymentMethods.Select(id => new { id = id.ToString(), name= id.ToPrettyString()}));
const ssPaymentMethods = @Json.Serialize(potentialPaymentMethods.Select(id => new { id = id.ToString(), name= id.ToString()}));
document.addEventListener("DOMContentLoaded", ev => {
const ssButton = document.createElement("button");
ssButton.type= "button";
@@ -142,7 +144,7 @@
<select id="sspmi" class="form-select">
@foreach (var opt in potentialPaymentMethods)
{
<option value="@opt.ToString()">@opt.ToPrettyString()</option>
<option value="@opt.ToString()">@opt.ToString()</option>
}
</select>
</div>

View File

@@ -4,13 +4,14 @@
@using BTCPayServer.Plugins.SideShift
@using Microsoft.AspNetCore.Mvc.TagHelpers
@model BTCPayServer.Plugins.SideShift.SideShiftSettings
@inject BTCPayNetworkProvider BTCPayNetworkProvider
@inject SideShiftService SideShiftService
@{
ViewData.SetActivePage("SideShift", "SideShift", "SideShift");
var store = Context.GetStoreData();
var allowedPaymentMethods = store.GetEnabledPaymentIds(BTCPayNetworkProvider)
.Select(pmi => new SelectListItem(pmi.ToPrettyString(), pmi.ToString()))
var allowedPaymentMethods = store.GetEnabledPaymentIds()
.Where(id => !id.ToString().EndsWith("LN") && !id.ToString().EndsWith("LNURL"))
.Select(pmi => new SelectListItem(pmi.ToString(), pmi.ToString()))
.Prepend(new SelectListItem("Any", ""));
var coins = await SideShiftService.GetDepositOptions();
var allowedCoins = coins.OrderBy(coin => coin.ToString()).Select(c => new SelectListItem(c.ToString(), $"{c.CryptoCode}_{c.Network}"));

View File

@@ -10,7 +10,7 @@
<PropertyGroup>
<Product>Subscriptions</Product>
<Description>Offer and manage subscriptions through BTCPay Server</Description>
<Version>1.0.1</Version>
<Version>1.0.2</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<!-- Plugin development properties -->

View File

@@ -1,14 +1,13 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using BTCPayServer;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.Models;
using BTCPayServer.Plugins.Subscriptions;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.PaymentRequests;
using Microsoft.AspNetCore.Authorization;
@@ -23,13 +22,16 @@ namespace BTCPayServer.Plugins.Subscriptions;
public class SubscriptionController : Controller
{
private readonly AppService _appService;
private readonly UriResolver _uriResolver;
private readonly PaymentRequestRepository _paymentRequestRepository;
private readonly SubscriptionService _subscriptionService;
public SubscriptionController(AppService appService,
UriResolver uriResolver,
PaymentRequestRepository paymentRequestRepository, SubscriptionService subscriptionService)
{
_appService = appService;
_uriResolver = uriResolver;
_paymentRequestRepository = paymentRequestRepository;
_subscriptionService = subscriptionService;
}
@@ -44,7 +46,7 @@ public class SubscriptionController : Controller
return NotFound();
var ss = app.GetSettings<SubscriptionAppSettings>();
ss.SubscriptionName = app.Name;
ViewData["StoreBranding"] = new StoreBrandingViewModel(app.StoreData.GetStoreBlob());
ViewData["StoreBranding"] =await StoreBrandingViewModel.CreateAsync(Request, _uriResolver, app.StoreData.GetStoreBlob());
return View(ss);
}
@@ -62,8 +64,7 @@ public class SubscriptionController : Controller
{
return NotFound();
}
ViewData["StoreBranding"] = new StoreBrandingViewModel(app.StoreData.GetStoreBlob());
ViewData["StoreBranding"] =await StoreBrandingViewModel.CreateAsync(Request, _uriResolver, app.StoreData.GetStoreBlob());
return View(ss);
}

View File

@@ -1,11 +1,9 @@
using System.IO;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Abstractions.Services;
using BTCPayServer.HostedServices.Webhooks;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
@@ -18,12 +16,11 @@ namespace BTCPayServer.Plugins.Subscriptions
{
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
[
new() {Identifier = nameof(BTCPayServer), Condition = ">=1.13.0"}
new() {Identifier = nameof(BTCPayServer), Condition = ">=2.0.0"}
];
public override void Execute(IServiceCollection applicationBuilder)
{
applicationBuilder.AddSingleton<ISwaggerProvider, SubscriptionsSwaggerProvider>();
applicationBuilder.AddSingleton<SubscriptionService>();
applicationBuilder.AddSingleton<IWebhookProvider>(o => o.GetRequiredService<SubscriptionService>());
@@ -35,19 +32,17 @@ namespace BTCPayServer.Plugins.Subscriptions
}
}
public class SubscriptionsSwaggerProvider: ISwaggerProvider
public class SubscriptionsSwaggerProvider : ISwaggerProvider
{
private readonly IFileProvider _fileProvider;
public SubscriptionsSwaggerProvider(IWebHostEnvironment webHostEnvironment)
{
_fileProvider = webHostEnvironment.WebRootFileProvider;
}
public async Task<JObject> Fetch()
{
var file = _fileProvider.GetFileInfo("Resources/swagger.subscriptions.json");
using var reader = new StreamReader(file.CreateReadStream());
return JObject.Parse(await reader.ReadToEndAsync());

View File

@@ -48,10 +48,10 @@ public class AppMigrate : IStartupTask
await using var ctx = _contextFactory.CreateContext();
var invoices = await ctx.Invoices
.Include(data => data.InvoiceSearchData)
.Where(data => data.StoreDataId == setting.Key && data.OrderId == "tickettailor").ToListAsync(cancellationToken: cancellationToken);
.Where(data => data.StoreDataId == setting.Key && data.InvoiceSearchData.Any(searchData => searchData.Value == "tickettailor")).ToListAsync(cancellationToken: cancellationToken);
foreach (var invoice in invoices)
{
var entity = invoice.GetBlob(_btcPayNetworkProvider);
var entity = invoice.GetBlob();
entity.Metadata.SetAdditionalData("appId", app.Id);
entity.InternalTags.Add(AppService.GetAppInternalTag(app.Id));
InvoiceRepository.AddToTextSearch(ctx, invoice, AppService.GetAppSearchTerm(app) );

View File

@@ -9,7 +9,7 @@
<PropertyGroup>
<Product>TicketTailor</Product>
<Description>Allows you to integrate with TicketTailor.com to sell tickets for Bitcoin</Description>
<Version>2.0.3</Version>
<Version>2.0.4</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<!-- Plugin development properties -->

View File

@@ -12,6 +12,7 @@ using BTCPayServer.Client.Models;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Models;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
using Microsoft.AspNetCore.Authorization;
@@ -28,6 +29,7 @@ namespace BTCPayServer.Plugins.TicketTailor
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly TicketTailorService _ticketTailorService;
private readonly UriResolver _uriResolver;
private readonly AppService _appService;
private readonly ApplicationDbContextFactory _contextFactory;
private readonly InvoiceRepository _invoiceRepository;
@@ -35,6 +37,7 @@ namespace BTCPayServer.Plugins.TicketTailor
public TicketTailorController(IHttpClientFactory httpClientFactory,
TicketTailorService ticketTailorService,
UriResolver uriResolver,
AppService appService,
ApplicationDbContextFactory contextFactory,
InvoiceRepository invoiceRepository,
@@ -42,6 +45,7 @@ namespace BTCPayServer.Plugins.TicketTailor
{
_httpClientFactory = httpClientFactory;
_ticketTailorService = ticketTailorService;
_uriResolver = uriResolver;
_appService = appService;
_contextFactory = contextFactory;
_invoiceRepository = invoiceRepository;
@@ -87,7 +91,7 @@ namespace BTCPayServer.Plugins.TicketTailor
return View(new TicketTailorViewModel()
{
Event = evt, Settings = config,
StoreBranding = new StoreBrandingViewModel(app.StoreData.GetStoreBlob())
StoreBranding = await StoreBrandingViewModel.CreateAsync(Request, _uriResolver, app.StoreData.GetStoreBlob())
});
}
}
@@ -270,7 +274,6 @@ namespace BTCPayServer.Plugins.TicketTailor
AdditionalSearchTerms = new[] {"tickettailor", hold.Value.Item1.Id, evt.Id, AppService.GetAppSearchTerm(app)},
Checkout =
{
RequiresRefundEmail = true,
RedirectAutomatically = price > 0,
RedirectURL = redirectUrl,
},
@@ -292,13 +295,13 @@ namespace BTCPayServer.Plugins.TicketTailor
}, app.StoreData, HttpContext.Request.GetAbsoluteRoot(),new List<string> { AppService.GetAppInternalTag(appId) }, CancellationToken.None);
while (inv.Price == 0 && inv.Status == InvoiceStatusLegacy.New)
while (inv.Price == 0 && inv.Status == InvoiceStatus.New)
{
if (inv.Status == InvoiceStatusLegacy.New)
if (inv.Status == InvoiceStatus.New)
inv = await _invoiceRepository.GetInvoice(inv.Id);
}
return inv.Status.ToModernStatus() == InvoiceStatus.Settled
return inv.Status == InvoiceStatus.Settled
? RedirectToAction("Receipt", new {invoiceId = inv.Id})
: RedirectToAction("Checkout", "UIInvoice", new {invoiceId = inv.Id});
}
@@ -342,13 +345,13 @@ namespace BTCPayServer.Plugins.TicketTailor
var appId = AppService.GetAppInternalTags(inv).First();
var result = new TicketReceiptPage() {InvoiceId = invoiceId};
result.Status = inv.Status.ToModernStatus();
result.Status = inv.Status;
if (result.Status == InvoiceStatus.Settled &&
inv.Metadata.AdditionalData.TryGetValue("ticketIds", out var ticketIds))
{
await SetTicketTailorTicketResult(appId, result, ticketIds.Values<string>());
}
else if (inv.Status.ToModernStatus() == InvoiceStatus.Settled)
else if (inv.Status == InvoiceStatus.Settled)
{
await _ticketTailorService.CheckAndIssueTicket(inv.Id);
}

View File

@@ -12,7 +12,7 @@ namespace BTCPayServer.Plugins.TicketTailor
{
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{
new() {Identifier = nameof(BTCPayServer), Condition = ">=1.12.0"}
new() {Identifier = nameof(BTCPayServer), Condition = ">=2.0.0"}
};
public override void Execute(IServiceCollection applicationBuilder)

View File

@@ -101,8 +101,7 @@ public class TicketTailorService : EventHostedServiceBase, IWebhookProvider
!new[]
{
InvoiceStatus.Settled, InvoiceStatus.Expired, InvoiceStatus.Invalid
}.Contains(invoiceEvent.Invoice.GetInvoiceState().Status
.ToModernStatus()):
}.Contains(invoiceEvent.Invoice.GetInvoiceState().Status):
return;
case InvoiceEvent invoiceEvent:
@@ -157,7 +156,7 @@ public class TicketTailorService : EventHostedServiceBase, IWebhookProvider
return;
}
if (new[] {InvoiceStatus.Invalid, InvoiceStatus.Expired}.Contains(invoice.Status.ToModernStatus()))
if (new[] {InvoiceStatus.Invalid, InvoiceStatus.Expired}.Contains(invoice.Status))
{
if (invoice.Metadata.AdditionalData.TryGetValue("holdId", out var jHoldIdx) &&
@@ -178,7 +177,7 @@ public class TicketTailorService : EventHostedServiceBase, IWebhookProvider
return;
}
if (invoice.Status.ToModernStatus() != InvoiceStatus.Settled)
if (invoice.Status != InvoiceStatus.Settled)
{
return;
}

View File

@@ -13,7 +13,7 @@
<PropertyGroup>
<Product>Coinjoin</Product>
<Description>Allows you to integrate your btcpayserver store with coinjoins.</Description>
<Version>1.0.100</Version>
<Version>1.0.101</Version>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>

View File

@@ -11,7 +11,9 @@ using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.Payments;
using BTCPayServer.Payments.PayJoin;
using BTCPayServer.Payouts;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets;
using LinqKit;
@@ -37,12 +39,14 @@ using WalletWasabi.WabiSabi.Backend.Rounds;
using WalletWasabi.WabiSabi.Client;
using WalletWasabi.Wallets;
using LogLevel = WalletWasabi.Logging.LogLevel;
using SecureRandom = WalletWasabi.Crypto.Randomness.SecureRandom;
namespace BTCPayServer.Plugins.Wabisabi;
public class BTCPayWallet : IWallet, IDestinationProvider
{
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private readonly WalletRepository _walletRepository;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly BitcoinLikePayoutHandler _bitcoinLikePayoutHandler;
@@ -56,6 +60,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
public static readonly BlockchainAnalyzer BlockchainAnalyzer = new();
public BTCPayWallet(
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
WalletRepository walletRepository,
BTCPayNetworkProvider btcPayNetworkProvider,
BitcoinLikePayoutHandler bitcoinLikePayoutHandler,
@@ -75,6 +80,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
WalletId = new WalletWasabi.Wallets.WalletId(new Guid(SHA256.HashData(Encoding.UTF8.GetBytes(storeId)).Take(16)
.ToArray()));
KeyChain = keyChain;
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_walletRepository = walletRepository;
_btcPayNetworkProvider = btcPayNetworkProvider;
_bitcoinLikePayoutHandler = bitcoinLikePayoutHandler;
@@ -495,7 +501,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
if (storeIdForutxo != StoreId)
{
var s = await _storeRepository.FindStore(storeIdForutxo);
var scheme = s.GetDerivationSchemeSettings(_btcPayNetworkProvider, "BTC");
var scheme = s.GetDerivationSchemeSettings(_paymentMethodHandlerDictionary, "BTC");
utxoDerivationScheme = scheme.AccountDerivation;
}
@@ -689,7 +695,7 @@ public async Task<IEnumerable<IDestination>> GetNextDestinationsAsync(int count,
try
{
var mixStore = await _storeRepository.FindStore(WabisabiStoreSettings.MixToOtherWallet);
var pm = mixStore.GetDerivationSchemeSettings(_btcPayNetworkProvider, "BTC");
var pm = mixStore.GetDerivationSchemeSettings(_paymentMethodHandlerDictionary, "BTC");
if (pm?.AccountDerivation?.ScriptPubKeyType() == DerivationScheme.ScriptPubKeyType())
@@ -718,20 +724,19 @@ public async Task<IEnumerable<IDestination>> GetNextDestinationsAsync(int count,
{
States = new [] {PayoutState.AwaitingPayment},
Stores = new []{StoreId},
PaymentMethods = new []{"BTC"}
PayoutMethods = new []{ PayoutTypes.CHAIN.GetPayoutMethodId("BTC").ToString()}
})).Select(async data =>
{
var claim = await _bitcoinLikePayoutHandler.ParseClaimDestination(new PaymentMethodId("BTC", BitcoinPaymentType.Instance),
data.Destination, CancellationToken.None);
var payoutBlob = data.GetBlob(_btcPayNetworkJsonSerializerSettings);
var claim = await _bitcoinLikePayoutHandler.ParseClaimDestination(payoutBlob.Destination, CancellationToken.None);
if (!string.IsNullOrEmpty(claim.error) || claim.destination is not IBitcoinLikeClaimDestination bitcoinLikeClaimDestination )
if (!string.IsNullOrEmpty(claim.error) || claim.destination is not IBitcoinLikeClaimDestination bitcoinLikeClaimDestination || data.Amount is null)
{
return null;
}
var payoutBlob = data.GetBlob(_btcPayNetworkJsonSerializerSettings);
var value = new Money(payoutBlob.CryptoAmount.Value, MoneyUnit.BTC);
var value = new Money(data.Amount.Value, MoneyUnit.BTC);
return new PendingPayment()
{
Identifier = data.Id,

View File

@@ -5,6 +5,7 @@ using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets;
using NBitcoin;
@@ -17,16 +18,19 @@ public class WabisabiScriptResolver: WabiSabiConfig.CoordinatorScriptResolver
{
private readonly IHttpClientFactory _httpClientFactory;
private readonly StoreRepository _storeRepository;
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
private readonly BTCPayNetworkProvider _networkProvider;
private readonly BTCPayWalletProvider _walletProvider;
public WabisabiScriptResolver(IHttpClientFactory httpClientFactory,
StoreRepository storeRepository,
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
BTCPayNetworkProvider networkProvider,
BTCPayWalletProvider walletProvider)
{
_httpClientFactory = httpClientFactory;
_storeRepository = storeRepository;
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
_networkProvider = networkProvider;
_walletProvider = walletProvider;
}
@@ -61,7 +65,7 @@ public class WabisabiScriptResolver: WabiSabiConfig.CoordinatorScriptResolver
var store = await _storeRepository.FindStore(value);
var cryptoCode = _networkProvider.GetAll().OfType<BTCPayNetwork>()
.First(payNetwork => payNetwork.NBitcoinNetwork == network);
var dss = store.GetDerivationSchemeSettings(_networkProvider, cryptoCode.CryptoCode);
var dss = store.GetDerivationSchemeSettings(_paymentMethodHandlerDictionary, cryptoCode.CryptoCode);
var w = _walletProvider.GetWallet(cryptoCode.CryptoCode);
var kpi = await w.ReserveAddressAsync(store.Id, dss.AccountDerivation, "wabisabi coordinator");
return kpi.ScriptPubKey;

View File

@@ -1,18 +1,17 @@
@using NBitcoin
@using BTCPayServer.Components.TruncateCenter
@using BTCPayServer.Payments
@using BTCPayServer
@using BTCPayServer.Services
@model BTCPayServer.Plugins.Wabisabi.BTCPayWallet.CoinjoinData.CoinjoinDataCoin
@inject BTCPayNetworkProvider BtcPayNetworkProvider
@inject TransactionLinkProviders TransactionLinkProviders
@{
var op = OutPoint.Parse(Model.Outpoint);
var text = Model.Outpoint;
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
}
<li class="bg-none list-group-item d-flex border-0 d-flex px-1 ">
<div class="truncate-center-id border-0 p-0 d-flex ">
<vc:truncate-center text="@text" link="@TransactionLinkProviders.GetTransactionLink(new PaymentMethodId("BTC", BitcoinPaymentType.Instance), op.Hash.ToString())" classes="truncate-center-id h-100 border-end-0"/>
<vc:truncate-center text="@text" link="@TransactionLinkProviders.GetTransactionLink(pmi, op.Hash.ToString())" classes="truncate-center-id h-100 border-end-0"/>
<div class="card truncate-center-id " style="border-left-style: dashed; min-width: 110px;">
<span class="text-nowrap">@Model.Amount BTC</span>
<span class="text-nowrap">

View File

@@ -1,17 +1,13 @@
@using BTCPayServer
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Components.TruncateCenter
@using BTCPayServer.Payments
@using BTCPayServer.Plugins.Wabisabi
@using BTCPayServer.Services
@using NBitcoin
@using WalletWasabi.Blockchain.Analysis
@model List<BTCPayServer.Plugins.Wabisabi.BTCPayWallet.CoinjoinData>
@inject BTCPayNetworkProvider BtcPayNetworkProvider
@inject TransactionLinkProviders TransactionLinkProviders
@{
var network = BtcPayNetworkProvider.BTC;
var pmi = new PaymentMethodId( network.CryptoCode, PaymentTypes.BTCLike);
var pmi = PaymentTypes.CHAIN.GetPaymentMethodId("BTC");
}
<div class="table-responsive" style=" transform: rotateX(180deg);">

Some files were not shown because too many files have changed in this diff Show More