using System; using System.Linq; using System.Net.Http; using System.Threading; using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Extensions; using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Services; using BTCPayServer.Common; using BTCPayServer.Configuration; using BTCPayServer.Controllers; using BTCPayServer.Data; using BTCPayServer.Data.Payouts.LightningLike; using BTCPayServer.HostedServices; using BTCPayServer.Lightning; using BTCPayServer.Lightning.CLightning; using BTCPayServer.Lightning.Eclair; using BTCPayServer.Lightning.Phoenixd; using BTCPayServer.Lightning.LND; using BTCPayServer.Logging; using BTCPayServer.PaymentRequest; using BTCPayServer.Payments; using BTCPayServer.Payments.Bitcoin; using BTCPayServer.Payments.Lightning; using BTCPayServer.Payments.PayJoin; using BTCPayServer.PayoutProcessors; using BTCPayServer.Plugins; using BTCPayServer.Rating; using BTCPayServer.Rating.Providers; using BTCPayServer.Security; using BTCPayServer.Security.Greenfield; using BTCPayServer.Services; using BTCPayServer.Services.Apps; using BTCPayServer.Services.Fees; using BTCPayServer.Services.Invoices; using BTCPayServer.Services.Labels; using BTCPayServer.Plugins.Emails.Services; using BTCPayServer.Services.Notifications; using BTCPayServer.Services.Notifications.Blobs; using BTCPayServer.Services.PaymentRequests; using BTCPayServer.Services.Rates; using BTCPayServer.Services.Stores; using BTCPayServer.Services.Wallets; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using NBitcoin; using NBitpayClient; using NBXplorer.DerivationStrategy; using Newtonsoft.Json; using BTCPayServer.Services.Reporting; using BTCPayServer.Services.WalletFileParsing; using BTCPayServer.Payments.LNURLPay; using System.Collections.Generic; using System.Threading.Tasks; using BTCPayServer.Abstractions.Constants; using BTCPayServer.Client; using BTCPayServer.Lightning.LNDhub; using BTCPayServer.Payouts; using BTCPayServer.Plugins.Bitcoin; using ExchangeSharp; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.WebUtilities; using Microsoft.EntityFrameworkCore; namespace BTCPayServer.Hosting { public static class BTCPayServerServices { public static IServiceCollection RegisterJsonConverter(this IServiceCollection services, Func create) { services.AddSingleton((s) => new JsonConverterRegistration(create)); return services; } public static IServiceCollection AddBTCPayServer(this IServiceCollection services, IConfiguration configuration, Logs logs) { services.TryAddScoped(); services.TryAddSingleton(); services.TryAddSingleton(); services.AddSingleton(o => o.GetRequiredService>().Value); services.AddSingleton(o => o.GetRequiredService>().Value.SerializerSettings); services.AddSingleton, ApplicationDbContextFactory>((provider) => provider.GetRequiredService()); services.AddSingleton>(); services.AddDbContext((provider, o) => { var factory = provider.GetRequiredService(); factory.ConfigureBuilder(o); }); services.AddHttpClient(); services.AddHttpClient(nameof(ExplorerClientProvider), httpClient => { httpClient.Timeout = Timeout.InfiniteTimeSpan; }); services.AddSingleton(); services.AddSingleton(logs); services.AddSingleton(); services.AddPayJoinServices(); services.AddScoped(); services.TryAddSingleton(); services.TryAddSingleton(provider => provider.GetService()); services.TryAddSingleton(provider => provider.GetService()); services.TryAddSingleton(); services.AddSingleton(provider => provider.GetRequiredService()); services.AddSingleton(); services.TryAddSingleton(); services.AddSingleton>(_ => new CLightningConnectionStringHandler()); services.AddSingleton>(client => new EclairConnectionStringHandler(client)); services.AddSingleton>(client => new PhoenixdConnectionStringHandler(client)); services.AddSingleton>(client => new LndConnectionStringHandler(client)); services.AddSingleton>(client => new LndHubConnectionStringHandler(client)); services.TryAddSingleton(); services.AddHttpClient(LightningClientFactoryService.OnionNamedClient) .ConfigurePrimaryHttpMessageHandler(); services.TryAddSingleton(); services.TryAddSingleton(o => o.GetRequiredService>().Value); services.AddStartupTask(); services.AddStartupTask(); // services.AddSettingsAccessor(); services.AddSettingsAccessor(); services.AddSettingsAccessor(); // services.AddSingleton(); AddOnchainWalletParsers(services); services.AddStartupTask(); services.AddStartupTask(); services.TryAddSingleton(); services.AddSingleton(); services.AddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.AddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.AddSingleton(); services.AddOptions().Configure((options) => { options.LoadArgs(configuration, logs); }); services.AddOptions().Configure((options) => { options.Configure(configuration); }); services.AddOptions().Configure>((options, datadirs) => { var postgresConnectionString = configuration["postgres"]; if (!string.IsNullOrEmpty(postgresConnectionString)) { options.ConnectionString = postgresConnectionString; } else { throw new InvalidOperationException("No database option was configured."); } }); services.AddOptions().Configure((options, btcPayNetworkProvider) => { foreach (BTCPayNetwork btcPayNetwork in btcPayNetworkProvider.GetAll().OfType()) { NBXplorerConnectionSetting setting = new NBXplorerConnectionSetting { CryptoCode = btcPayNetwork.CryptoCode, ExplorerUri = configuration.GetOrDefault( $"{btcPayNetwork.CryptoCode}.explorer.url", btcPayNetwork.NBXplorerNetwork.DefaultSettings.DefaultUrl), CookieFile = configuration.GetOrDefault( $"{btcPayNetwork.CryptoCode}.explorer.cookiefile", btcPayNetwork.NBXplorerNetwork.DefaultSettings.DefaultCookieFile) }; options.NBXplorerConnectionSettings.Add(setting); options.ConnectionString = configuration.GetOrDefault("explorer.postgres", null); } }); services.AddOptions() .Configure((options, btcPayNetworkProvider, lightningClientFactoryService) => { foreach (var net in btcPayNetworkProvider.GetAll().OfType()) { var lightning = configuration.GetOrDefault($"{net.CryptoCode}.lightning", string.Empty); if (lightning.Length != 0) { string error = null; ILightningClient lightningClient = null; try { lightningClient = lightningClientFactoryService.Create(lightning, net); } catch (Exception e) { error = e.Message; } if (error is not null) { logs.Configuration.LogWarning($"Invalid setting {net.CryptoCode}.lightning, " + Environment.NewLine + $"If you have a c-lightning server use: 'type=clightning;server=/root/.lightning/lightning-rpc', " + Environment.NewLine + $"If you have a lightning charge server: 'type=charge;server=https://charge.example.com;api-token=yourapitoken'" + Environment.NewLine + $"If you have a lnd server: 'type=lnd-rest;server=https://lnd:lnd@lnd.example.com;macaroon=abf239...;certthumbprint=2abdf302...'" + Environment.NewLine + $" lnd server: 'type=lnd-rest;server=https://lnd:lnd@lnd.example.com;macaroonfilepath=/root/.lnd/admin.macaroon;certthumbprint=2abdf302...'" + Environment.NewLine + $"If you have an eclair server: 'type=eclair;server=http://eclair.com:4570;password=eclairpassword;bitcoin-host=bitcoind:37393;bitcoin-auth=bitcoinrpcuser:bitcoinrpcpassword" + Environment.NewLine + $" eclair server: 'type=eclair;server=http://eclair.com:4570;password=eclairpassword;bitcoin-host=bitcoind:37393" + Environment.NewLine + $"If you have a phoenixd server: 'type=phoenixd;server=http://phoenixd:9740/;password=phoenixdpassword'" + Environment.NewLine + $"Error: {error}" + Environment.NewLine + "This service will not be exposed through BTCPay Server"); } else { if (lightningClient.ToString() != lightning) { logs.Configuration.LogWarning( $"Setting {net.CryptoCode}.lightning is a deprecated format ({lightning}), it will work now, but please replace it for future versions with '{lightningClient}'"); } options.InternalLightningByCryptoCode.Add(net.CryptoCode, lightningClient); } } } }); services.AddOptions().Configure((options, btcPayNetworkProvider) => { foreach (var net in btcPayNetworkProvider.GetAll().OfType()) { options.ExternalServices.Load(net.CryptoCode, configuration); } options.ExternalServices.LoadNonCryptoServices(configuration); var services = configuration.GetOrDefault("externalservices", null); if (services != null) { foreach (var service in services.Split(new[] { ';', ',' }, StringSplitOptions.RemoveEmptyEntries) .Select(p => (p, SeparatorIndex: p.IndexOf(':', StringComparison.OrdinalIgnoreCase))) .Where(p => p.SeparatorIndex != -1) .Select(p => (Name: p.p.Substring(0, p.SeparatorIndex), Link: p.p.Substring(p.SeparatorIndex + 1)))) { if (Uri.TryCreate(service.Link, UriKind.RelativeOrAbsolute, out var uri)) options.OtherExternalServices.AddOrReplace(service.Name, uri); } } }); services.TryAddSingleton(); services.AddExceptionHandler(); services.TryAddSingleton(); services.AddSingleton(); services.AddSingleton(provider => provider.GetService()); services.TryAddTransient(); services.TryAddTransient(); services.TryAddSingleton(o => { var htmlSanitizer = new Ganss.Xss.HtmlSanitizer(); htmlSanitizer.RemovingAtRule += (sender, args) => { }; htmlSanitizer.RemovingTag += (sender, args) => { if (args.Tag.TagName.Equals("img", StringComparison.InvariantCultureIgnoreCase)) { if (!args.Tag.ClassList.Contains("img-fluid")) { args.Tag.ClassList.Add("img-fluid"); } args.Cancel = true; } }; htmlSanitizer.RemovingAttribute += (sender, args) => { if (args.Tag.TagName.Equals("img", StringComparison.InvariantCultureIgnoreCase) && args.Attribute.Name.Equals("src", StringComparison.InvariantCultureIgnoreCase) && args.Reason == Ganss.Xss.RemoveReason.NotAllowedUrlValue) { args.Cancel = true; } }; htmlSanitizer.RemovingStyle += (sender, args) => { args.Cancel = true; }; htmlSanitizer.AllowedAttributes.Add("class"); htmlSanitizer.AllowedTags.Add("iframe"); htmlSanitizer.AllowedTags.Add("style"); htmlSanitizer.AllowedTags.Remove("img"); htmlSanitizer.AllowedAttributes.Add("webkitallowfullscreen"); htmlSanitizer.AllowedAttributes.Add("allowfullscreen"); htmlSanitizer.AllowedSchemes.Add("mailto"); htmlSanitizer.AllowedSchemes.Add("bitcoin"); htmlSanitizer.AllowedSchemes.Add("lightning"); return htmlSanitizer; }); services.AddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.AddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.AddSingleton(); services.AddScheduledTask(TimeSpan.FromMinutes(10)); // PendingTransactionWebhookProvider webhooks registered in WebhookExtensions services.TryAddSingleton(); services.AddSingleton(provider => provider.GetService()); RegisterCurrencyData(services); services.AddScheduledTask(TimeSpan.FromMinutes(3.0)); services.AddSingleton(f => f.GetRequiredService()); services.Configure((o) => { o.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(WalletId))); o.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(DerivationStrategyBase))); }); services.AddUIExtension("checkout-end", "Bitcoin/BitcoinLikeMethodCheckout"); services.AddUIExtension("checkout-end", "Lightning/LightningLikeMethodCheckout"); services.AddUIExtension("store-invoices-payments", "Bitcoin/ViewBitcoinLikePaymentData"); services.AddUIExtension("store-invoices-payments", "Lightning/ViewLightningLikePaymentData"); services.AddSingleton(); services.AddSingleton(o => o.GetRequiredService()); services.AddSingleton(); services.AddSingleton(o => o.GetRequiredService()); services.AddSingleton(); services.AddScheduledTask(TimeSpan.FromDays(1)); services.AddSearchResultItemProvider(); services.AddReportProvider(); services.AddReportProvider(); services.AddReportProvider(); services.AddReportProvider(); services.AddReportProvider(); services.AddReportProvider(); services.AddReportProvider(); services.AddSingleton>(o => o.GetRequiredService>().ToDictionary(o => o.PaymentMethodId, o => o)); services.AddSingleton>(o => o.GetRequiredService>().ToDictionary(o => o.PaymentMethodId, o => o)); services.AddSingleton>(o => o.GetRequiredService>().ToDictionary(o => o.PaymentMethodId, o => o)); services.AddHttpClient(LightningLikePayoutHandler.LightningLikePayoutHandlerOnionNamedClient) .ConfigurePrimaryHttpMessageHandler(); services.AddSingleton(); services.AddSingleton(o => o.GetRequiredService()); services.AddSingleton(); services.AddUIExtension("store-integrations-nav", "LNURL/LightningAddressNav"); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); RegisterExchangeRecommendations(services); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddScheduledDbScript("Invoice Cleanup", """ WITH useless_invoices AS ( SELECT * FROM "Invoices" WHERE "Status" = 'Expired' AND "ExceptionStatus" = '' AND "Created" < @now - INTERVAL '6 months' ORDER BY "Created" LIMIT 1000 ), deleted_invoices AS ( DELETE FROM "Invoices" WHERE "Id" IN ( SELECT "Id" FROM useless_invoices) RETURNING * ) SELECT COUNT(*) FROM deleted_invoices; """); services.AddMigration("20260402_idx_invoices_expired_cleanup", """ CREATE INDEX IF NOT EXISTS idx_invoices_expired_cleanup ON "Invoices" ("Created", "Id") WHERE "Status" = 'Expired' AND "ExceptionStatus" = ''; """); services.AddSingleton(); services.AddSingleton(s => s.GetRequiredService()); services.AddScheduledTask(TimeSpan.FromSeconds(30)); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(s => s.GetRequiredService()); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.TryAddSingleton(); services.AddSingleton(x => x.GetRequiredService()); RegisterRateSources(services); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddScoped(); services.AddTransient(); services.AddTransient(); services.AddSingleton(); // Add application services. services.AddSingleton(); services.AddSingleton(); services.AddPayoutProcesors(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); foreach (var routeDataToStoreId in new BuiltInPermissionScopeProvider.RouteValueToStoreIdQuery[] { new("appId", "SELECT \"StoreDataId\" FROM \"Apps\" WHERE \"Id\" = @id"), new("payReqId", "SELECT \"StoreDataId\" FROM \"PaymentRequests\" WHERE \"Id\" = @id"), new("paymentRequestId", "SELECT \"StoreDataId\" FROM \"PaymentRequests\" WHERE \"Id\" = @id"), new("pullPaymentId", "SELECT \"StoreId\" FROM \"PullPayments\" WHERE \"Id\" = @id"), new("payoutId", "SELECT \"StoreDataId\" FROM \"Payouts\" WHERE \"Id\" = @id"), new("invoiceId", "SELECT \"StoreDataId\" FROM \"Invoices\" WHERE \"Id\" = @id"), }) services.AddSingleton(routeDataToStoreId); services.AddScoped(); services.AddTransient(); services.AddBtcPayServerAuthenticationSchemes(); services.AddAuthorization(); services.AddSingleton, PermissionAuthorizationOptionsSetup>(); services.AddCors(options => { options.AddPolicy(CorsPolicies.All, p => p.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin()); }); services.AddRateLimits(); services.AddSingleton(); services.SkipModelValidation(); services.SkipModelValidation(); if (configuration.GetOrDefault("cheatmode", false)) { services.AddSingleton(); services.AddSingleton(o => o.GetRequiredService()); } services.AddPolicyDefinitions(new[] { new PolicyDefinition( Policies.CanViewInvoices, new PermissionDisplay("View invoices", "Allows viewing invoices."), new PermissionDisplay("View invoices", "Allows viewing invoices on the selected stores.")), new PolicyDefinition( Policies.CanCreateInvoice, new PermissionDisplay("Create an invoice", "Allows creating new invoices."), new PermissionDisplay("Create an invoice", "Allows creating new invoices on the selected stores.")), new PolicyDefinition( Policies.CanModifyInvoices, new PermissionDisplay("Modify invoices", "Allows viewing and modifying invoices."), new PermissionDisplay("Modify invoices", "Allows viewing and modifying invoices on the selected stores."), new[] { Policies.CanViewInvoices, Policies.CanCreateInvoice, Policies.CanCreateLightningInvoiceInStore }), new PolicyDefinition( Policies.CanModifyWebhooks, new PermissionDisplay("Modify stores webhooks", "Allows modifying the webhooks of all your stores."), new PermissionDisplay("Modify selected stores' webhooks", "Allows modifying the webhooks of the selected stores.")), new PolicyDefinition( Policies.CanSendStoreEmail, new PermissionDisplay("Send store emails", "Allows sending emails on behalf of all your stores."), new PermissionDisplay("Send selected stores' emails", "Allows sending emails on behalf of the selected stores.")), new PolicyDefinition( Policies.CanModifyServerSettings, new PermissionDisplay("Manage your server", "Grants total control on the server settings of your server."), includedPermissions: new[] { Policies.CanUseInternalLightningNode, Policies.CanManageUsers }), new PolicyDefinition( Policies.CanModifyStoreSettings, new PermissionDisplay("Modify your stores", "Allows managing invoices on all your stores and modify their settings."), new PermissionDisplay("Manage selected stores", "Allows managing invoices on the selected stores and modify their settings."), new[] { Policies.CanManagePullPayments, Policies.CanModifyInvoices, Policies.CanViewStoreSettings, Policies.CanModifyWebhooks, Policies.CanModifyPaymentRequests, Policies.CanManagePayouts, Policies.CanUseLightningNodeInStore, Policies.CanSendStoreEmail }), new PolicyDefinition( Policies.CanViewStoreSettings, new PermissionDisplay("View your stores", "Allows viewing stores settings."), new PermissionDisplay("View your stores", "Allows viewing the selected stores' settings."), new[] { Policies.CanViewInvoices, Policies.CanViewPaymentRequests, Policies.CanViewReports, Policies.CanViewPullPayments, Policies.CanViewPayouts }), new PolicyDefinition( Policies.CanViewReports, new PermissionDisplay("View your reports", "Allows viewing reports."), new PermissionDisplay("View your selected stores' reports", "Allows viewing the selected stores' reports.")), new PolicyDefinition( Policies.CanViewPaymentRequests, new PermissionDisplay("View your payment requests", "Allows viewing payment requests."), new PermissionDisplay("View your payment requests", "Allows viewing the selected stores' payment requests.")), new PolicyDefinition( Policies.CanModifyPaymentRequests, new PermissionDisplay("Modify your payment requests", "Allows viewing, modifying, deleting and creating new payment requests on all your stores."), new PermissionDisplay("Manage selected stores' payment requests", "Allows viewing, modifying, deleting and creating new payment requests on the selected stores."), new[] { Policies.CanViewPaymentRequests }), new PolicyDefinition( Policies.CanModifyProfile, new PermissionDisplay("Manage your profile", "Allows viewing and modifying your user profile."), includedPermissions: new[] { Policies.CanViewProfile }), new PolicyDefinition( Policies.CanViewProfile, new PermissionDisplay("View your profile", "Allows viewing your user profile.")), new PolicyDefinition( Policies.CanViewUsers, new PermissionDisplay("View users", "Allows seeing all users on this server.")), new PolicyDefinition( Policies.CanCreateUser, new PermissionDisplay("Create new users", "Allows creating new users on this server.")), new PolicyDefinition( Policies.CanDeleteUser, new PermissionDisplay("Delete user", "Allows deleting the user to whom it is assigned. Admin users can delete any user without this permission.")), new PolicyDefinition( Policies.CanManageNotificationsForUser, new PermissionDisplay("Manage your notifications", "Allows viewing and modifying your user notifications."), includedPermissions: new[] { Policies.CanViewNotificationsForUser }), new PolicyDefinition( Policies.CanViewNotificationsForUser, new PermissionDisplay("View your notifications", "Allows viewing your user notifications.")), new PolicyDefinition( Policies.Unrestricted, new PermissionDisplay("Unrestricted access", "Grants unrestricted access to your account.")), new PolicyDefinition( Policies.CanUseInternalLightningNode, new PermissionDisplay("Use the internal lightning node", "Allows using the internal BTCPay Server lightning node to create BOLT11 invoices, connect to other nodes, open new channels and pay BOLT11 invoices."), includedPermissions: new[] { Policies.CanCreateLightningInvoiceInternalNode, Policies.CanViewLightningInvoiceInternalNode }), new PolicyDefinition( Policies.CanViewLightningInvoiceInternalNode, new PermissionDisplay("View invoices from internal lightning node", "Allows using the internal BTCPay Server lightning node to view BOLT11 invoices.")), new PolicyDefinition( Policies.CanCreateLightningInvoiceInternalNode, new PermissionDisplay("Create invoices with internal lightning node", "Allows using the internal BTCPay Server lightning node to create BOLT11 invoices.")), new PolicyDefinition( Policies.CanUseLightningNodeInStore, new PermissionDisplay("Use the lightning nodes associated with your stores", "Allows using the lightning nodes connected to all your stores to create BOLT11 invoices, connect to other nodes, open new channels and pay BOLT11 invoices."), new PermissionDisplay("Use the lightning nodes associated with your stores", "Allows using the lightning nodes connected to the selected stores to create BOLT11 invoices, connect to other nodes, open new channels and pay BOLT11 invoices."), new[] { Policies.CanViewLightningInvoiceInStore, Policies.CanCreateLightningInvoiceInStore }), new PolicyDefinition( Policies.CanViewLightningInvoiceInStore, new PermissionDisplay("View the lightning invoices associated with your stores", "Allows viewing the lightning invoices connected to all your stores."), new PermissionDisplay("View the lightning invoices associated with your stores", "Allows viewing the lightning invoices connected to the selected stores.")), new PolicyDefinition( Policies.CanCreateLightningInvoiceInStore, new PermissionDisplay("Create invoices from the lightning nodes associated with your stores", "Allows using the lightning nodes connected to all your stores to create BOLT11 invoices."), new PermissionDisplay("Create invoices from the lightning nodes associated with your stores", "Allows using the lightning nodes connected to the selected stores to create BOLT11 invoices."), new[] { Policies.CanViewLightningInvoiceInStore }), new PolicyDefinition( Policies.CanManagePullPayments, new PermissionDisplay("Manage your pull payments", "Allows viewing, modifying, deleting and creating pull payments on all your stores."), new PermissionDisplay("Manage selected stores' pull payments", "Allows viewing, modifying, deleting and creating pull payments on the selected stores."), new[] { Policies.CanCreatePullPayments, Policies.CanArchivePullPayments }), new PolicyDefinition( Policies.CanArchivePullPayments, new PermissionDisplay("Archive your pull payments", "Allows deleting pull payments on all your stores."), new PermissionDisplay("Archive selected stores' pull payments", "Allows deleting pull payments on the selected stores.")), new PolicyDefinition( Policies.CanCreatePullPayments, new PermissionDisplay("Create pull payments", "Allows creating pull payments on all your stores."), new PermissionDisplay("Create pull payments in selected stores", "Allows creating pull payments on the selected stores."), new[] { Policies.CanCreateNonApprovedPullPayments }), new PolicyDefinition( Policies.CanViewPullPayments, new PermissionDisplay("View your pull payments", "Allows viewing pull payments on all your stores."), new PermissionDisplay("View selected stores' pull payments", "Allows viewing pull payments on the selected stores.")), new PolicyDefinition( Policies.CanCreateNonApprovedPullPayments, new PermissionDisplay("Create non-approved pull payments", "Allows creating pull payments without automatic approval on all your stores."), new PermissionDisplay("Create non-approved pull payments in selected stores", "Allows viewing, modifying, deleting and creating pull payments without automatic approval on the selected stores."), new[] { Policies.CanViewPullPayments }), new PolicyDefinition( Policies.CanManageUsers, new PermissionDisplay("Manage users", "Allows creating/deleting API keys for users."), includedPermissions: new[] { Policies.CanCreateUser }), new PolicyDefinition( Policies.CanManagePayouts, new PermissionDisplay("Manage payouts", "Allows managing payouts on all your stores."), new PermissionDisplay("Manage payouts in selected stores", "Allows managing payouts on the selected stores."), new[] { Policies.CanViewPayouts }), new PolicyDefinition( Policies.CanViewPayouts, new PermissionDisplay("View payouts", "Allows viewing payouts on all your stores."), new PermissionDisplay("View payouts in selected stores", "Allows viewing payouts on the selected stores.")) }); return services; } public static void RegisterExchangeRecommendations(IServiceCollection services) { foreach (var rule in new Dictionary() { { "EUR", "kraken" }, { "USD", "kraken" }, { "CAD", "kraken" }, { "GBP", "kraken" }, { "CHF", "kraken" }, { "GTQ", "bitpay" }, { "COP", "yadio" }, { "ARS", "yadio" }, { "JPY", "bitbank" }, { "TRY", "btcturk" }, { "UGX", "yadio" }, { "RSD", "bitpay" }, { "NGN", "bitnob" }, { "NOK", "barebitcoin" }, { "CZK", "coinmate" }, { "ZAR", "luno" }, { "INR", "coindcx" } }) { var r = new DefaultRules.Recommendation(rule.Key, rule.Value); r.Order = DefaultRules.HardcodedRecommendedExchangeOrder; services.AddSingleton(r); } } public static void AddOnchainWalletParsers(IServiceCollection services) { services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(provider => provider.GetService()); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); } internal static void RegisterCurrencyData(IServiceCollection services) { services.TryAddSingleton(); services.AddSingleton(c => new AssemblyCurrencyDataProvider(typeof(BTCPayServer.Rating.BidAsk).Assembly, "BTCPayServer.Rating.Currencies.json")); } internal static void RegisterRateSources(IServiceCollection services) { // We need to be careful to only add exchanges which OnGetTickers implementation make only 1 request services.AddRateProviderExchangeSharp(new("binance", "Binance", "https://api.binance.com/api/v1/ticker/24hr")); services.AddRateProviderExchangeSharp(new("poloniex", "Poloniex", " https://api.poloniex.com/markets/price")); services.AddRateProviderExchangeSharp(new("ndax", "NDAX", "https://ndax.io/api/returnTicker")); services.AddRateProviderExchangeSharp(new("bitfinex", "Bitfinex", "https://api.bitfinex.com/v2/tickers?symbols=tBTCUSD,tLTCUSD,tLTCBTC,tETHUSD,tETHBTC,tETCBTC,tETCUSD,tRRTUSD,tRRTBTC,tZECUSD,tZECBTC,tXMRUSD,tXMRBTC,tDSHUSD,tDSHBTC,tBTCEUR,tBTCJPY,tXRPUSD,tXRPBTC,tIOTUSD,tIOTBTC,tIOTETH,tEOSUSD,tEOSBTC,tEOSETH,tSANUSD,tSANBTC,tSANETH,tOMGUSD,tOMGBTC,tOMGETH,tNEOUSD,tNEOBTC,tNEOETH,tETPUSD,tETPBTC,tETPETH,tQTMUSD,tQTMBTC,tQTMETH,tAVTUSD,tAVTBTC,tAVTETH,tEDOUSD,tEDOBTC,tEDOETH,tBTGUSD,tBTGBTC,tDATUSD,tDATBTC,tDATETH,tQSHUSD,tQSHBTC,tQSHETH,tYYWUSD,tYYWBTC,tYYWETH,tGNTUSD,tGNTBTC,tGNTETH,tSNTUSD,tSNTBTC,tSNTETH,tIOTEUR,tBATUSD,tBATBTC,tBATETH,tMNAUSD,tMNABTC,tMNAETH,tFUNUSD,tFUNBTC,tFUNETH,tZRXUSD,tZRXBTC,tZRXETH,tTNBUSD,tTNBBTC,tTNBETH,tSPKUSD,tSPKBTC,tSPKETH,tTRXUSD,tTRXBTC,tTRXETH,tRCNUSD,tRCNBTC,tRCNETH,tRLCUSD,tRLCBTC,tRLCETH,tAIDUSD,tAIDBTC,tAIDETH,tSNGUSD,tSNGBTC,tSNGETH,tREPUSD,tREPBTC,tREPETH,tELFUSD,tELFBTC,tELFETH,tNECUSD,tNECBTC,tNECETH,tBTCGBP,tETHEUR,tETHJPY,tETHGBP,tNEOEUR,tNEOJPY,tNEOGBP,tEOSEUR,tEOSJPY,tEOSGBP,tIOTJPY,tIOTGBP,tIOSUSD,tIOSBTC,tIOSETH,tAIOUSD,tAIOBTC,tAIOETH,tREQUSD,tREQBTC,tREQETH,tRDNUSD,tRDNBTC,tRDNETH,tLRCUSD,tLRCBTC,tLRCETH,tWAXUSD,tWAXBTC,tWAXETH,tDAIUSD,tDAIBTC,tDAIETH,tAGIUSD,tAGIBTC,tAGIETH,tBFTUSD,tBFTBTC,tBFTETH,tMTNUSD,tMTNBTC,tMTNETH,tODEUSD,tODEBTC,tODEETH,tANTUSD,tANTBTC,tANTETH,tDTHUSD,tDTHBTC,tDTHETH,tMITUSD,tMITBTC,tMITETH,tSTJUSD,tSTJBTC,tSTJETH,tXLMUSD,tXLMEUR,tXLMJPY,tXLMGBP,tXLMBTC,tXLMETH,tXVGUSD,tXVGEUR,tXVGJPY,tXVGGBP,tXVGBTC,tXVGETH,tBCIUSD,tBCIBTC,tMKRUSD,tMKRBTC,tMKRETH,tKNCUSD,tKNCBTC,tKNCETH,tPOAUSD,tPOABTC,tPOAETH,tEVTUSD,tLYMUSD,tLYMBTC,tLYMETH,tUTKUSD,tUTKBTC,tUTKETH,tVEEUSD,tVEEBTC,tVEEETH,tDADUSD,tDADBTC,tDADETH,tORSUSD,tORSBTC,tORSETH,tAUCUSD,tAUCBTC,tAUCETH,tPOYUSD,tPOYBTC,tPOYETH,tFSNUSD,tFSNBTC,tFSNETH,tCBTUSD,tCBTBTC,tCBTETH,tZCNUSD,tZCNBTC,tZCNETH,tSENUSD,tSENBTC,tSENETH,tNCAUSD,tNCABTC,tNCAETH,tCNDUSD,tCNDBTC,tCNDETH,tCTXUSD,tCTXBTC,tCTXETH,tPAIUSD,tPAIBTC,tSEEUSD,tSEEBTC,tSEEETH,tESSUSD,tESSBTC,tESSETH,tATMUSD,tATMBTC,tATMETH,tHOTUSD,tHOTBTC,tHOTETH,tDTAUSD,tDTABTC,tDTAETH,tIQXUSD,tIQXBTC,tIQXEOS,tWPRUSD,tWPRBTC,PRETH,tZILUSD,tZILBTC,tZILETH,tBNTUSD,tBNTBTC,tBNTETH,tABSUSD,tABSETH,tXRAUSD,tXRAETH,tMANUSD,tMANETH,tBBNUSD,tBBNETH,tNIOUSD,tNIOETH,tDGXUSD,tDGXETH,tVETUSD,tVETBTC,tVETETH,tUTNUSD,tUTNETH,tTKNUSD,tTKNETH,tGOTUSD,tGOTEUR,tGOTETH,tXTZUSD,tXTZBTC,tCNNUSD,tCNNETH,tBOXUSD,tBOXETH,tTRXEUR,tTRXGBP,tTRXJPY,tMGOUSD,tMGOETH,tRTEUSD,tRTEETH,tYGGUSD,tYGGETH,tMLNUSD,tMLNETH,tWTCUSD,tWTCETH,tCSXUSD,tCSXETH,tOMNUSD,tOMNBTC,tINTUSD,tINTETH,tDRNUSD,tDRNETH,tPNKUSD,tPNKETH,tDGBUSD,tDGBBTC,tBSVUSD,tBSVBTC,tBABUSD,tBABBTC,tWLOUSD,tWLOXLM,tVLDUSD,tVLDETH,tENJUSD,tENJETH,tONLUSD,tONLETH,tRBTUSD,tRBTBTC,tUSTUSD,tEUTEUR,tEUTUSD,tGSDUSD,tUDCUSD,tTSDUSD,tPAXUSD,tRIFUSD,tRIFBTC,tPASUSD,tPASETH,tVSYUSD,tVSYBTC,tZRXDAI,tMKRDAI,tOMGDAI,tBTTUSD,tBTTBTC,tBTCUST,tETHUST,tCLOUSD,tCLOBTC,tIMPUSD,tIMPETH,tLTCUST,tEOSUST,tBABUST,tSCRUSD,tSCRETH,tGNOUSD,tGNOETH,tGENUSD,tGENETH,tATOUSD,tATOBTC,tATOETH,tWBTUSD,tXCHUSD,tEUSUSD,tWBTETH,tXCHETH,tEUSETH,tLEOUSD,tLEOBTC,tLEOUST,tLEOEOS,tLEOETH,tASTUSD,tASTETH,tFOAUSD,tFOAETH,tUFRUSD,tUFRETH,tZBTUSD,tZBTUST,tOKBUSD,tUSKUSD,tGTXUSD,tKANUSD,tOKBUST,tOKBETH,tOKBBTC,tUSKUST,tUSKETH,tUSKBTC,tUSKEOS,tGTXUST,tKANUST,tAMPUSD,tALGUSD,tALGBTC,tALGUST,tBTCXCH,tSWMUSD,tSWMETH,tTRIUSD,tTRIETH,tLOOUSD,tLOOETH,tAMPUST,tDUSK:USD,tDUSK:BTC,tUOSUSD,tUOSBTC,tRRBUSD,tRRBUST,tDTXUSD,tDTXUST,tAMPBTC,tFTTUSD,tFTTUST,tPAXUST,tUDCUST,tTSDUST,tBTC:CNHT,tUST:CNHT,tCNH:CNHT,tCHZUSD,tCHZUST,tBTCF0:USTF0,tETHF0:USTF0")); services.AddRateProviderExchangeSharp(new("okex", "OKEx", "https://www.okex.com/api/futures/v3/instruments/ticker")); services.AddRateProviderExchangeSharp(new("coinbasepro", "Coinbase Pro", "https://api.pro.coinbase.com/products")); // Handmade providers services.AddRateProvider(); services.AddRateProvider(); services.AddRateProvider(); services.AddRateProvider(); services.AddRateProvider(); services.AddRateProvider(); services.AddRateProvider(); services.AddRateProvider(); services.AddRateProvider(); services.AddRateProvider(); services.AddRateProvider(); services.AddRateProvider(); services.AddRateProvider(); services.AddRateProvider(); services.AddRateProvider(); services.AddRateProvider(); services.AddRateProvider(); services.AddRateProvider(); services.AddRateProvider(); services.AddRateProvider(); services.AddSingleton(); services.AddSingleton(o => o.GetRequiredService()); services.AddSingleton(); services.AddSingleton(o => o.GetRequiredService()); // Broken // Providers.Add("argoneum", new ArgoneumRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_ARGONEUM"))); // Those exchanges make too many requests, exchange sharp do not parallelize so it is too slow... //AddExchangeSharpProviders("gemini"); //AddExchangeSharpProviders("bitstamp"); //AddExchangeSharpProviders("bitmex"); } public static void AddRateProvider(this IServiceCollection services) where T : class, IRateProvider { services.AddSingleton(); } public static IServiceCollection AddBTCPayNetwork(this IServiceCollection services, BTCPayNetworkBase network) { services.AddSingleton(new DefaultRules(network.DefaultRateRules)); services.AddSingleton(network); return services; } public static IServiceCollection AddCurrencyData(this IServiceCollection services, params CurrencyData[] currencyData) { services.AddSingleton(c => new InMemoryCurrencyDataProvider(currencyData)); return services; } public static IServiceCollection AddBTCPayNetwork(this IServiceCollection services, BTCPayNetwork network) { services.AddSingleton(new DefaultRules(network.DefaultRateRules)); // BTC { var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(network.CryptoCode); services.AddDefaultPrettyName(pmi, network.DisplayName); services.AddSingleton(network); services.AddSingleton(provider => (BitcoinLikePaymentHandler)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinLikePaymentHandler), new object[] { network, pmi })); services.AddSingleton(provider => (IPaymentLinkExtension)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinPaymentLinkExtension), new object[] { network, pmi })); services.AddSingleton(provider => (BitcoinCheckoutModelExtension)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinCheckoutModelExtension), new object[] { network, pmi })); services.AddSingleton(provider => (IPaymentMethodBitpayAPIExtension)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinPaymentMethodBitpayAPIExtension), new object[] { pmi })); services.AddSingleton(provider => (ICheckoutCheatModeExtension)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinCheckoutCheatModeExtension), new object[] { network })); if (!network.ReadonlyWallet && network.WalletSupported) { var payoutMethodId = PayoutTypes.CHAIN.GetPayoutMethodId(network.CryptoCode); services.AddSingleton(provider => (IPayoutHandler)ActivatorUtilities.CreateInstance(provider, typeof(BitcoinLikePayoutHandler), new object[] { payoutMethodId, network })); } } if (network.NBitcoinNetwork.Consensus.SupportSegwit && network.SupportLightning) { // LN { var pmi = PaymentTypes.LN.GetPaymentMethodId(network.CryptoCode); if (network.IsBTC) services.AddDefaultPrettyName(pmi, "Lightning"); else services.AddDefaultPrettyName(pmi, $"Lightning ({network.DisplayName})"); services.AddSingleton(provider => (LightningLikePaymentHandler)ActivatorUtilities.CreateInstance(provider, typeof(LightningLikePaymentHandler), new object[] { network, pmi })); services.AddSingleton(provider => (IPaymentLinkExtension)ActivatorUtilities.CreateInstance(provider, typeof(LightningPaymentLinkExtension), new object[] { network, pmi })); services.AddSingleton(provider => (ICheckoutModelExtension)ActivatorUtilities.CreateInstance(provider, typeof(LNCheckoutModelExtension), new object[] { network, pmi })); services.AddSingleton(provider => (IPaymentMethodBitpayAPIExtension)ActivatorUtilities.CreateInstance(provider, typeof(LightningPaymentMethodBitpayAPIExtension), new object[] { pmi })); var payoutMethodId = PayoutTypes.LN.GetPayoutMethodId(network.CryptoCode); services.AddSingleton(provider => (IPayoutHandler)ActivatorUtilities.CreateInstance(provider, typeof(LightningLikePayoutHandler), new object[] { payoutMethodId, network })); services.AddSingleton(provider => (ICheckoutCheatModeExtension)ActivatorUtilities.CreateInstance(provider, typeof(LightningCheckoutCheatModeExtension), new object[] { network })); } // LNURL { var pmi = PaymentTypes.LNURL.GetPaymentMethodId(network.CryptoCode); if (network.IsBTC) services.AddDefaultPrettyName(pmi, "Lightning (via LNURL)"); else services.AddDefaultPrettyName(pmi, $"Lightning ({network.DisplayName} via LNURL)"); services.AddSingleton(provider => (LNURLPayPaymentHandler)ActivatorUtilities.CreateInstance(provider, typeof(LNURLPayPaymentHandler), new object[] { network, pmi })); services.AddSingleton(provider => (IPaymentLinkExtension)ActivatorUtilities.CreateInstance(provider, typeof(LNURLPayPaymentLinkExtension), new object[] { network, pmi })); services.AddSingleton(provider => (ICheckoutModelExtension)ActivatorUtilities.CreateInstance(provider, typeof(LNURLCheckoutModelExtension), new object[] { network, pmi })); services.AddSingleton(provider => (IPaymentMethodBitpayAPIExtension)ActivatorUtilities.CreateInstance(provider, typeof(LNURLPayPaymentMethodBitpayAPIExtension), new object[] { pmi })); } } return services; } public static void AddTransactionLinkProvider(this IServiceCollection services, PaymentMethodId paymentMethodId, TransactionLinkProvider provider) { services.AddSingleton(new TransactionLinkProviders.Entry(paymentMethodId, provider)); } [Obsolete("Use AddTransactionLinkProvider(services, PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode), provider) instead")] public static void AddTransactionLinkProvider(this IServiceCollection services, string cryptoCode, TransactionLinkProvider provider) => AddTransactionLinkProvider(services, PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode), provider); public static void AddRateProviderExchangeSharp(this IServiceCollection services, RateSourceInfo rateInfo) where T : ExchangeSharp.ExchangeAPI { services.AddSingleton>(o => { var instance = ActivatorUtilities.CreateInstance>(o); instance.RateSourceInfo = rateInfo; return instance; }); } public static void SkipModelValidation(this IServiceCollection services) { services.AddSingleton>(); } private static void AddBtcPayServerAuthenticationSchemes(this IServiceCollection services) { services.PostConfigure(IdentityConstants.ApplicationScheme, opt => { opt.LoginPath = "/login"; opt.AccessDeniedPath = "/errors/403"; opt.LogoutPath = "/logout"; ConfigureAccessDeniedRedirect(opt); }); services.AddAuthentication() .AddCookie(AuthenticationSchemes.LimitedLogin, options => { options.Cookie.Name = "limited_login"; options.ExpireTimeSpan = TimeSpan.FromMinutes(60); // short-lived options.SlidingExpiration = false; options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.SameAsRequest; options.LoginPath = "/login"; options.AccessDeniedPath = "/errors/403"; options.LogoutPath = "/logout"; ConfigureAccessDeniedRedirect(options); }) .AddAPIKeyAuthentication(); } private static void ConfigureAccessDeniedRedirect(CookieAuthenticationOptions options) { var onRedirectToAccessDenied = options.Events.OnRedirectToAccessDenied; options.Events.OnRedirectToAccessDenied = context => { if (context.HttpContext.Items.TryGetValue(PermissionAuthorizationHandler.PolicyRequirementKey, out var p) && p is PolicyRequirement policyRequirement) { context.RedirectUri = QueryHelpers.AddQueryString( context.RedirectUri, UIErrorController.MissingPermissionQueryKey, policyRequirement.Policy); } return onRedirectToAccessDenied(context); }; } public static IApplicationBuilder UsePayServer(this IApplicationBuilder app) { app.UseMiddleware(); app.UseMiddleware(); return app; } public static IApplicationBuilder UseHeadersOverride(this IApplicationBuilder app) { app.UseMiddleware(); return app; } } }