make prism support opensats

This commit is contained in:
Kukks
2024-02-02 11:20:27 +01:00
parent 1efe97d4e1
commit abafab95f7
19 changed files with 1554 additions and 445 deletions

View File

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

View File

@@ -4,16 +4,12 @@
@using BTCPayServer.HostedServices
@using BTCPayServer.Payments
@using BTCPayServer.PayoutProcessors
@using BTCPayServer.Services.Custodian.Client
@using Microsoft.AspNetCore.Http
@using Microsoft.AspNetCore.Routing
@using Microsoft.Extensions.Logging
@using NBitcoin
@using LightningAddressData = BTCPayServer.Data.LightningAddressData
@using MarkPayoutRequest = BTCPayServer.HostedServices.MarkPayoutRequest
@using System.Collections
@using BTCPayServer.Abstractions.Custodians
@using BTCPayServer.Abstractions.Extensions
@inject IPluginHookService PluginHookService
@inject LightningAddressService LightningAddressService
@inject PayoutProcessorService PayoutProcessorService
@@ -22,8 +18,6 @@
@inject LinkGenerator LinkGenerator
@inject PullPaymentHostedService PullPaymentHostedService
@inject IHttpContextAccessor HttpContextAccessor
@inject CustodianAccountRepository CustodianAccountRepository
@inject IEnumerable<ICustodian> Custodians
@inject ILogger<PrismEdit> Logger
@implements IDisposable
@@ -47,10 +41,6 @@ else
{
<option value="@destination">@destination</option>
}
@foreach (var destination in CustodianDestinations)
{
<option value="@destination.Key">@destination.Value</option>
}
</datalist>
@@ -215,7 +205,6 @@ else
public PaymentMethodId pmichain { get; set; } = new("BTC", PaymentTypes.BTCLike);
public bool NoPayoutProcessors { get; set; }
public Dictionary<string, string> CustodianDestinations { get; set; }
private string PrismEditButtonsFilter { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
@@ -247,34 +236,6 @@ else
await Task.WhenAll(tasks);
Settings = await fetchSettings;
Users = await fetchLnAddresses;
CustodianDestinations = await FetchCustodians();
async Task<Dictionary<string, string>> FetchCustodians()
{
var result = new Dictionary<string, string>();
var custodianConfigs = await CustodianAccountRepository.FindByStoreId(StoreId);
foreach (var custodianConfig in custodianConfigs)
{
var custodian = Custodians.GetCustodianByCode(custodianConfig.CustodianCode);
if(custodian is not ICanDeposit canDeposit)
continue;
foreach (var depositablePaymentMethod in canDeposit.GetDepositablePaymentMethods())
{
var pmi = PaymentMethodId.TryParse(depositablePaymentMethod);
if(pmi is null || pmi.CryptoCode != "BTC")
continue;
var custodianDestination = new CustodianDestinationValidator.CustodianDestination()
{
CustodianId = custodianConfig.Id,
PaymentMethod = pmi.ToString()
};
result.TryAdd(custodianDestination.ToString(), $"{custodianConfig.Name} {pmi.ToPrettyString()} (Custodian)");
}
}
return result;
}
EditContext = new EditContext(Settings);
MessageStore = new ValidationMessageStore(EditContext);

View File

@@ -1,85 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Custodians;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Data;
using BTCPayServer.Payments;
using BTCPayServer.Services.Custodian.Client;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Plugins.Prism;
public class CustodianDestinationValidator : IPluginHookFilter
{
private readonly IServiceProvider _serviceProvider;
public string Hook => "prism-destination-validate";
public CustodianDestinationValidator(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task<object> Execute(object args)
{
var result = new PrismDestinationValidationResult();
if (args is not string args1 || !args1.StartsWith("custodian:")) return args;
try
{
var custodianDestination =
JObject.Parse(args1.Substring("custodian:".Length)).ToObject<CustodianDestination>();
var custodianPaymentMethod = custodianDestination.PaymentMethod is null
? new PaymentMethodId("BTC", PaymentTypes.LightningLike)
: PaymentMethodId.Parse(custodianDestination.PaymentMethod);
result.PaymentMethod = custodianPaymentMethod;
await using var ctx = _serviceProvider.GetService<ApplicationDbContextFactory>().CreateContext();
var custodianAccountData = ctx.CustodianAccount.SingleOrDefault(data => data.Id == custodianDestination.CustodianId);
if (custodianAccountData is null)
{
result.Success = false;
return result;
}
var custdodian = _serviceProvider.GetServices<ICustodian>().GetCustodianByCode(custodianAccountData.CustodianCode);
if (custdodian is null)
{
result.Success = false;
return result;
}
if (custdodian is ICanDeposit canDeposit &&
canDeposit.GetDepositablePaymentMethods() is { } paymentMethods &&
paymentMethods.Any(s => PaymentMethodId.TryParse(s) == custodianPaymentMethod))
{
result.Success = true;
return result;
}
result.Success = false;
return result;
}
catch (Exception e)
{
result.Success = false;
return result;
}
}
public class CustodianDestination
{
public string CustodianId { get; set; }
public string PaymentMethod { get; set; }
override public string ToString()
{
return $"custodian:{JObject.FromObject(this)}";
}
}
}

View File

@@ -1,104 +0,0 @@
using System;
using System.Linq;
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 Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Plugins.Prism;
public class CustodianPrismClaimCreate : IPluginHookFilter
{
private readonly IServiceProvider _serviceProvider;
public string Hook => "prism-claim-create";
public CustodianPrismClaimCreate(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task<object> Execute(object args)
{
if (args is not ClaimRequest claimRequest)
{
return args;
}
if (claimRequest.Destination?.ToString() is not { } args1 || !args1.StartsWith("custodian:")) return args;
try
{
var custodianDestination = JObject.Parse(args1.Substring("custodian:".Length))
.ToObject<CustodianDestinationValidator.CustodianDestination>();
var custodianPaymentMethod = custodianDestination.PaymentMethod is null
? new PaymentMethodId("BTC", PaymentTypes.LightningLike)
: PaymentMethodId.Parse(custodianDestination.PaymentMethod);
await using var ctx = _serviceProvider.GetRequiredService<ApplicationDbContextFactory>().CreateContext();
var custodianAccountData = await ctx.CustodianAccount.SingleOrDefaultAsync(data => data.Id == custodianDestination.CustodianId);
if (custodianAccountData is null)
{
return null;
}
var custdodian = _serviceProvider.GetServices<ICustodian>().GetCustodianByCode(custodianAccountData.CustodianCode);
if (custdodian is null)
{
return null;
}
if (custdodian is not ICanDeposit canDeposit ||
canDeposit.GetDepositablePaymentMethods() is { } paymentMethods &&
paymentMethods.Any(s => PaymentMethodId.TryParse(s) == custodianPaymentMethod))
{
return null;
}
var handler = _serviceProvider.GetServices<IPayoutHandler>().FindPayoutHandler(custodianPaymentMethod);
if (handler is null)
{
return null;
}
var config = custodianAccountData.GetBlob();
config["depositAddressConfig"] = JToken.FromObject(new
{
amount = claimRequest.Value
});
var depositAddressAsync =
await canDeposit.GetDepositAddressAsync(custodianPaymentMethod.ToString(),config, CancellationToken.None);
if (depositAddressAsync.Address is null)
{
return null;
}
var claimDestination = await handler.ParseClaimDestination(custodianPaymentMethod,
depositAddressAsync.Address, CancellationToken.None);
if (claimDestination.destination is null)
{
return null;
}
claimRequest.Destination = claimDestination.destination;
claimRequest.PaymentMethodId = custodianPaymentMethod;
return claimRequest;
}
catch (Exception e)
{
return null;
}
}
}

View File

@@ -0,0 +1,96 @@
using System;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Payments;
using Microsoft.Extensions.DependencyInjection;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Plugins.Prism;
public class OpenSatsDestinationValidator : IPluginHookFilter
{
private readonly IServiceProvider _serviceProvider;
public string Hook => "prism-destination-validate";
public OpenSatsDestinationValidator(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task<object> Execute(object args)
{
var result = new PrismDestinationValidationResult();
if (args is not string args1 || !args1.StartsWith("opensats")) return args;
try
{
var parts = args1.Split(":", StringSplitOptions.RemoveEmptyEntries);
var project = "opensats";
var paymentMethod = new PaymentMethodId("BTC", PaymentTypes.LightningLike);
if (parts.Length > 1)
{
project = parts[1];
}
if (parts.Length > 2)
{
paymentMethod = PaymentMethodId.Parse(parts[2]);
}
var handler = _serviceProvider.GetServices<IPayoutHandler>().FindPayoutHandler(paymentMethod);
if (handler is null)
{
result.Success = false;
}
var httpClientFactory = _serviceProvider.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient("opensats");
var content = new StringContent(JObject.FromObject(new
{
btcpay = project,
name = "kukks <3 you"
}).ToString(), Encoding.UTF8, "application/json");
var xResult = await httpClient.PostAsync("https://opensats.org/api/btcpay",content).ConfigureAwait(false);
var rawInvoice = JObject.Parse(await xResult.Content.ReadAsStringAsync().ConfigureAwait(false));
var invoiceUrl = $"{rawInvoice.Value<string>("checkoutLink").TrimEnd('/')}/{paymentMethod}/status";
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);
if (claimDestination.destination is null)
{
result.Success = false;
}
result.Success = true;
result.PaymentMethod = paymentMethod;
return result;
}
catch (Exception e)
{
result.Success = false;
return result;
}
}
public class CustodianDestination
{
public string CustodianId { get; set; }
public string PaymentMethod { get; set; }
override public string ToString()
{
return $"custodian:{JObject.FromObject(this)}";
}
}
}

View File

@@ -0,0 +1,98 @@
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 Microsoft.Extensions.DependencyInjection;
using NBitcoin;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Plugins.Prism;
public class OpenSatsPrismClaimCreate : IPluginHookFilter
{
private readonly IServiceProvider _serviceProvider;
public string Hook => "prism-claim-create";
public OpenSatsPrismClaimCreate(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task<object> Execute(object args)
{
if (args is not ClaimRequest claimRequest)
{
return args;
}
if (claimRequest.Destination?.ToString() is not { } args1 || !args1.StartsWith("opensats")) return args;
try
{
var parts = args1.Split(":", StringSplitOptions.RemoveEmptyEntries);
var project = "opensats";
var paymentMethod = new PaymentMethodId("BTC", PaymentTypes.LightningLike);
if (parts.Length > 1)
{
project = parts[1];
}
if (parts.Length > 2)
{
paymentMethod = PaymentMethodId.Parse(parts[2]);
}
var handler = _serviceProvider.GetServices<IPayoutHandler>().FindPayoutHandler(paymentMethod);
if (handler is null)
{
return null;
}
var httpClientFactory = _serviceProvider.GetRequiredService<IHttpClientFactory>();
var httpClient = httpClientFactory.CreateClient("opensats");
var content = new StringContent(JObject.FromObject(new
{
project_name = project,
project_slug = project,
name = "kukks <3 you"
}).ToString(), Encoding.UTF8, "application/json");
var result = await httpClient.PostAsync("https://opensats.org/api/btcpay",content).ConfigureAwait(false);
var rawInvoice = JObject.Parse(await result.Content.ReadAsStringAsync().ConfigureAwait(false));
var invoiceUrl = $"{rawInvoice.Value<string>("checkoutLink").TrimEnd('/')}/{paymentMethod}/status";
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);
if (claimDestination.destination is null)
{
return null;
}
claimRequest.Destination = claimDestination.destination;
claimRequest.PaymentMethodId = paymentMethod;
return claimRequest;
}
catch (Exception)
{
return null;
}
}
}

View File

@@ -22,12 +22,12 @@ public class PrismPlugin : BaseBTCPayServerPlugin
"store-integrations-nav"));
applicationBuilder.AddSingleton<SatBreaker>();
applicationBuilder.AddHostedService(provider => provider.GetRequiredService<SatBreaker>());
applicationBuilder.AddSingleton<IPluginHookFilter, CustodianDestinationValidator>();
applicationBuilder.AddSingleton<IPluginHookFilter, OpenSatsDestinationValidator>();
applicationBuilder.AddSingleton<IPluginHookFilter, LNURLPrismDestinationValidator>();
applicationBuilder.AddSingleton<IPluginHookFilter, OnChainPrismDestinationValidator>();
applicationBuilder.AddSingleton<IPluginHookFilter, LNURLPrismClaimCreate>();
applicationBuilder.AddSingleton<IPluginHookFilter, OnChainPrismClaimCreate>();
applicationBuilder.AddSingleton<IPluginHookFilter, CustodianPrismClaimCreate>();
applicationBuilder.AddSingleton<IPluginHookFilter, OpenSatsPrismClaimCreate>();
base.Execute(applicationBuilder);
}