mirror of
https://github.com/aljazceru/BTCPayServerPlugins.git
synced 2025-12-17 07:34:24 +01:00
bringin poc
This commit is contained in:
@@ -7,8 +7,8 @@
|
|||||||
<!-- -->
|
<!-- -->
|
||||||
<!-- Plugin specific properties -->
|
<!-- Plugin specific properties -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Product>Dynamic Reports</Product>
|
<Product>Bringin</Product>
|
||||||
<Description>Allows you to create custom reports using SQL.</Description>
|
<Description>Euro Offramp</Description>
|
||||||
<Version>1.0.0</Version>
|
<Version>1.0.0</Version>
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
@@ -33,4 +33,9 @@
|
|||||||
<EmbeddedResource Include="Resources\**" />
|
<EmbeddedResource Include="Resources\**" />
|
||||||
<ProjectReference Include="..\..\submodules\btcpayserver\BTCPayServer\BTCPayServer.csproj" />
|
<ProjectReference Include="..\..\submodules\btcpayserver\BTCPayServer\BTCPayServer.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Views\Bringin\" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -43,40 +43,57 @@ public class BringinClient
|
|||||||
public async Task<CreateOrderResponse> PlaceOrder(CreateOrderRequest request)
|
public async Task<CreateOrderResponse> PlaceOrder(CreateOrderRequest request)
|
||||||
{
|
{
|
||||||
var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json");
|
var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json");
|
||||||
var response = await HttpClient.PostAsync($"/api/v0/offramp/order/lightning", content);
|
var response = await HttpClient.PostAsync($"/api/v0/offramp/order", content);
|
||||||
var responseContent = await response.Content.ReadAsStringAsync();
|
var responseContent = await response.Content.ReadAsStringAsync();
|
||||||
if (response.IsSuccessStatusCode) return JObject.Parse(responseContent).ToObject<CreateOrderResponse>();
|
if (response.IsSuccessStatusCode) return JObject.Parse(responseContent).ToObject<CreateOrderResponse>();
|
||||||
var error = JObject.Parse(responseContent).ToObject<BringinErrorResponse>();
|
var error = JObject.Parse(responseContent).ToObject<BringinErrorResponse>();
|
||||||
throw new BringinException(error);
|
throw new BringinException(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<RateResponse> GetOrderInfo(GetOrderRequest request)
|
public async Task<decimal> GetFiatBalance(string currency = "EUR")
|
||||||
{
|
{
|
||||||
var content = new StringContent(JsonConvert.SerializeObject(request), Encoding.UTF8, "application/json");
|
var content = new StringContent(JsonConvert.SerializeObject(new
|
||||||
var response = await HttpClient.PostAsync($"/api/v0/offramp/order/lightning", content);
|
{
|
||||||
|
currency
|
||||||
|
}), Encoding.UTF8, "application/json");
|
||||||
|
var response = await HttpClient.PostAsync($"/api/v0/user/get-balance/fiat", content);
|
||||||
var responseContent = await response.Content.ReadAsStringAsync();
|
var responseContent = await response.Content.ReadAsStringAsync();
|
||||||
if (response.IsSuccessStatusCode) return JObject.Parse(responseContent).ToObject<RateResponse>();
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
var balance = JObject.Parse(responseContent).ToObject<BalanceResponse>();
|
||||||
|
return balance.Balance / 100m; //response is in cents
|
||||||
|
}
|
||||||
var error = JObject.Parse(responseContent).ToObject<BringinErrorResponse>();
|
var error = JObject.Parse(responseContent).ToObject<BringinErrorResponse>();
|
||||||
throw new BringinException(error);
|
throw new BringinException(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GetOrderResponse
|
public class BalanceResponse
|
||||||
{
|
{
|
||||||
public string OrderId { get; set; }
|
[JsonProperty("balance")]
|
||||||
public string Status { get; set; }
|
|
||||||
public string SubType { get; set; }
|
|
||||||
|
|
||||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||||
public decimal SourceAmount { get; set; }
|
public decimal Balance {
|
||||||
|
get;
|
||||||
public string SourceCurrency { get; set; }
|
set;
|
||||||
|
}
|
||||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
|
||||||
public decimal DestinationAmount { get; set; }
|
|
||||||
|
|
||||||
public string DestinationCurrency { get; set; }
|
|
||||||
public BringinPrice BringinPrice { get; set; }
|
|
||||||
}
|
}
|
||||||
|
//
|
||||||
|
// public class GetOrderResponse
|
||||||
|
// {
|
||||||
|
// public string OrderId { get; set; }
|
||||||
|
// public string Status { get; set; }
|
||||||
|
// public string SubType { get; set; }
|
||||||
|
//
|
||||||
|
// [JsonConverter(typeof(NumericStringJsonConverter))]
|
||||||
|
// public decimal SourceAmount { get; set; }
|
||||||
|
//
|
||||||
|
// public string SourceCurrency { get; set; }
|
||||||
|
//
|
||||||
|
// [JsonConverter(typeof(NumericStringJsonConverter))]
|
||||||
|
// public decimal DestinationAmount { get; set; }
|
||||||
|
//
|
||||||
|
// public string DestinationCurrency { get; set; }
|
||||||
|
// public BringinPrice BringinPrice { get; set; }
|
||||||
|
// }
|
||||||
|
|
||||||
public class BringinPrice
|
public class BringinPrice
|
||||||
{
|
{
|
||||||
@@ -86,32 +103,31 @@ public class BringinClient
|
|||||||
public string Currency { get; set; }
|
public string Currency { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GetOrderRequest
|
|
||||||
{
|
|
||||||
public string UserId { get; set; }
|
|
||||||
public string OrderId { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class CreateOrderResponse
|
public class CreateOrderResponse
|
||||||
{
|
{
|
||||||
|
[JsonProperty("id")]
|
||||||
public string Id { get; set; }
|
public string Id { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("amount")]
|
||||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||||
public decimal Amount { get; set; }
|
public decimal Amount { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("invoice")]
|
||||||
public string Invoice { get; set; }
|
public string Invoice { get; set; }
|
||||||
|
|
||||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
|
||||||
[JsonProperty("expiresAt")]
|
[JsonProperty("expiresAt")]
|
||||||
public string Expiry { get; set; }
|
public long Expiry { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CreateOrderRequest
|
public class CreateOrderRequest
|
||||||
{
|
{ [JsonProperty("sourceAmount")]
|
||||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||||
public decimal SourceAmount { get; set; }
|
public decimal SourceAmount { get; set; }
|
||||||
|
|
||||||
[JsonProperty("ipAddress")] public string IP { get; set; }
|
[JsonProperty("ipAddress")] public string IP { get; set; }
|
||||||
|
[JsonProperty("paymentMethod")]
|
||||||
|
public string PaymentMethod { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class RateResponse
|
public class RateResponse
|
||||||
@@ -119,8 +135,8 @@ public class BringinClient
|
|||||||
public string Ticker { get; set; }
|
public string Ticker { get; set; }
|
||||||
public string Currency { get; set; }
|
public string Currency { get; set; }
|
||||||
|
|
||||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
|
||||||
public string Timestamp { get; set; }
|
public long Timestamp { get; set; }
|
||||||
|
|
||||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||||
public decimal Price { get; set; }
|
public decimal Price { get; set; }
|
||||||
@@ -136,14 +152,4 @@ public class BringinClient
|
|||||||
public string ErrorCode { get; set; }
|
public string ErrorCode { get; set; }
|
||||||
public JObject ErrorDetails { get; set; }
|
public JObject ErrorDetails { get; set; }
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public class BringinException : Exception
|
|
||||||
{
|
|
||||||
private readonly BringinClient.BringinErrorResponse _error;
|
|
||||||
|
|
||||||
public BringinException(BringinClient.BringinErrorResponse error)
|
|
||||||
{
|
|
||||||
_error = error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
176
Plugins/BTCPayServer.Plugins.Bringin/BringinCustodian.cs
Normal file
176
Plugins/BTCPayServer.Plugins.Bringin/BringinCustodian.cs
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Abstractions.Custodians;
|
||||||
|
using BTCPayServer.Abstractions.Form;
|
||||||
|
using BTCPayServer.Client.Models;
|
||||||
|
using BTCPayServer.Forms;
|
||||||
|
using BTCPayServer.Payments;
|
||||||
|
using NBitcoin;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Plugins.Bringin;
|
||||||
|
|
||||||
|
public class BringinApiKeyFormComponentProvider:FormComponentProviderBase
|
||||||
|
{
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
public override string View => "Bringin/ApiKeyElement";
|
||||||
|
|
||||||
|
public BringinApiKeyFormComponentProvider(IHttpClientFactory httpClientFactory)
|
||||||
|
{
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Validate(Form form, Field field)
|
||||||
|
{
|
||||||
|
if (field.Required)
|
||||||
|
{
|
||||||
|
ValidateField<RequiredAttribute>(field);
|
||||||
|
}
|
||||||
|
if(field.ValidationErrors.Any())
|
||||||
|
return;
|
||||||
|
var httpClient = _httpClientFactory.CreateClient("bringin");
|
||||||
|
httpClient.BaseAddress = new Uri("https://dev.bringin.xyz");
|
||||||
|
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("api-key", GetValue(form, field));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var userId = new BringinClient(GetValue(form, field), httpClient).GetUserId().GetAwaiter().GetResult();
|
||||||
|
if(userId is null)
|
||||||
|
field.ValidationErrors.Add("Invalid API Key");
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
field.ValidationErrors.Add("Invalid API Key");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Register(Dictionary<string, IFormComponentProvider> typeToComponentProvider)
|
||||||
|
{
|
||||||
|
typeToComponentProvider.Add("bringin-apikey", this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class BringinCustodian : ICustodian, ICanDeposit
|
||||||
|
{
|
||||||
|
private readonly IHttpClientFactory _httpClientFactory;
|
||||||
|
private readonly FormDataService _formDataService;
|
||||||
|
public string Code => "bringin";
|
||||||
|
|
||||||
|
public string Name => "Bringin";
|
||||||
|
|
||||||
|
public BringinCustodian(IHttpClientFactory httpClientFactory, FormDataService formDataService)
|
||||||
|
{
|
||||||
|
_httpClientFactory = httpClientFactory;
|
||||||
|
_formDataService = formDataService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Dictionary<string, decimal>> GetAssetBalancesAsync(JObject config,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var bringinClient = FromConfig(config);
|
||||||
|
if (bringinClient is null)
|
||||||
|
throw new BadConfigException(Array.Empty<string>());
|
||||||
|
|
||||||
|
var balance = await bringinClient.GetFiatBalance();
|
||||||
|
return new Dictionary<string, decimal>()
|
||||||
|
{
|
||||||
|
{"EUR", balance}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Form> GetConfigForm(JObject config, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
|
||||||
|
var f = new Form();
|
||||||
|
|
||||||
|
var fieldset = Field.CreateFieldset();
|
||||||
|
fieldset.Label = "Connection details";
|
||||||
|
fieldset.Fields.Add(new Field()
|
||||||
|
{
|
||||||
|
Name = "apiKey",
|
||||||
|
Label = "API Key",
|
||||||
|
Type = "bringin-apikey",
|
||||||
|
Value = config["apiKey"]?.Value<string>(),
|
||||||
|
Required = true,
|
||||||
|
HelpText = "Enter your Bringin API Key which can be obtained from <a href=\"https://dev-app.bringin.xyz\" target=\"_blank\">here</a>"
|
||||||
|
});
|
||||||
|
// fieldset.Fields.Add(new Field()
|
||||||
|
// {
|
||||||
|
// Name = "server",
|
||||||
|
// Label = "Bringin Server (optional)",
|
||||||
|
// Type = "password",
|
||||||
|
// Value = config["server"]?.Value<string>(),
|
||||||
|
// Required = false,
|
||||||
|
// OriginalValue = "https://dev.bringin.xyz",
|
||||||
|
// HelpText = "Enter the Bringin server URL. This is optional and defaults to https://dev.bringin.xyz"
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
f.Fields.Add(fieldset);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<DepositAddressData> GetDepositAddressAsync(string paymentMethod, JObject config,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var amount = config["depositAddressConfig"]?.Value<decimal>();
|
||||||
|
|
||||||
|
var bringinClient = FromConfig(config);
|
||||||
|
if (bringinClient is null)
|
||||||
|
throw new Exception("Invalid API Key");
|
||||||
|
if (amount is null or <= 0)
|
||||||
|
{
|
||||||
|
var rate = await bringinClient.GetRate();
|
||||||
|
//rate.bringinRate is the price for 1 BTC in EUR
|
||||||
|
//100eur = 1/rate.bringinRate BTC
|
||||||
|
amount = 100m / rate.BringinPrice;
|
||||||
|
}
|
||||||
|
// var rate = await bringinClient.GetRate();
|
||||||
|
var host = await Dns.GetHostEntryAsync(Dns.GetHostName(), cancellationToken);
|
||||||
|
var ipToUse = host.AddressList.FirstOrDefault(address => address.AddressFamily == AddressFamily.InterNetwork)?.ToString();
|
||||||
|
var request = new BringinClient.CreateOrderRequest()
|
||||||
|
{
|
||||||
|
SourceAmount = Money.Coins(amount.Value).Satoshi,
|
||||||
|
IP = ipToUse,
|
||||||
|
PaymentMethod = "LIGHTNING"
|
||||||
|
};
|
||||||
|
var order = await bringinClient.PlaceOrder(request);
|
||||||
|
return new DepositAddressData()
|
||||||
|
{
|
||||||
|
Address = order.Invoice
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public string[] GetDepositablePaymentMethods()
|
||||||
|
{
|
||||||
|
return new[] {new PaymentMethodId("BTC", LightningPaymentType.Instance).ToString()};
|
||||||
|
}
|
||||||
|
|
||||||
|
private BringinClient? FromConfig(JObject config)
|
||||||
|
{
|
||||||
|
Uri backend = new Uri("https://dev.bringin.xyz");
|
||||||
|
if (config.TryGetValue("apiKey", out var apiKey))
|
||||||
|
{
|
||||||
|
// if (config.TryGetValue("server", out var serverToken) && serverToken.Value<string>() is { } server &&
|
||||||
|
// !string.IsNullOrEmpty(server))
|
||||||
|
// {
|
||||||
|
// backend = new Uri(server);
|
||||||
|
// }
|
||||||
|
|
||||||
|
var httpClient = _httpClientFactory.CreateClient("bringin");
|
||||||
|
httpClient.BaseAddress = backend;
|
||||||
|
|
||||||
|
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("api-key", apiKey.Value<string>());
|
||||||
|
return new BringinClient(apiKey.Value<string>(), httpClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
Plugins/BTCPayServer.Plugins.Bringin/BringinException.cs
Normal file
13
Plugins/BTCPayServer.Plugins.Bringin/BringinException.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Plugins.Bringin;
|
||||||
|
|
||||||
|
public class BringinException : Exception
|
||||||
|
{
|
||||||
|
private readonly BringinClient.BringinErrorResponse _error;
|
||||||
|
|
||||||
|
public BringinException(BringinClient.BringinErrorResponse error)
|
||||||
|
{
|
||||||
|
_error = error;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
Plugins/BTCPayServer.Plugins.Bringin/BringinPlugin.cs
Normal file
26
Plugins/BTCPayServer.Plugins.Bringin/BringinPlugin.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using BTCPayServer.Abstractions.Contracts;
|
||||||
|
using BTCPayServer.Abstractions.Custodians;
|
||||||
|
using BTCPayServer.Abstractions.Extensions;
|
||||||
|
using BTCPayServer.Abstractions.Models;
|
||||||
|
using BTCPayServer.Forms;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Plugins.Bringin;
|
||||||
|
|
||||||
|
public class BringinPlugin : BaseBTCPayServerPlugin
|
||||||
|
{
|
||||||
|
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
|
||||||
|
{
|
||||||
|
new() {Identifier = nameof(BTCPayServer), Condition = ">=1.12.0"}
|
||||||
|
};
|
||||||
|
|
||||||
|
public override void Execute(IServiceCollection applicationBuilder)
|
||||||
|
{
|
||||||
|
applicationBuilder.AddStartupTask<CustodianEnablerTask>();
|
||||||
|
applicationBuilder.AddSingleton<ICustodian, BringinCustodian>();
|
||||||
|
applicationBuilder.AddSingleton<IFormComponentProvider, BringinApiKeyFormComponentProvider>();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
24
Plugins/BTCPayServer.Plugins.Bringin/CustodianEnablerTask.cs
Normal file
24
Plugins/BTCPayServer.Plugins.Bringin/CustodianEnablerTask.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Abstractions.Contracts;
|
||||||
|
using BTCPayServer.Services;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Plugins.Bringin;
|
||||||
|
|
||||||
|
public class CustodianEnablerTask: IStartupTask
|
||||||
|
{
|
||||||
|
private readonly SettingsRepository _settingsRepository;
|
||||||
|
|
||||||
|
public CustodianEnablerTask(SettingsRepository settingsRepository)
|
||||||
|
{
|
||||||
|
_settingsRepository = settingsRepository;
|
||||||
|
}
|
||||||
|
public async Task ExecuteAsync(CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var policySettings = await _settingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
|
||||||
|
if(policySettings.Experimental)
|
||||||
|
return;
|
||||||
|
policySettings.Experimental = true;
|
||||||
|
await _settingsRepository.UpdateSetting(policySettings);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
@model BTCPayServer.Abstractions.Form.Field
|
||||||
|
@{
|
||||||
|
var isInvalid = ViewContext.ModelState[Model.Name]?.ValidationState is Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid;
|
||||||
|
var errors = isInvalid ? ViewContext.ModelState[Model.Name].Errors : null;
|
||||||
|
}
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label" for="@Model.Name"@(Model.Required ? " data-required" : "")>
|
||||||
|
@Safe.Raw(Model.Label)
|
||||||
|
</label>
|
||||||
|
<input id="@Model.Name" type="password" class="form-control @(errors is null ? "" : "is-invalid")"
|
||||||
|
name="@Model.Name" value="@Model.Value" data-val="true" readonly="@Model.Constant"
|
||||||
|
@if (!string.IsNullOrEmpty(Model.HelpText))
|
||||||
|
{
|
||||||
|
@Safe.Raw($" aria-describedby=\"HelpText-{Model.Name}\"")
|
||||||
|
}
|
||||||
|
@if (Model.Required)
|
||||||
|
{
|
||||||
|
@Safe.Raw($" data-val-required=\"{Model.Label} is required.\" required")
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<span class="text-danger" data-valmsg-for="@Model.Name" data-valmsg-replace="true">@(isInvalid && errors.Any() ? errors.First().ErrorMessage : string.Empty)</span>
|
||||||
|
@if (!string.IsNullOrEmpty(Model.HelpText))
|
||||||
|
{
|
||||||
|
<div id="@($"HelpText-{Model.Name}")" class="form-text">
|
||||||
|
@Safe.Raw(Model.HelpText)
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
5
Plugins/BTCPayServer.Plugins.Bringin/_ViewImports.cshtml
Normal file
5
Plugins/BTCPayServer.Plugins.Bringin/_ViewImports.cshtml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
@using BTCPayServer.Abstractions.Services
|
||||||
|
@inject Safe Safe
|
||||||
|
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
||||||
|
@addTagHelper *, BTCPayServer
|
||||||
|
@addTagHelper *, BTCPayServer.Abstractions
|
||||||
Reference in New Issue
Block a user