diff --git a/Plugins/BTCPayServer.Plugins.Prism/PrismController.cs b/Plugins/BTCPayServer.Plugins.Prism/PrismController.cs index 7039282..51f2237 100644 --- a/Plugins/BTCPayServer.Plugins.Prism/PrismController.cs +++ b/Plugins/BTCPayServer.Plugins.Prism/PrismController.cs @@ -1,20 +1,16 @@ #nullable enable using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; +using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Models; using BTCPayServer.Client; using BTCPayServer.Filters; -using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Cors; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Caching.Memory; namespace BTCPayServer.Plugins.Prism; @@ -24,10 +20,12 @@ namespace BTCPayServer.Plugins.Prism; public class PrismController : Controller { private readonly SatBreaker _satBreaker; + private readonly IPluginHookService _pluginHookService; - public PrismController( SatBreaker satBreaker) + public PrismController( SatBreaker satBreaker, IPluginHookService pluginHookService) { _satBreaker = satBreaker; + _pluginHookService = pluginHookService; } [HttpGet] @@ -40,7 +38,8 @@ public class PrismController : Controller [HttpPost] public async Task Edit(string storeId, PrismSettings settings, string command) { - for (int i = 0; i < settings.Splits?.Length; i++) + + for (var i = 0; i < settings.Splits?.Length; i++) { var prism = settings.Splits[i]; if (string.IsNullOrEmpty(prism.Source)) @@ -77,6 +76,7 @@ public class PrismController : Controller try { + LNURL.LNURL.ExtractUriFromInternetIdentifier(dest); } catch (Exception e) @@ -87,8 +87,9 @@ public class PrismController : Controller } catch (Exception exception) { - - ModelState.AddModelError($"Splits[{i}].Destinations[{j}].Destination", "Destination is not a valid LN address or LNURL"); + var result = await _pluginHookService.ApplyFilter("prism-destination-validate", dest); + if(result is not true) + ModelState.AddModelError($"Splits[{i}].Destinations[{j}].Destination", "Destination is not a valid LN address or LNURL"); } } } diff --git a/Plugins/BTCPayServer.Plugins.Prism/PrismPlugin.cs b/Plugins/BTCPayServer.Plugins.Prism/PrismPlugin.cs index b8e2930..df1dd9f 100644 --- a/Plugins/BTCPayServer.Plugins.Prism/PrismPlugin.cs +++ b/Plugins/BTCPayServer.Plugins.Prism/PrismPlugin.cs @@ -11,7 +11,7 @@ public class PrismPlugin : BaseBTCPayServerPlugin { public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = { - new() {Identifier = nameof(BTCPayServer), Condition = ">=1.9.0"} + new() {Identifier = nameof(BTCPayServer), Condition = ">=1.10.0"} }; public override void Execute(IServiceCollection applicationBuilder) diff --git a/Plugins/BTCPayServer.Plugins.Prism/SatBreaker.cs b/Plugins/BTCPayServer.Plugins.Prism/SatBreaker.cs index 7af602b..bb375c7 100644 --- a/Plugins/BTCPayServer.Plugins.Prism/SatBreaker.cs +++ b/Plugins/BTCPayServer.Plugins.Prism/SatBreaker.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; +using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Client.Models; using BTCPayServer.Configuration; using BTCPayServer.Data; @@ -17,6 +18,7 @@ using BTCPayServer.Services.Stores; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NBitcoin; +using LightningAddressData = BTCPayServer.Data.LightningAddressData; namespace BTCPayServer.Plugins.Prism { @@ -34,6 +36,7 @@ namespace BTCPayServer.Plugins.Prism private readonly LightningClientFactoryService _lightningClientFactoryService; private readonly IOptions _lightningNetworkOptions; private readonly BTCPayNetworkJsonSerializerSettings _btcPayNetworkJsonSerializerSettings; + private readonly IPluginHookService _pluginHookService; private Dictionary _prismSettings; public SatBreaker(StoreRepository storeRepository, @@ -45,7 +48,8 @@ namespace BTCPayServer.Plugins.Prism BTCPayNetworkProvider btcPayNetworkProvider, LightningClientFactoryService lightningClientFactoryService, IOptions lightningNetworkOptions, - BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings) : base(eventAggregator, logger) + BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings, + IPluginHookService pluginHookService) : base(eventAggregator, logger) { _storeRepository = storeRepository; _logger = logger; @@ -56,6 +60,7 @@ namespace BTCPayServer.Plugins.Prism _lightningClientFactoryService = lightningClientFactoryService; _lightningNetworkOptions = lightningNetworkOptions; _btcPayNetworkJsonSerializerSettings = btcPayNetworkJsonSerializerSettings; + _pluginHookService = pluginHookService; } public override async Task StartAsync(CancellationToken cancellationToken) @@ -286,22 +291,43 @@ namespace BTCPayServer.Plugins.Prism new[] {InvoiceEventCode.Completed, InvoiceEventCode.MarkedCompleted}.Contains( invoiceEvent.EventCode)) { + + + if (!_prismSettings.TryGetValue(invoiceEvent.Invoice.StoreId, out var prismSettings) || !prismSettings.Enabled) + { + return; + } + + var catchAllPrism = prismSettings.Splits.FirstOrDefault(split => split.Source == "*"); + Split prism = null; + LightningAddressData address = null; var pm = invoiceEvent.Invoice.GetPaymentMethod(new PaymentMethodId("BTC", LNURLPayPaymentType.Instance)); - var pmd = pm?.GetPaymentMethodDetails() as LNURLPayPaymentMethodDetails; - if (string.IsNullOrEmpty(pmd?.ConsumedLightningAddress)) + var pmdRaw = pm?.GetPaymentMethodDetails(); + var pmd = pmdRaw as LNURLPayPaymentMethodDetails; + if (string.IsNullOrEmpty(pmd?.ConsumedLightningAddress) && catchAllPrism is null) { return; - } - - var address = - await _lightningAddressService.ResolveByAddress(pmd.ConsumedLightningAddress.Split("@")[0]); - if (address is null || !_prismSettings.TryGetValue(address.StoreDataId, out var prismSettings) || - !prismSettings.Enabled) + }else if (!string.IsNullOrEmpty(pmd?.ConsumedLightningAddress)) { - return; - } + address = await _lightningAddressService.ResolveByAddress(pmd.ConsumedLightningAddress.Split("@")[0]); + if (address is null) + { + return; + } - var splits = prismSettings.Splits.FirstOrDefault(s => s.Source == address.Username)?.Destinations; + prism = prismSettings.Splits.FirstOrDefault(s => s.Source.Equals(address.Username, StringComparison.InvariantCultureIgnoreCase)); + } + else + { + //do not run prism on payments not LN based. + if (invoiceEvent.Invoice.GetPayments("BTC", true).All(entity => + { + var pmi = entity.GetPaymentMethodId(); + return pmi.CryptoCode == "BTC" && (pmi.PaymentType == LightningPaymentType.Instance || pmi.PaymentType == LNURLPayPaymentType.Instance); + })) + prism = catchAllPrism; + } + var splits = prism?.Destinations; if (splits?.Any() is not true) { return; @@ -357,10 +383,23 @@ namespace BTCPayServer.Plugins.Prism { continue; } + IClaimDestination dest = null; + var dest2 = await _pluginHookService.ApplyFilter("prism-claim-destination", destination); + dest = dest2 switch + { + IClaimDestination claimDestination => claimDestination, + string destStr when !string.IsNullOrEmpty(destStr) => new LNURLPayClaimDestinaton(destStr), + _ => null + }; + + if (dest is null) + { + continue; + } var payout = await _pullPaymentHostedService.Claim(new ClaimRequest() { - Destination = new LNURLPayClaimDestinaton(destination), + Destination = dest, PreApprove = true, StoreId = storeId, PaymentMethodId = new PaymentMethodId("BTC", LightningPaymentType.Instance), diff --git a/Plugins/BTCPayServer.Plugins.Prism/Views/Prism/Edit.cshtml b/Plugins/BTCPayServer.Plugins.Prism/Views/Prism/Edit.cshtml index e7b9498..be5a050 100644 --- a/Plugins/BTCPayServer.Plugins.Prism/Views/Prism/Edit.cshtml +++ b/Plugins/BTCPayServer.Plugins.Prism/Views/Prism/Edit.cshtml @@ -34,8 +34,8 @@ @if (!users.Any()) { } @@ -53,7 +53,7 @@

- The prism plugin allows automated value splits for your lightning payments. You can set up multiple prisms, each with their own source (which is a lightning address username) and destinations (which are other lightning addresses or lnurls). The plugin will automatically credit the configured percentage of the payment to the destination (while also making sure there is 2% reserved to cater for fees, don't worry, once the lightning node tells us the exact fee amount, we credit/debit the balance after the payment), and once the configured threshold is reached, a payout will be created. Then, a payout processor will run at intervals and process the payout. + The prism plugin allows automated value splits for your lightning payments. You can set up multiple prisms, each with their own source (which is a lightning address username, or use * as a catch-all for all invoices settled through Lightning, excluding ones which Prism can handle explicitly) and destinations (which are other lightning addresses or lnurls). The plugin will automatically credit the configured percentage of the payment to the destination (while also making sure there is 2% reserved to cater for fees, don't worry, once the lightning node tells us the exact fee amount, we credit/debit the balance after the payment), and once the configured threshold is reached, a payout will be created. Then, a payout processor will run at intervals and process the payout.

@@ -326,4 +326,5 @@ document.addEventListener("DOMContentLoaded", ()=>{ }) } }); - \ No newline at end of file + + \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.SideShift/BTCPayServer.Plugins.SideShift.csproj b/Plugins/BTCPayServer.Plugins.SideShift/BTCPayServer.Plugins.SideShift.csproj index 63e08a3..06cc4a0 100644 --- a/Plugins/BTCPayServer.Plugins.SideShift/BTCPayServer.Plugins.SideShift.csproj +++ b/Plugins/BTCPayServer.Plugins.SideShift/BTCPayServer.Plugins.SideShift.csproj @@ -29,10 +29,10 @@ - - + + - + diff --git a/Plugins/BTCPayServer.Plugins.SideShift/SideShiftController.cs b/Plugins/BTCPayServer.Plugins.SideShift/SideShiftController.cs index 15bb439..f400d12 100644 --- a/Plugins/BTCPayServer.Plugins.SideShift/SideShiftController.cs +++ b/Plugins/BTCPayServer.Plugins.SideShift/SideShiftController.cs @@ -1,10 +1,23 @@ using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Threading; using System.Threading.Tasks; -using BTCPayServer.Abstractions.Constants; +using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Client; +using BTCPayServer.Client.Models; +using BTCPayServer.Data; +using BTCPayServer.HostedServices; +using BTCPayServer.Payments; +using BTCPayServer.Services; +using BTCPayServer.Services.Stores; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using NBitcoin; using Newtonsoft.Json.Linq; +using AuthenticationSchemes = BTCPayServer.Abstractions.Constants.AuthenticationSchemes; namespace BTCPayServer.Plugins.SideShift { @@ -15,11 +28,29 @@ namespace BTCPayServer.Plugins.SideShift { private readonly BTCPayServerClient _btcPayServerClient; private readonly SideShiftService _sideShiftService; + private readonly IHttpClientFactory _httpClientFactory; + private readonly IEnumerable _payoutHandlers; + private readonly PullPaymentHostedService _pullPaymentHostedService; + private readonly StoreRepository _storeRepository; + private readonly BTCPayNetworkJsonSerializerSettings _serializerSettings; + private readonly ApplicationDbContextFactory _dbContextFactory; - public SideShiftController(BTCPayServerClient btcPayServerClient, SideShiftService sideShiftService) + public SideShiftController(BTCPayServerClient btcPayServerClient, + SideShiftService sideShiftService, + IHttpClientFactory httpClientFactory, + IEnumerable payoutHandlers, + PullPaymentHostedService pullPaymentHostedService, + StoreRepository storeRepository, + BTCPayNetworkJsonSerializerSettings serializerSettings, ApplicationDbContextFactory dbContextFactory) { _btcPayServerClient = btcPayServerClient; _sideShiftService = sideShiftService; + _httpClientFactory = httpClientFactory; + _payoutHandlers = payoutHandlers; + _pullPaymentHostedService = pullPaymentHostedService; + _storeRepository = storeRepository; + _serializerSettings = serializerSettings; + _dbContextFactory = dbContextFactory; } [HttpGet("")] @@ -78,5 +109,229 @@ namespace BTCPayServer.Plugins.SideShift return View(vm); } } + + + + [HttpPost("~/plugins/sidehift/{pullPaymentId}/payouts")] + [AllowAnonymous] + public async Task CreatePayout(string pullPaymentId, + [FromBody] CreateSideShiftPayoutRequest request) + { + IPayoutHandler handler = null; + if (string.IsNullOrEmpty(request.ShiftCurrency)) + { + ModelState.AddModelError(nameof(request.ShiftCurrency), "ShiftCurrency must be specified"); + } + + if (string.IsNullOrEmpty(request.ShiftNetwork)) + { + ModelState.AddModelError(nameof(request.ShiftNetwork), "ShiftNetwork must be specified"); + } + + if (request.Amount is null) + { + ModelState.AddModelError(nameof(request.Amount), "Amount must be specified"); + } + + if (!PaymentMethodId.TryParse(request.PaymentMethod, out var pmi)) + { + ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method"); + } + else + { + handler = _payoutHandlers.FindPayoutHandler(pmi); + if (handler == null) + { + ModelState.AddModelError(nameof(request.PaymentMethod), "Invalid payment method"); + } + } + + if (!ModelState.IsValid) + { + return this.CreateValidationError(ModelState); + } + + var pp = await + _pullPaymentHostedService.GetPullPayment(pullPaymentId, false); + var ppBlob = pp?.GetBlob(); + if (ppBlob is null) + { + return NotFound(); + } + + var ip = HttpContext.Connection.RemoteIpAddress; + + var client = _httpClientFactory.CreateClient("sideshift"); + if (ip is not null && !ip.IsLocal()) + client.DefaultRequestHeaders.Add("x-user-ip", ip.ToString()); + // + // var quoteResponse = await client.PostAsJsonAsync("https://sideshift.ai/api/v2/quotes", new + // { + // depositCoin = pmi.CryptoCode, + // depositNetwork = pmi.PaymentType == LightningPaymentType.Instance ? "lightning" : null, + // settleCoin = request.ShiftCurrency, + // settleNetwork = request.ShiftNetwork, + // depositAmount = request.Amount.ToString(), + // affiliateId = "qg0OrfHJV" + // } + // ); + // quoteResponse.EnsureSuccessStatusCode(); + // var quote = await quoteResponse.Content.ReadAsAsync(); + // var shiftResponse = await client.PostAsJsonAsync("https://sideshift.ai/api/v2/shifts/fixed", new + // { + // settleAddress = request.Destination, + // settleMemo = request.Memo, + // quoteId = quote.id, + // affiliateId = "qg0OrfHJV" + // } + // ); + // shiftResponse.EnsureSuccessStatusCode(); + // var shift = await shiftResponse.Content.ReadAsAsync(); + + 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, + settleCoin = request.ShiftCurrency, + settleNetwork = request.ShiftNetwork, + } + ); + if (!shiftResponse.IsSuccessStatusCode) + { + var error = JObject.Parse(await shiftResponse.Content.ReadAsStringAsync()); + ModelState.AddModelError("",error["error"]["message"].Value()); + + return this.CreateValidationError(ModelState); + } + var shift = await shiftResponse.Content.ReadAsAsync(); + + + var destination = + await handler.ParseAndValidateClaimDestination(pmi, shift.depositAddress, ppBlob, + CancellationToken.None); + + var claim = await _pullPaymentHostedService.Claim(new ClaimRequest() + { + PullPaymentId = pullPaymentId, + Destination = destination.destination, + PaymentMethodId = pmi, + Value = request.Amount + }); + if (claim.Result == ClaimRequest.ClaimResult.Ok) + { + await using var ctx = _dbContextFactory.CreateContext(); + ppBlob.Description += $"The payout of {claim.PayoutData.Destination} will be forwarded to SideShift.ai for further conversion. Please go to the order page for support."; + pp.SetBlob(ppBlob); + ctx.Attach(pp).State = EntityState.Modified; + await ctx.SaveChangesAsync(); + + } + return HandleClaimResult(claim); + } + + + private IActionResult HandleClaimResult(ClaimRequest.ClaimResponse result) + { + switch (result.Result) + { + case ClaimRequest.ClaimResult.Ok: + break; + case ClaimRequest.ClaimResult.Duplicate: + return this.CreateAPIError("duplicate-destination", ClaimRequest.GetErrorMessage(result.Result)); + case ClaimRequest.ClaimResult.Expired: + return this.CreateAPIError("expired", ClaimRequest.GetErrorMessage(result.Result)); + case ClaimRequest.ClaimResult.NotStarted: + return this.CreateAPIError("not-started", ClaimRequest.GetErrorMessage(result.Result)); + case ClaimRequest.ClaimResult.Archived: + return this.CreateAPIError("archived", ClaimRequest.GetErrorMessage(result.Result)); + case ClaimRequest.ClaimResult.Overdraft: + return this.CreateAPIError("overdraft", ClaimRequest.GetErrorMessage(result.Result)); + case ClaimRequest.ClaimResult.AmountTooLow: + return this.CreateAPIError("amount-too-low", ClaimRequest.GetErrorMessage(result.Result)); + case ClaimRequest.ClaimResult.PaymentMethodNotSupported: + return this.CreateAPIError("payment-method-not-supported", + ClaimRequest.GetErrorMessage(result.Result)); + default: + throw new NotSupportedException("Unsupported ClaimResult"); + } + + return Ok(ToModel(result.PayoutData)); + } + + private Client.Models.PayoutData ToModel(Data.PayoutData p) + { + var blob = p.GetBlob(_serializerSettings); + var model = new Client.Models.PayoutData + { + Id = p.Id, + PullPaymentId = p.PullPaymentDataId, + Date = p.Date, + Amount = blob.Amount, + PaymentMethodAmount = blob.CryptoAmount, + Revision = blob.Revision, + State = p.State, + Destination = blob.Destination, + PaymentMethod = p.PaymentMethodId, + CryptoCode = p.GetPaymentMethodId().CryptoCode, + PaymentProof = p.GetProofBlobJson() + }; + return model; + } + + public class CreateSideShiftPayoutThroughStoreRequest : CreatePayoutThroughStoreRequest + { + public string Memo { get; set; } + public string ShiftCurrency { get; set; } + public string ShiftNetwork { get; set; } + } + + public class CreateSideShiftPayoutRequest : CreatePayoutRequest + { + public string Memo { get; set; } + public string ShiftCurrency { get; set; } + public string ShiftNetwork { get; set; } + } + + public class QuoteResponse + { + public string id { get; set; } + public string createdAt { get; set; } + public string depositCoin { get; set; } + public string settleCoin { get; set; } + public string depositNetwork { get; set; } + public string settleNetwork { get; set; } + public string expiresAt { get; set; } + public string depositAmount { get; set; } + public string settleAmount { get; set; } + public string rate { get; set; } + public string affiliateId { get; set; } + } + + public class ShiftResponse + { + public string id { get; set; } + public string createdAt { get; set; } + public string depositCoin { get; set; } + public string settleCoin { get; set; } + public string depositNetwork { get; set; } + public string settleNetwork { get; set; } + public string depositAddress { get; set; } + public string settleAddress { get; set; } + public string depositMin { get; set; } + public string depositMax { get; set; } + public string refundAddress { get; set; } + public string type { get; set; } + public string quoteId { get; set; } + public string depositAmount { get; set; } + public string settleAmount { get; set; } + public string expiresAt { get; set; } + public string status { get; set; } + public string updatedAt { get; set; } + public string rate { get; set; } + public string averageShiftSeconds { get; set; } + } } -} +} \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.SideShift/SideShiftPlugin.cs b/Plugins/BTCPayServer.Plugins.SideShift/SideShiftPlugin.cs index c66bc83..566ff50 100644 --- a/Plugins/BTCPayServer.Plugins.SideShift/SideShiftPlugin.cs +++ b/Plugins/BTCPayServer.Plugins.SideShift/SideShiftPlugin.cs @@ -1,7 +1,14 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Services; +using BTCPayServer.Data; +using BTCPayServer.Data.Payouts.LightningLike; +using BTCPayServer.Lightning; using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json.Linq; namespace BTCPayServer.Plugins.SideShift { @@ -9,13 +16,16 @@ namespace BTCPayServer.Plugins.SideShift { public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = { - new() { Identifier = nameof(BTCPayServer), Condition = ">=1.7.4" } + new() {Identifier = nameof(BTCPayServer), Condition = ">=1.7.4"} }; + public override void Execute(IServiceCollection applicationBuilder) { applicationBuilder.AddSingleton(); applicationBuilder.AddSingleton(new UIExtension("SideShift/SideShiftNav", "store-integrations-nav")); + applicationBuilder.AddSingleton(new UIExtension("SideShift/PullPaymentViewInsert", + "pullpayment-view")); applicationBuilder.AddSingleton(new UIExtension("SideShift/StoreIntegrationSideShiftOption", "store-integrations-list")); // Checkout v2 @@ -34,7 +44,109 @@ namespace BTCPayServer.Plugins.SideShift "checkout-lightning-post-tabs")); applicationBuilder.AddSingleton(new UIExtension("SideShift/CheckoutEnd", "checkout-end")); + applicationBuilder.AddSingleton(new UIExtension("SideShift/PrismEnhance", + "prism-edit")); + applicationBuilder.AddSingleton(); + applicationBuilder.AddSingleton(); base.Execute(applicationBuilder); } } -} + + + public class PrismSideshiftDestination + { + public string ShiftCoin { get; set; } + public string ShiftNetwork { get; set; } + public string ShiftDestination { get; set; } + public string ShiftMemo { get; set; } + + public bool Valid() + { + return !string.IsNullOrEmpty(ShiftCoin) && !string.IsNullOrEmpty(ShiftNetwork) && + !string.IsNullOrEmpty(ShiftDestination); + } + } + + public class PrismDestinationValidate : IPluginHookFilter + { + public string Hook => "prism-destination-validate"; + public async Task Execute(object args) + { + if (args is not string args1 || !args1.StartsWith("sideshift:")) return args; + var json = JObject.Parse(args1.Substring("sideshift:".Length)).ToObject(); + return json.Valid(); + } + + } + + public class PrismClaimDestination : IPluginHookFilter + { + private readonly IHttpClientFactory _httpClientFactory; + private readonly BTCPayNetworkProvider _networkProvider; + public string Hook => "prism-claim-destination"; + + public PrismClaimDestination(IHttpClientFactory httpClientFactory, BTCPayNetworkProvider networkProvider) + { + _httpClientFactory = httpClientFactory; + _networkProvider = networkProvider; + } + public async Task Execute(object args) + { + var network = _networkProvider.GetNetwork("BTC"); + if (args is not string s || network is null) + { + return Task.FromResult(args); + } + if (args is not string args1 || !args1.StartsWith("sideshift:")) return args; + var request = JObject.Parse(args1.Substring("sideshift:".Length)).ToObject(); + 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 = "lightning", + settleCoin = request.ShiftCoin, + settleNetwork = request.ShiftNetwork, + } + ); + if (!shiftResponse.IsSuccessStatusCode) + { + return null; + } + var shift = await shiftResponse.Content.ReadAsAsync(); + try + { + LNURL.LNURL.Parse(shift.depositAddress, out var lnurl); + return new LNURLPayClaimDestinaton(shift.depositAddress); + } + catch (Exception e) + { + if (BOLT11PaymentRequest.TryParse(shift.depositAddress, out var bolt11, network.NBitcoinNetwork)) + { + return new BoltInvoiceClaimDestination(shift.depositAddress, bolt11); + } + } + + return null; + + } + } + + public class SideShiftAvailableCoin + { + public string coin { get; set; } + public string[] networks { get; set; } + public string name { get; set; } + public bool hasMemo { get; set; } + public JToken fixedOnly { get; set; } + public JToken variableOnly { get; set; } + } +} \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.SideShift/Views/Shared/SideShift/PrismEnhance.cshtml b/Plugins/BTCPayServer.Plugins.SideShift/Views/Shared/SideShift/PrismEnhance.cshtml new file mode 100644 index 0000000..2dd2827 --- /dev/null +++ b/Plugins/BTCPayServer.Plugins.SideShift/Views/Shared/SideShift/PrismEnhance.cshtml @@ -0,0 +1,149 @@ +@using System.Net.Http +@using BTCPayServer.Abstractions.TagHelpers +@using BTCPayServer.Plugins.SideShift +@using Microsoft.AspNetCore.Mvc.TagHelpers +@using Newtonsoft.Json +@using Newtonsoft.Json.Linq +@inject IHttpClientFactory HttpClientFactory +@{ + var client = HttpClientFactory.CreateClient("sideshift"); + var request = new HttpRequestMessage(HttpMethod.Get, "https://sideshift.ai/api/v2/coins"); + var response = await client.SendAsync(request); + if (!response.IsSuccessStatusCode) + { + return; + } + var coins = await response.Content.ReadAsStringAsync().ContinueWith(t => JsonConvert.DeserializeObject>(t.Result)); + var availableCoins = coins.SelectMany(coin => coin.networks.Select(s => (Coin: coin, Network: s))) + .Where(tuple => (tuple.Coin.fixedOnly.Type == JTokenType.Boolean && !tuple.Coin.fixedOnly.Value()) || ( + tuple.Coin.fixedOnly is JArray varOnlyArray && varOnlyArray.All(v => v.Value() != tuple.Network))).ToList(); + +} + + + + \ No newline at end of file diff --git a/Plugins/BTCPayServer.Plugins.SideShift/Views/Shared/SideShift/PullPaymentViewInsert.cshtml b/Plugins/BTCPayServer.Plugins.SideShift/Views/Shared/SideShift/PullPaymentViewInsert.cshtml new file mode 100644 index 0000000..324cfe4 --- /dev/null +++ b/Plugins/BTCPayServer.Plugins.SideShift/Views/Shared/SideShift/PullPaymentViewInsert.cshtml @@ -0,0 +1,186 @@ +@using System.Net.Http +@using BTCPayServer.Plugins.SideShift +@using Newtonsoft.Json +@using Newtonsoft.Json.Linq +@model BTCPayServer.Models.ViewPullPaymentModel +@inject IHttpClientFactory HttpClientFactory +@inject SideShiftService SideShiftService +@{ + var ss = await SideShiftService.GetSideShiftForStore(Model.StoreId); + if (ss?.Enabled is not true) + { + return; + } + var client = HttpClientFactory.CreateClient("sideshift"); + var request = new HttpRequestMessage(HttpMethod.Get, "https://sideshift.ai/api/v2/coins"); + var response = await client.SendAsync(request); + if (!response.IsSuccessStatusCode) + { + return; + } + var coins = await response.Content.ReadAsStringAsync().ContinueWith(t => JsonConvert.DeserializeObject>(t.Result)); + var availableCoins = coins.SelectMany(coin => coin.networks.Select(s => (Coin: coin, Network: s))) + .Where(tuple => (tuple.Coin.fixedOnly.Type == JTokenType.Boolean && !tuple.Coin.fixedOnly.Value()) || ( + tuple.Coin.fixedOnly is JArray varOnlyArray && varOnlyArray.All(v => v.Value() != tuple.Network))).ToList(); + + var potentialPaymentMethods = Model.PaymentMethods;//.Where(id => id.CryptoCode.Equals(Model.Currency, StringComparison.OrdinalIgnoreCase)).ToList(); + if (Model.IsPending && potentialPaymentMethods.Any() && availableCoins.Any()) + { + + + } +} \ No newline at end of file diff --git a/submodules/btcpayserver b/submodules/btcpayserver index fcd50a3..3fb5e85 160000 --- a/submodules/btcpayserver +++ b/submodules/btcpayserver @@ -1 +1 @@ -Subproject commit fcd50a3f6fb2ae6f525c7e0477ed477219c84038 +Subproject commit 3fb5e853699ba8f1885d6f19f108b2cb75acef80