Compare commits
52 Commits
Author | SHA1 | Date | |
---|---|---|---|
14841ad7c9 | |||
2c6aa12aab | |||
ef4d39db3c | |||
7a566c477d | |||
85c40aef23 | |||
d00fa42553 | |||
e9e94f5e99 | |||
5e2d4407ca | |||
846bd08e20 | |||
a1a4eed860 | |||
1e582625f3 | |||
39b018fdf3 | |||
83304de1c6 | |||
5c8e03dcbf | |||
4dddc539f6 | |||
9950b781b4 | |||
7a32f692d1 | |||
d480be925b | |||
3775317047 | |||
500bdd9bf1 | |||
57bda24664 | |||
6401af00fe | |||
3b3a18bbbc | |||
b4e9dfeeaf | |||
16f5def245 | |||
26e7de534b | |||
a3ae694048 | |||
b04d70f141 | |||
101d6131c7 | |||
faabd68f6f | |||
aa72b814da | |||
0dcda0f289 | |||
a2b039f983 | |||
1a54f2d01a | |||
4276994265 | |||
2c6133b4f7 | |||
64181d1a93 | |||
25e9a27a78 | |||
f3edaf5160 | |||
7bfdf2d11d | |||
d2808cf662 | |||
8cafa8a483 | |||
724af44e41 | |||
bac9ef4f93 | |||
ada6f3b844 | |||
c8a26ce952 | |||
6cf80b7533 | |||
79df523bb2 | |||
e921f9757a | |||
bed9737d64 | |||
2583eb15ec | |||
1879ea55e8 |
@ -138,6 +138,12 @@ namespace BTCPayServer.Tests
|
||||
BidAsk = new BidAsk(4500m)
|
||||
});
|
||||
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||
{
|
||||
Exchange = "coinaverage",
|
||||
CurrencyPair = CurrencyPair.Parse("LTC_BTC"),
|
||||
BidAsk = new BidAsk(0.001m)
|
||||
});
|
||||
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||
{
|
||||
Exchange = "coinaverage",
|
||||
CurrencyPair = CurrencyPair.Parse("LTC_USD"),
|
||||
|
@ -46,8 +46,8 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Assert.Equal(test.Expected, rules.GetRuleFor(CurrencyPair.Parse(test.Pair)).ToString());
|
||||
}
|
||||
rules.GlobalMultiplier = 2.32m;
|
||||
Assert.Equal("(bittrex(DOGE_BTC) * gdax(BTC_USD) * 1.1) * 2.32", rules.GetRuleFor(CurrencyPair.Parse("DOGE_USD")).ToString());
|
||||
rules.Spread = 0.2m;
|
||||
Assert.Equal("(bittrex(DOGE_BTC) * gdax(BTC_USD) * 1.1) * (0.8, 1.2)", rules.GetRuleFor(CurrencyPair.Parse("DOGE_USD")).ToString());
|
||||
////////////////
|
||||
|
||||
// Check errors conditions
|
||||
@ -104,7 +104,7 @@ namespace BTCPayServer.Tests
|
||||
rule2.Reevaluate();
|
||||
Assert.False(rule2.HasError);
|
||||
Assert.Equal("5000 * 2000.4 * 1.1", rule2.ToString(true));
|
||||
Assert.Equal(rule2.Value, 5000m * 2000.4m * 1.1m);
|
||||
Assert.Equal(rule2.BidAsk.Bid, 5000m * 2000.4m * 1.1m);
|
||||
////////
|
||||
|
||||
// Make sure parenthesis are correctly calculated
|
||||
@ -113,22 +113,22 @@ namespace BTCPayServer.Tests
|
||||
builder.AppendLine("BTC_USD = -3 + coinbase(BTC_CAD) + 50 - 5");
|
||||
builder.AppendLine("DOGE_BTC = 2000");
|
||||
Assert.True(RateRules.TryParse(builder.ToString(), out rules));
|
||||
rules.GlobalMultiplier = 1.1m;
|
||||
rules.Spread = 0.1m;
|
||||
|
||||
rule2 = rules.GetRuleFor(CurrencyPair.Parse("DOGE_USD"));
|
||||
Assert.Equal("(2000 * (-3 + coinbase(BTC_CAD) + 50 - 5)) * 1.1", rule2.ToString());
|
||||
Assert.Equal("(2000 * (-3 + coinbase(BTC_CAD) + 50 - 5)) * (0.9, 1.1)", rule2.ToString());
|
||||
rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), new BidAsk(1000m));
|
||||
Assert.True(rule2.Reevaluate());
|
||||
Assert.Equal("(2000 * (-3 + 1000 + 50 - 5)) * 1.1", rule2.ToString(true));
|
||||
Assert.Equal((2000m * (-3m + 1000m + 50m - 5m)) * 1.1m, rule2.Value.Value);
|
||||
Assert.Equal("(2000 * (-3 + 1000 + 50 - 5)) * (0.9, 1.1)", rule2.ToString(true));
|
||||
Assert.Equal((2000m * (-3m + 1000m + 50m - 5m)) * 0.9m, rule2.BidAsk.Bid);
|
||||
|
||||
// Test inverse
|
||||
rule2 = rules.GetRuleFor(CurrencyPair.Parse("USD_DOGE"));
|
||||
Assert.Equal("(1 / (2000 * (-3 + coinbase(BTC_CAD) + 50 - 5))) * 1.1", rule2.ToString());
|
||||
Assert.Equal("(1 / (2000 * (-3 + coinbase(BTC_CAD) + 50 - 5))) * (0.9, 1.1)", rule2.ToString());
|
||||
rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), new BidAsk(1000m));
|
||||
Assert.True(rule2.Reevaluate());
|
||||
Assert.Equal("(1 / (2000 * (-3 + 1000 + 50 - 5))) * 1.1", rule2.ToString(true));
|
||||
Assert.Equal((1.0m / (2000m * (-3m + 1000m + 50m - 5m))) * 1.1m, rule2.Value.Value);
|
||||
Assert.Equal("(1 / (2000 * (-3 + 1000 + 50 - 5))) * (0.9, 1.1)", rule2.ToString(true));
|
||||
Assert.Equal((1.0m / (2000m * (-3m + 1000m + 50m - 5m))) * 0.9m, rule2.BidAsk.Bid);
|
||||
////////
|
||||
|
||||
// Make sure kraken is not converted to CurrencyPair
|
||||
@ -147,12 +147,12 @@ namespace BTCPayServer.Tests
|
||||
rule2.ExchangeRates.SetRate("kraken", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m));
|
||||
Assert.True(rule2.Reevaluate());
|
||||
Assert.Equal("(6000, 6100)", rule2.ToString(true));
|
||||
Assert.Equal(6000m, rule2.Value.Value);
|
||||
Assert.Equal(6000m, rule2.BidAsk.Bid);
|
||||
rule2 = rules.GetRuleFor(CurrencyPair.Parse("USD_BTC"));
|
||||
rule2.ExchangeRates.SetRate("kraken", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m));
|
||||
Assert.True(rule2.Reevaluate());
|
||||
Assert.Equal("1 / (6000, 6100)", rule2.ToString(true));
|
||||
Assert.Equal(1m / 6100m, rule2.Value.Value);
|
||||
Assert.Equal(1m / 6100m, rule2.BidAsk.Bid);
|
||||
|
||||
// Make sure the inverse has more priority than X_X or CDNT_X
|
||||
builder = new StringBuilder();
|
||||
|
@ -35,6 +35,7 @@ using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Validation;
|
||||
using ExchangeSharp;
|
||||
@ -521,14 +522,14 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// We get this format from "openssl x509 -noout -fingerprint -sha256 -inform pem -in <certificate>"
|
||||
var certthumbprint2 = "C5:1B:B1:D4:02:30:6D:0D:A0:0E:85:58:1B:32:AA:56:16:6B:CB:AB:7E:B8:88:FF:92:5D:71:67:EB:43:6D:06";
|
||||
|
||||
|
||||
var lndUri = $"type=lnd-rest;server=https://lnd:lnd@127.0.0.1:53280/;macaroon={macaroon};certthumbprint={certthumbprint}";
|
||||
var lndUri2 = $"type=lnd-rest;server=https://lnd:lnd@127.0.0.1:53280/;macaroon={macaroon};certthumbprint={certthumbprint2}";
|
||||
|
||||
var certificateHash = new X509Certificate2(Encoders.Hex.DecodeData("2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494942396a4343415a7967417749424167495156397a62474252724e54716b4e4b55676d72524d377a414b42676771686b6a4f50515144416a41784d5238770a485159445651514b45785a73626d5167595856306232646c626d56795958526c5a43426a5a584a304d51347744415944565151444577564754304e56557a41650a467730784f4441304d6a55794d7a517a4d6a4261467730784f5441324d6a41794d7a517a4d6a42614d444578487a416442674e5642416f54466d78755a4342680a645852765a3256755a584a686447566b49474e6c636e5178446a414d42674e5642414d5442555a50513156544d466b77457759484b6f5a497a6a3043415159490a4b6f5a497a6a304441516344516741454b7557424568564f75707965434157476130766e713262712f59396b41755a78616865646d454553482b753936436d450a397577486b4b2b4a7667547a66385141783550513741357254637155374b57595170303175364f426c5443426b6a414f42674e56485138424166384542414d430a4171517744775944565230544151482f42415577417745422f7a427642674e56485245456144426d6767564754304e565534494a6247396a5957786f62334e300a6877522f4141414268784141414141414141414141414141414141414141414268775373474f69786877514b41457342687753702f717473687754417141724c0a687753702f6d4a72687753702f754f77687753702f714e59687753702f6874436877514b70514157687753702f6c42514d416f4743437147534d343942414d430a413067414d45554349464866716d595a5043647a4a5178386b47586859473834394c31766541364c784d6f7a4f5774356d726835416945413662756e51556c710a6558553070474168776c3041654d726a4d4974394c7652736179756162565a593278343d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a"))
|
||||
.GetCertHash(System.Security.Cryptography.HashAlgorithmName.SHA256);
|
||||
|
||||
|
||||
|
||||
Assert.True(LightningConnectionString.TryParse(lndUri, false, out conn));
|
||||
Assert.True(LightningConnectionString.TryParse(lndUri2, false, out var conn2));
|
||||
Assert.Equal(conn2.ToString(), conn.ToString());
|
||||
@ -574,7 +575,7 @@ namespace BTCPayServer.Tests
|
||||
user.GrantAccess();
|
||||
user.RegisterLightningNode("BTC", type);
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
|
||||
|
||||
tester.PrepareLightning(type);
|
||||
|
||||
Task.WaitAll(CanSendLightningPaymentCore(tester, user));
|
||||
@ -731,6 +732,40 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanGetRates()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var acc = tester.NewAccount();
|
||||
acc.GrantAccess();
|
||||
acc.RegisterDerivationScheme("BTC");
|
||||
acc.RegisterDerivationScheme("LTC");
|
||||
|
||||
var rateController = acc.GetController<RateController>();
|
||||
var GetBaseCurrencyRatesResult = JObject.Parse(((JsonResult)rateController.GetBaseCurrencyRates("BTC", acc.StoreId)
|
||||
.GetAwaiter().GetResult()).Value.ToJson()).ToObject<DataWrapper<Rate[]>>();
|
||||
Assert.NotNull(GetBaseCurrencyRatesResult);
|
||||
Assert.NotNull(GetBaseCurrencyRatesResult.Data);
|
||||
Assert.Equal(2, GetBaseCurrencyRatesResult.Data.Length);
|
||||
Assert.Single(GetBaseCurrencyRatesResult.Data.Where(o => o.Code == "LTC"));
|
||||
|
||||
var GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, acc.StoreId)
|
||||
.GetAwaiter().GetResult()).Value.ToJson()).ToObject<DataWrapper<Rate[]>>();
|
||||
Assert.NotNull(GetRatesResult);
|
||||
Assert.NotNull(GetRatesResult.Data);
|
||||
Assert.Equal(2, GetRatesResult.Data.Length);
|
||||
|
||||
var GetCurrencyPairRateResult = JObject.Parse(((JsonResult)rateController.GetCurrencyPairRate("BTC", "LTC", acc.StoreId)
|
||||
.GetAwaiter().GetResult()).Value.ToJson()).ToObject<DataWrapper<Rate>>();
|
||||
|
||||
Assert.NotNull(GetCurrencyPairRateResult);
|
||||
Assert.NotNull(GetCurrencyPairRateResult.Data);
|
||||
Assert.Equal("LTC", GetCurrencyPairRateResult.Data.Code);
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertSearchInvoice(TestAccount acc, bool expected, string invoiceId, string filter)
|
||||
{
|
||||
var result = (Models.InvoicingModels.InvoicesModel)((ViewResult)acc.GetController<InvoiceController>().ListInvoices(filter).Result).Model;
|
||||
@ -947,8 +982,8 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var storeController = user.GetController<StoresController>();
|
||||
var vm = (RatesViewModel)((ViewResult)storeController.Rates()).Model;
|
||||
Assert.Equal(1.0, vm.RateMultiplier);
|
||||
vm.RateMultiplier = 0.5;
|
||||
Assert.Equal(0.0, vm.Spread);
|
||||
vm.Spread = 40;
|
||||
storeController.Rates(vm).Wait();
|
||||
|
||||
|
||||
@ -962,7 +997,11 @@ namespace BTCPayServer.Tests
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
Assert.True(invoice2.BtcPrice.Almost(invoice1.BtcPrice * 2, 0.00001m));
|
||||
// The rate was 5000 USD per BTC
|
||||
// Now it should be 3000 USD per BTC
|
||||
// So the expected price should be
|
||||
var expected = Money.Coins(5000m / 3000m);
|
||||
Assert.True(invoice2.BtcPrice.Almost(expected, 0.00001m));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1044,7 +1083,7 @@ namespace BTCPayServer.Tests
|
||||
var rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates()).Model);
|
||||
Assert.False(rateVm.ShowScripting);
|
||||
Assert.Equal("coinaverage", rateVm.PreferredExchange);
|
||||
Assert.Equal(1.0, rateVm.RateMultiplier);
|
||||
Assert.Equal(0.0, rateVm.Spread);
|
||||
Assert.Null(rateVm.TestRateRules);
|
||||
|
||||
rateVm.PreferredExchange = "bitflyer";
|
||||
@ -1053,13 +1092,13 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal("bitflyer", rateVm.PreferredExchange);
|
||||
|
||||
rateVm.ScriptTest = "BTC_JPY,BTC_CAD";
|
||||
rateVm.RateMultiplier = 1.1;
|
||||
rateVm.Spread = 10;
|
||||
store = user.GetController<StoresController>();
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates(rateVm, "Test").Result).Model);
|
||||
Assert.NotNull(rateVm.TestRateRules);
|
||||
Assert.Equal(2, rateVm.TestRateRules.Count);
|
||||
Assert.False(rateVm.TestRateRules[0].Error);
|
||||
Assert.StartsWith("(bitflyer(BTC_JPY)) * 1.10 =", rateVm.TestRateRules[0].Rule, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.StartsWith("(bitflyer(BTC_JPY)) * (0.9, 1.1) =", rateVm.TestRateRules[0].Rule, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.True(rateVm.TestRateRules[1].Error);
|
||||
Assert.IsType<RedirectToActionResult>(store.Rates(rateVm, "Save").Result);
|
||||
|
||||
@ -1072,19 +1111,19 @@ namespace BTCPayServer.Tests
|
||||
rateVm.ScriptTest = "BTC_JPY";
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates(rateVm, "Test").Result).Model);
|
||||
Assert.True(rateVm.ShowScripting);
|
||||
Assert.Contains("(bitflyer(BTC_JPY)) * 1.10 = ", rateVm.TestRateRules[0].Rule, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.Contains("(bitflyer(BTC_JPY)) * (0.9, 1.1) = ", rateVm.TestRateRules[0].Rule, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
rateVm.ScriptTest = "BTC_USD,BTC_CAD,DOGE_USD,DOGE_CAD";
|
||||
rateVm.Script = "DOGE_X = bittrex(DOGE_BTC) * BTC_X;\n" +
|
||||
"X_CAD = quadrigacx(X_CAD);\n" +
|
||||
"X_X = gdax(X_X);";
|
||||
rateVm.RateMultiplier = 0.5;
|
||||
rateVm.Spread = 50;
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates(rateVm, "Test").Result).Model);
|
||||
Assert.True(rateVm.TestRateRules.All(t => !t.Error));
|
||||
Assert.IsType<RedirectToActionResult>(store.Rates(rateVm, "Save").Result);
|
||||
store = user.GetController<StoresController>();
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates()).Model);
|
||||
Assert.Equal(0.5, rateVm.RateMultiplier);
|
||||
Assert.Equal(50, rateVm.Spread);
|
||||
Assert.True(rateVm.ShowScripting);
|
||||
Assert.Contains("DOGE_X", rateVm.Script, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
@ -1261,6 +1300,64 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal($"{tpub}-[p2sh]", result.ToString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanDisablePaymentMethods()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.Start();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
user.RegisterDerivationScheme("LTC");
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.CLightning);
|
||||
|
||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Price = 1.5m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
Assert.Equal(3, invoice.CryptoInfo.Length);
|
||||
|
||||
var controller = user.GetController<StoresController>();
|
||||
var lightningVM = (LightningNodeViewModel)Assert.IsType<ViewResult>(controller.AddLightningNode(user.StoreId, "BTC")).Model;
|
||||
Assert.True(lightningVM.Enabled);
|
||||
lightningVM.Enabled = false;
|
||||
controller.AddLightningNode(user.StoreId, lightningVM, "save", "BTC").GetAwaiter().GetResult();
|
||||
lightningVM = (LightningNodeViewModel)Assert.IsType<ViewResult>(controller.AddLightningNode(user.StoreId, "BTC")).Model;
|
||||
Assert.False(lightningVM.Enabled);
|
||||
|
||||
var derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
Assert.True(derivationVM.Enabled);
|
||||
derivationVM.Enabled = false;
|
||||
Assert.IsType<RedirectToActionResult>(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult());
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
// Confirmation
|
||||
controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult();
|
||||
Assert.False(derivationVM.Enabled);
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
Assert.False(derivationVM.Enabled);
|
||||
|
||||
invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Price = 1.5m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
Assert.Single(invoice.CryptoInfo);
|
||||
Assert.Equal("LTC", invoice.CryptoInfo[0].CryptoCode);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CanSetPaymentMethodLimits()
|
||||
{
|
||||
@ -1592,7 +1689,7 @@ namespace BTCPayServer.Tests
|
||||
foreach (var value in result)
|
||||
{
|
||||
var rateResult = value.Value.GetAwaiter().GetResult();
|
||||
Assert.NotNull(rateResult.Value);
|
||||
Assert.NotNull(rateResult.BidAsk);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1629,7 +1726,7 @@ namespace BTCPayServer.Tests
|
||||
// Should cache at exchange level so this should hit the cache
|
||||
var fetchedRate2 = factory.FetchRate(CurrencyPair.Parse("LTC_USD"), rateRules).GetAwaiter().GetResult();
|
||||
Assert.True(fetchedRate.Cached);
|
||||
Assert.NotEqual(fetchedRate.Value.Value, fetchedRate2.Value.Value);
|
||||
Assert.NotEqual(fetchedRate.BidAsk.Bid, fetchedRate2.BidAsk.Bid);
|
||||
|
||||
// Should cache at exchange level this should not hit the cache as it is different exchange
|
||||
RateRules.TryParse("X_X = bittrex(X_X);", out rateRules);
|
||||
|
35
BTCPayServer/BTCPayNetworkProvider.Dash.cs
Normal file
35
BTCPayServer/BTCPayNetworkProvider.Dash.cs
Normal file
@ -0,0 +1,35 @@
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public partial class BTCPayNetworkProvider
|
||||
{
|
||||
public void InitDash()
|
||||
{
|
||||
//not needed: NBitcoin.Altcoins.Dash.Instance.EnsureRegistered();
|
||||
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("DASH");
|
||||
Add(new BTCPayNetwork()
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Dash",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet
|
||||
? "https://insight.dash.org/insight/tx/{0}"
|
||||
: "https://testnet-insight.dashevo.org/insight/tx/{0}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "dash",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"DASH_X = DASH_BTC * BTC_X",
|
||||
"DASH_BTC = bittrex(DASH_BTC)"
|
||||
},
|
||||
CryptoImagePath = "imlegacy/dash.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
//https://github.com/satoshilabs/slips/blob/master/slip-0044.md
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("5'")
|
||||
: new KeyPath("1'"),
|
||||
MinFee = Money.Satoshis(1m)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -50,6 +50,7 @@ namespace BTCPayServer
|
||||
InitDogecoin();
|
||||
InitBitcoinGold();
|
||||
InitMonacoin();
|
||||
InitDash();
|
||||
InitPolis();
|
||||
InitFeathercoin();
|
||||
InitGroestlcoin();
|
||||
|
@ -2,7 +2,7 @@
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<Version>1.0.2.76</Version>
|
||||
<Version>1.0.2.87</Version>
|
||||
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
|
@ -17,6 +17,7 @@ using BTCPayServer.Services.Mails;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.Security;
|
||||
using System.Globalization;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -236,23 +237,25 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> Register(string returnUrl = null)
|
||||
public async Task<IActionResult> Register(string returnUrl = null, bool logon = true)
|
||||
{
|
||||
var policies = await _SettingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
|
||||
if (policies.LockSubscription)
|
||||
if (policies.LockSubscription && !User.IsInRole(Roles.ServerAdmin))
|
||||
return RedirectToAction(nameof(HomeController.Index), "Home");
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
ViewData["Logon"] = logon.ToString(CultureInfo.InvariantCulture).ToLowerInvariant();
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null)
|
||||
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null, bool logon = true)
|
||||
{
|
||||
ViewData["ReturnUrl"] = returnUrl;
|
||||
ViewData["Logon"] = logon.ToString(CultureInfo.InvariantCulture).ToLowerInvariant();
|
||||
var policies = await _SettingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
|
||||
if (policies.LockSubscription)
|
||||
if (policies.LockSubscription && !User.IsInRole(Roles.ServerAdmin))
|
||||
return RedirectToAction(nameof(HomeController.Index), "Home");
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
@ -274,7 +277,8 @@ namespace BTCPayServer.Controllers
|
||||
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
|
||||
if (!policies.RequiresConfirmedEmail)
|
||||
{
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
if(logon)
|
||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||
return RedirectToLocal(returnUrl);
|
||||
}
|
||||
else
|
||||
|
@ -19,6 +19,7 @@ using BTCPayServer.Services.Rates;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -208,6 +209,7 @@ namespace BTCPayServer.Controllers
|
||||
[HttpPost]
|
||||
[Route("{appId}/pos")]
|
||||
[IgnoreAntiforgeryToken]
|
||||
[EnableCors(CorsPolicies.All)]
|
||||
public async Task<IActionResult> ViewPointOfSale(string appId,
|
||||
decimal amount,
|
||||
string email,
|
||||
|
@ -213,7 +213,7 @@ namespace BTCPayServer.Controllers
|
||||
bool isDefaultCrypto = false;
|
||||
if (paymentMethodIdStr == null)
|
||||
{
|
||||
paymentMethodIdStr = store.GetDefaultCrypto();
|
||||
paymentMethodIdStr = store.GetDefaultCrypto(_NetworkProvider);
|
||||
isDefaultCrypto = true;
|
||||
}
|
||||
|
||||
@ -231,7 +231,11 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (!isDefaultCrypto)
|
||||
return null;
|
||||
var paymentMethodTemp = invoice.GetPaymentMethods(_NetworkProvider).First();
|
||||
var paymentMethodTemp = invoice.GetPaymentMethods(_NetworkProvider)
|
||||
.Where(c=> paymentMethodId.CryptoCode == c.GetId().CryptoCode)
|
||||
.FirstOrDefault();
|
||||
if (paymentMethodTemp == null)
|
||||
paymentMethodTemp = invoice.GetPaymentMethods(_NetworkProvider).First();
|
||||
network = paymentMethodTemp.Network;
|
||||
paymentMethodId = paymentMethodTemp.GetId();
|
||||
paymentMethodIdStr = paymentMethodId.ToString();
|
||||
|
@ -123,9 +123,10 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
HashSet<CurrencyPair> currencyPairsToFetch = new HashSet<CurrencyPair>();
|
||||
var rules = storeBlob.GetRateRules(_NetworkProvider);
|
||||
|
||||
var excludeFilter = storeBlob.GetExcludedPaymentMethods(); // Here we can compose filters from other origin with PaymentFilter.Any()
|
||||
foreach (var network in store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.Select(c => _NetworkProvider.GetNetwork(c.PaymentId.CryptoCode))
|
||||
.Where(s => !excludeFilter.Match(s.PaymentId))
|
||||
.Select(c => _NetworkProvider.GetNetwork(c.PaymentId.CryptoCode))
|
||||
.Where(c => c != null))
|
||||
{
|
||||
currencyPairsToFetch.Add(new CurrencyPair(network.CryptoCode, invoice.Currency));
|
||||
@ -140,6 +141,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
var fetchingAll = WhenAllFetched(logs, fetchingByCurrencyPair);
|
||||
var supportedPaymentMethods = store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.Where(s => !excludeFilter.Match(s.PaymentId))
|
||||
.Select(c =>
|
||||
(Handler: (IPaymentMethodHandler)_ServiceProvider.GetService(typeof(IPaymentMethodHandler<>).MakeGenericType(c.GetType())),
|
||||
SupportedPaymentMethod: c,
|
||||
@ -209,7 +211,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var rate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, entity.ProductInformation.Currency)];
|
||||
if (rate.Value == null)
|
||||
if (rate.BidAsk == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@ -217,7 +219,7 @@ namespace BTCPayServer.Controllers
|
||||
paymentMethod.ParentEntity = entity;
|
||||
paymentMethod.Network = network;
|
||||
paymentMethod.SetId(supportedPaymentMethod.PaymentId);
|
||||
paymentMethod.Rate = rate.Value.Value;
|
||||
paymentMethod.Rate = rate.BidAsk.Bid;
|
||||
var paymentDetails = await handler.CreatePaymentMethodDetails(supportedPaymentMethod, paymentMethod, store, network);
|
||||
if (storeBlob.NetworkFeeDisabled)
|
||||
paymentDetails.SetNoTxFee();
|
||||
@ -244,9 +246,9 @@ namespace BTCPayServer.Controllers
|
||||
if (compare != null)
|
||||
{
|
||||
var limitValueRate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, limitValue.Currency)];
|
||||
if (limitValueRate.Value.HasValue)
|
||||
if (limitValueRate.BidAsk != null)
|
||||
{
|
||||
var limitValueCrypto = Money.Coins(limitValue.Value / limitValueRate.Value.Value);
|
||||
var limitValueCrypto = Money.Coins(limitValue.Value / limitValueRate.BidAsk.Bid);
|
||||
if (compare(paymentMethod.Calculate().Due, limitValueCrypto))
|
||||
{
|
||||
logs.Write($"{supportedPaymentMethod.PaymentId.CryptoCode}: {errorMessage}");
|
||||
|
@ -31,6 +31,49 @@ namespace BTCPayServer.Controllers
|
||||
_CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
||||
}
|
||||
|
||||
[Route("rates/{baseCurrency}")]
|
||||
[HttpGet]
|
||||
[BitpayAPIConstraint]
|
||||
public async Task<IActionResult> GetBaseCurrencyRates(string baseCurrency, string storeId)
|
||||
{
|
||||
storeId = storeId ?? this.HttpContext.GetStoreData()?.Id;
|
||||
var store = this.HttpContext.GetStoreData();
|
||||
if (store == null || store.Id != storeId)
|
||||
store = await _StoreRepo.FindStore(storeId);
|
||||
if (store == null)
|
||||
{
|
||||
var err = Json(new BitpayErrorsModel() { Error = "Store not found" });
|
||||
err.StatusCode = 404;
|
||||
return err;
|
||||
}
|
||||
var supportedMethods = store.GetSupportedPaymentMethods(_NetworkProvider);
|
||||
|
||||
var currencyCodes = supportedMethods.Where(method => !string.IsNullOrEmpty(method.PaymentId.CryptoCode))
|
||||
.Select(method => method.PaymentId.CryptoCode).Distinct();
|
||||
|
||||
var currencypairs = BuildCurrencyPairs(currencyCodes, baseCurrency);
|
||||
|
||||
var result = await GetRates2(currencypairs, store.Id);
|
||||
var rates = (result as JsonResult)?.Value as Rate[];
|
||||
if (rates == null)
|
||||
return result;
|
||||
return Json(new DataWrapper<Rate[]>(rates));
|
||||
}
|
||||
|
||||
|
||||
[Route("rates/{baseCurrency}/{currency}")]
|
||||
[HttpGet]
|
||||
[BitpayAPIConstraint]
|
||||
public async Task<IActionResult> GetCurrencyPairRate(string baseCurrency, string currency, string storeId)
|
||||
{
|
||||
storeId = storeId ?? this.HttpContext.GetStoreData()?.Id;
|
||||
var result = await GetRates2($"{baseCurrency}_{currency}", storeId);
|
||||
var rates = (result as JsonResult)?.Value as Rate[];
|
||||
if (rates == null)
|
||||
return result;
|
||||
return Json(new DataWrapper<Rate>(rates.First()));
|
||||
}
|
||||
|
||||
[Route("rates")]
|
||||
[HttpGet]
|
||||
[BitpayAPIConstraint]
|
||||
@ -44,19 +87,19 @@ namespace BTCPayServer.Controllers
|
||||
return Json(new DataWrapper<Rate[]>(rates));
|
||||
}
|
||||
|
||||
|
||||
[Route("api/rates")]
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> GetRates2(string currencyPairs, string storeId)
|
||||
{
|
||||
if(storeId == null || currencyPairs == null)
|
||||
if (storeId == null)
|
||||
{
|
||||
var result = Json(new BitpayErrorsModel() { Error = "You need to specify storeId (in your store settings) and currencyPairs (eg. BTC_USD,LTC_CAD)" });
|
||||
var result = Json(new BitpayErrorsModel() { Error = "You need to specify storeId (in your store settings)" });
|
||||
result.StatusCode = 400;
|
||||
return result;
|
||||
}
|
||||
|
||||
var store = this.HttpContext.GetStoreData();
|
||||
if(store == null || store.Id != storeId)
|
||||
if (store == null || store.Id != storeId)
|
||||
store = await _StoreRepo.FindStore(storeId);
|
||||
if (store == null)
|
||||
{
|
||||
@ -64,12 +107,30 @@ namespace BTCPayServer.Controllers
|
||||
result.StatusCode = 404;
|
||||
return result;
|
||||
}
|
||||
|
||||
if (currencyPairs == null)
|
||||
{
|
||||
var supportedMethods = store.GetSupportedPaymentMethods(_NetworkProvider);
|
||||
var currencyCodes = supportedMethods.Select(method => method.PaymentId.CryptoCode).Distinct();
|
||||
var defaultCrypto = store.GetDefaultCrypto(_NetworkProvider);
|
||||
|
||||
currencyPairs = BuildCurrencyPairs(currencyCodes, defaultCrypto);
|
||||
|
||||
if (string.IsNullOrEmpty(currencyPairs))
|
||||
{
|
||||
var result = Json(new BitpayErrorsModel() { Error = "You need to specify currencyPairs (eg. BTC_USD,LTC_CAD)" });
|
||||
result.StatusCode = 400;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var rules = store.GetStoreBlob().GetRateRules(_NetworkProvider);
|
||||
|
||||
HashSet<CurrencyPair> pairs = new HashSet<CurrencyPair>();
|
||||
foreach(var currency in currencyPairs.Split(','))
|
||||
foreach (var currency in currencyPairs.Split(','))
|
||||
{
|
||||
if(!CurrencyPair.TryParse(currency, out var pair))
|
||||
if (!CurrencyPair.TryParse(currency, out var pair))
|
||||
{
|
||||
var result = Json(new BitpayErrorsModel() { Error = $"Currency pair {currency} uncorrectly formatted" });
|
||||
result.StatusCode = 400;
|
||||
@ -81,7 +142,7 @@ namespace BTCPayServer.Controllers
|
||||
var fetching = _RateProviderFactory.FetchRates(pairs, rules);
|
||||
await Task.WhenAll(fetching.Select(f => f.Value).ToArray());
|
||||
return Json(pairs
|
||||
.Select(r => (Pair: r, Value: fetching[r].GetAwaiter().GetResult().Value))
|
||||
.Select(r => (Pair: r, Value: fetching[r].GetAwaiter().GetResult().BidAsk?.Bid))
|
||||
.Where(r => r.Value.HasValue)
|
||||
.Select(r =>
|
||||
new Rate()
|
||||
@ -94,6 +155,20 @@ namespace BTCPayServer.Controllers
|
||||
}).Where(n => n.Name != null).ToArray());
|
||||
}
|
||||
|
||||
private static string BuildCurrencyPairs(IEnumerable<string> currencyCodes, string baseCrypto)
|
||||
{
|
||||
StringBuilder currencyPairsBuilder = new StringBuilder();
|
||||
bool first = true;
|
||||
foreach (var currencyCode in currencyCodes)
|
||||
{
|
||||
if(!first)
|
||||
currencyPairsBuilder.Append(",");
|
||||
first = false;
|
||||
currencyPairsBuilder.Append($"{baseCrypto}_{currencyCode}");
|
||||
}
|
||||
return currencyPairsBuilder.ToString();
|
||||
}
|
||||
|
||||
public class Rate
|
||||
{
|
||||
|
||||
|
@ -32,6 +32,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
private UserManager<ApplicationUser> _UserManager;
|
||||
SettingsRepository _SettingsRepository;
|
||||
private readonly NBXplorerDashboard _dashBoard;
|
||||
private BTCPayRateProviderFactory _RateProviderFactory;
|
||||
private StoreRepository _StoreRepository;
|
||||
LightningConfigurationProvider _LnConfigProvider;
|
||||
@ -41,12 +42,14 @@ namespace BTCPayServer.Controllers
|
||||
Configuration.BTCPayServerOptions options,
|
||||
BTCPayRateProviderFactory rateProviderFactory,
|
||||
SettingsRepository settingsRepository,
|
||||
NBXplorerDashboard dashBoard,
|
||||
LightningConfigurationProvider lnConfigProvider,
|
||||
Services.Stores.StoreRepository storeRepository)
|
||||
{
|
||||
_Options = options;
|
||||
_UserManager = userManager;
|
||||
_SettingsRepository = settingsRepository;
|
||||
_dashBoard = dashBoard;
|
||||
_RateProviderFactory = rateProviderFactory;
|
||||
_StoreRepository = storeRepository;
|
||||
_LnConfigProvider = lnConfigProvider;
|
||||
@ -407,6 +410,11 @@ namespace BTCPayServer.Controllers
|
||||
[Route("server/services/lnd-grpc/{cryptoCode}/{index}")]
|
||||
public IActionResult LNDGRPCServices(string cryptoCode, int index, uint? nonce)
|
||||
{
|
||||
if(!_dashBoard.IsFullySynched(cryptoCode, out var unusud))
|
||||
{
|
||||
StatusMessage = $"Error: {cryptoCode} is not fully synched";
|
||||
return RedirectToAction(nameof(Services));
|
||||
}
|
||||
var external = GetExternalLNDConnectionString(cryptoCode, index);
|
||||
if (external == null)
|
||||
return NotFound();
|
||||
|
@ -44,6 +44,7 @@ namespace BTCPayServer.Controllers
|
||||
private void SetExistingValues(StoreData store, DerivationSchemeViewModel vm)
|
||||
{
|
||||
vm.DerivationScheme = GetExistingDerivationStrategy(vm.CryptoCode, store)?.DerivationStrategyBase.ToString();
|
||||
vm.Enabled = !store.GetStoreBlob().IsExcluded(new PaymentMethodId(vm.CryptoCode, PaymentTypes.BTCLike));
|
||||
}
|
||||
|
||||
private DerivationStrategy GetExistingDerivationStrategy(string cryptoCode, StoreData store)
|
||||
@ -78,6 +79,11 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
|
||||
var exisingStrategy = store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.Where(c => c.PaymentId == paymentMethodId)
|
||||
.OfType<DerivationStrategy>()
|
||||
.Select(c => c.DerivationStrategyBase.ToString())
|
||||
.FirstOrDefault();
|
||||
DerivationStrategy strategy = null;
|
||||
try
|
||||
{
|
||||
@ -94,10 +100,32 @@ namespace BTCPayServer.Controllers
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
if (!vm.Confirmation && strategy != null)
|
||||
return ShowAddresses(vm, strategy);
|
||||
var showAddress = (vm.Confirmation && !string.IsNullOrWhiteSpace(vm.HintAddress)) || // Testing hint address
|
||||
(!vm.Confirmation && strategy != null && exisingStrategy != strategy.DerivationStrategyBase.ToString()); // Checking addresses after setting xpub
|
||||
|
||||
if (vm.Confirmation && !string.IsNullOrWhiteSpace(vm.HintAddress))
|
||||
if (!showAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (strategy != null)
|
||||
await wallet.TrackAsync(strategy.DerivationStrategyBase);
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, strategy);
|
||||
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
storeBlob.SetExcluded(paymentMethodId, !vm.Enabled);
|
||||
store.SetStoreBlob(storeBlob);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid Derivation Scheme");
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
await _Repo.UpdateStore(store);
|
||||
StatusMessage = $"Derivation scheme for {network.CryptoCode} has been modified.";
|
||||
return RedirectToAction(nameof(UpdateStore), new { storeId = storeId });
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(vm.HintAddress))
|
||||
{
|
||||
BitcoinAddress address = null;
|
||||
try
|
||||
@ -123,26 +151,8 @@ namespace BTCPayServer.Controllers
|
||||
vm.StatusMessage = "Address successfully found, please verify that the rest is correct and click on \"Confirm\"";
|
||||
ModelState.Remove(nameof(vm.HintAddress));
|
||||
ModelState.Remove(nameof(vm.DerivationScheme));
|
||||
return ShowAddresses(vm, strategy);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
if (strategy != null)
|
||||
await wallet.TrackAsync(strategy.DerivationStrategyBase);
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, strategy);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid Derivation Scheme");
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
await _Repo.UpdateStore(store);
|
||||
StatusMessage = $"Derivation scheme for {network.CryptoCode} has been modified.";
|
||||
return RedirectToAction(nameof(UpdateStore), new { storeId = storeId });
|
||||
}
|
||||
return ShowAddresses(vm, strategy);
|
||||
}
|
||||
|
||||
private IActionResult ShowAddresses(DerivationSchemeViewModel vm, DerivationStrategy strategy)
|
||||
|
@ -24,9 +24,11 @@ namespace BTCPayServer.Controllers
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
LightningNodeViewModel vm = new LightningNodeViewModel();
|
||||
vm.CryptoCode = cryptoCode;
|
||||
vm.InternalLightningNode = GetInternalLighningNode(cryptoCode)?.ToString();
|
||||
LightningNodeViewModel vm = new LightningNodeViewModel
|
||||
{
|
||||
CryptoCode = cryptoCode,
|
||||
InternalLightningNode = GetInternalLighningNode(cryptoCode)?.ToString()
|
||||
};
|
||||
SetExistingValues(store, vm);
|
||||
return View(vm);
|
||||
}
|
||||
@ -34,6 +36,7 @@ namespace BTCPayServer.Controllers
|
||||
private void SetExistingValues(StoreData store, LightningNodeViewModel vm)
|
||||
{
|
||||
vm.ConnectionString = GetExistingLightningSupportedPaymentMethod(vm.CryptoCode, store)?.GetLightningUrl()?.ToString();
|
||||
vm.Enabled = !store.GetStoreBlob().IsExcluded(new PaymentMethodId(vm.CryptoCode, PaymentTypes.LightningLike));
|
||||
}
|
||||
private LightningSupportedPaymentMethod GetExistingLightningSupportedPaymentMethod(string cryptoCode, StoreData store)
|
||||
{
|
||||
@ -132,41 +135,43 @@ namespace BTCPayServer.Controllers
|
||||
CryptoCode = paymentMethodId.CryptoCode
|
||||
};
|
||||
paymentMethod.SetLightningUrl(connectionString);
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
storeBlob.SetExcluded(paymentMethod.PaymentId , !vm.Enabled);
|
||||
store.SetStoreBlob(storeBlob);
|
||||
}
|
||||
|
||||
if (command == "save")
|
||||
switch (command)
|
||||
{
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod);
|
||||
await _Repo.UpdateStore(store);
|
||||
StatusMessage = $"Lightning node modified ({network.CryptoCode})";
|
||||
return RedirectToAction(nameof(UpdateStore), new { storeId = storeId });
|
||||
}
|
||||
else // if(command == "test")
|
||||
{
|
||||
if (paymentMethod == null)
|
||||
{
|
||||
case "save":
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod);
|
||||
await _Repo.UpdateStore(store);
|
||||
StatusMessage = $"Lightning node modified ({network.CryptoCode})";
|
||||
return RedirectToAction(nameof(UpdateStore), new { storeId = storeId });
|
||||
case "test" when paymentMethod == null:
|
||||
ModelState.AddModelError(nameof(vm.ConnectionString), "Missing url parameter");
|
||||
return View(vm);
|
||||
}
|
||||
var handler = (LightningLikePaymentHandler)_ServiceProvider.GetRequiredService<IPaymentMethodHandler<Payments.Lightning.LightningSupportedPaymentMethod>>();
|
||||
try
|
||||
{
|
||||
var info = await handler.Test(paymentMethod, network);
|
||||
if (!vm.SkipPortTest)
|
||||
case "test":
|
||||
var handler = (LightningLikePaymentHandler)_ServiceProvider.GetRequiredService<IPaymentMethodHandler<Payments.Lightning.LightningSupportedPaymentMethod>>();
|
||||
try
|
||||
{
|
||||
using (CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(20)))
|
||||
var info = await handler.Test(paymentMethod, network);
|
||||
if (!vm.SkipPortTest)
|
||||
{
|
||||
await handler.TestConnection(info, cts.Token);
|
||||
using (CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(20)))
|
||||
{
|
||||
await handler.TestConnection(info, cts.Token);
|
||||
}
|
||||
}
|
||||
vm.StatusMessage = $"Connection to the lightning node succeeded ({info})";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
vm.StatusMessage = $"Error: {ex.Message}";
|
||||
return View(vm);
|
||||
}
|
||||
vm.StatusMessage = $"Connection to the lightning node succeeded ({info})";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
vm.StatusMessage = $"Error: {ex.Message}";
|
||||
return View(vm);
|
||||
}
|
||||
return View(vm);
|
||||
default:
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,7 +179,7 @@ namespace BTCPayServer.Controllers
|
||||
var storeBlob = StoreData.GetStoreBlob();
|
||||
var vm = new RatesViewModel();
|
||||
vm.SetExchangeRates(GetSupportedExchanges(), storeBlob.PreferredExchange ?? CoinAverageRateProvider.CoinAverageName);
|
||||
vm.RateMultiplier = (double)storeBlob.GetRateMultiplier();
|
||||
vm.Spread = (double)(storeBlob.Spread * 100m);
|
||||
vm.Script = storeBlob.GetRateRules(_NetworkProvider).ToString();
|
||||
vm.DefaultScript = storeBlob.GetDefaultRateRules(_NetworkProvider).ToString();
|
||||
vm.AvailableExchanges = GetSupportedExchanges();
|
||||
@ -204,7 +204,7 @@ namespace BTCPayServer.Controllers
|
||||
model.AvailableExchanges = GetSupportedExchanges();
|
||||
|
||||
blob.PreferredExchange = model.PreferredExchange;
|
||||
blob.SetRateMultiplier(model.RateMultiplier);
|
||||
blob.Spread = (decimal)model.Spread / 100.0m;
|
||||
|
||||
if (!model.ShowScripting)
|
||||
{
|
||||
@ -262,7 +262,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
CurrencyPair = fetch.Key.ToString(),
|
||||
Error = testResult.Errors.Count != 0,
|
||||
Rule = testResult.Errors.Count == 0 ? testResult.Rule + " = " + testResult.Value.Value.ToString(CultureInfo.InvariantCulture)
|
||||
Rule = testResult.Errors.Count == 0 ? testResult.Rule + " = " + testResult.BidAsk.Bid.ToString(CultureInfo.InvariantCulture)
|
||||
: testResult.EvaluatedRule
|
||||
});
|
||||
}
|
||||
@ -317,7 +317,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var storeBlob = StoreData.GetStoreBlob();
|
||||
var vm = new CheckoutExperienceViewModel();
|
||||
vm.SetCryptoCurrencies(_ExplorerProvider, StoreData.GetDefaultCrypto());
|
||||
vm.SetCryptoCurrencies(_ExplorerProvider, StoreData.GetDefaultCrypto(_NetworkProvider));
|
||||
vm.SetLanguages(_LangService, storeBlob.DefaultLang);
|
||||
vm.LightningMaxValue = storeBlob.LightningMaxValue?.ToString() ?? "";
|
||||
vm.OnChainMinValue = storeBlob.OnChainMinValue?.ToString() ?? "";
|
||||
@ -352,7 +352,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
bool needUpdate = false;
|
||||
var blob = StoreData.GetStoreBlob();
|
||||
if (StoreData.GetDefaultCrypto() != model.DefaultCryptoCurrency)
|
||||
if (StoreData.GetDefaultCrypto(_NetworkProvider) != model.DefaultCryptoCurrency)
|
||||
{
|
||||
needUpdate = true;
|
||||
StoreData.SetDefaultCrypto(model.DefaultCryptoCurrency);
|
||||
@ -404,7 +404,7 @@ namespace BTCPayServer.Controllers
|
||||
vm.NetworkFee = !storeBlob.NetworkFeeDisabled;
|
||||
vm.SpeedPolicy = store.SpeedPolicy;
|
||||
vm.CanDelete = _Repo.CanDeleteStores();
|
||||
AddPaymentMethods(store, vm);
|
||||
AddPaymentMethods(store, storeBlob, vm);
|
||||
vm.MonitoringExpiration = storeBlob.MonitoringExpiration;
|
||||
vm.InvoiceExpiration = storeBlob.InvoiceExpiration;
|
||||
vm.LightningDescriptionTemplate = storeBlob.LightningDescriptionTemplate;
|
||||
@ -413,8 +413,9 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
|
||||
private void AddPaymentMethods(StoreData store, StoreViewModel vm)
|
||||
private void AddPaymentMethods(StoreData store, StoreBlob storeBlob, StoreViewModel vm)
|
||||
{
|
||||
var excludeFilters = storeBlob.GetExcludedPaymentMethods();
|
||||
var derivationByCryptoCode =
|
||||
store
|
||||
.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
@ -428,6 +429,7 @@ namespace BTCPayServer.Controllers
|
||||
Crypto = network.CryptoCode,
|
||||
Value = strategy?.DerivationStrategyBase?.ToString() ?? string.Empty,
|
||||
WalletId = new WalletId(store.Id, network.CryptoCode),
|
||||
Enabled = !excludeFilters.Match(new Payments.PaymentMethodId(network.CryptoCode, Payments.PaymentTypes.BTCLike))
|
||||
});
|
||||
}
|
||||
|
||||
@ -439,10 +441,12 @@ namespace BTCPayServer.Controllers
|
||||
foreach (var network in _NetworkProvider.GetAll())
|
||||
{
|
||||
var lightning = lightningByCryptoCode.TryGet(network.CryptoCode);
|
||||
var paymentId = new Payments.PaymentMethodId(network.CryptoCode, Payments.PaymentTypes.LightningLike);
|
||||
vm.LightningNodes.Add(new StoreViewModel.LightningNode()
|
||||
{
|
||||
CryptoCode = network.CryptoCode,
|
||||
Address = lightning?.GetLightningUrl()?.BaseUri.AbsoluteUri ?? string.Empty
|
||||
Address = lightning?.GetLightningUrl()?.BaseUri.AbsoluteUri ?? string.Empty,
|
||||
Enabled = !excludeFilters.Match(paymentId)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -130,6 +130,7 @@ namespace BTCPayServer.Controllers
|
||||
vm.Positive = tx.BalanceChange >= Money.Zero;
|
||||
vm.Balance = tx.BalanceChange.ToString();
|
||||
}
|
||||
model.Transactions = model.Transactions.OrderByDescending(t => t.Timestamp).ToList();
|
||||
return View(model);
|
||||
}
|
||||
|
||||
@ -149,7 +150,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
var storeData = store.GetStoreBlob();
|
||||
var rateRules = store.GetStoreBlob().GetRateRules(_NetworkProvider);
|
||||
rateRules.GlobalMultiplier = 1.0m;
|
||||
rateRules.Spread = 0.0m;
|
||||
var currencyPair = new Rating.CurrencyPair(paymentMethod.PaymentId.CryptoCode, GetCurrencyCode(storeData.DefaultLang) ?? "USD");
|
||||
WalletModel model = new WalletModel();
|
||||
model.ServerUrl = GetLedgerWebsocketUrl(this.HttpContext, walletId.CryptoCode, paymentMethod.DerivationStrategyBase);
|
||||
@ -161,9 +162,9 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
cts.CancelAfter(TimeSpan.FromSeconds(5));
|
||||
var result = await _RateProvider.FetchRate(currencyPair, rateRules).WithCancellation(cts.Token);
|
||||
if (result.Value != null)
|
||||
if (result.BidAsk != null)
|
||||
{
|
||||
model.Rate = result.Value;
|
||||
model.Rate = result.BidAsk.Center;
|
||||
model.Divisibility = _currencyTable.GetNumberFormatInfo(currencyPair.Right, true).CurrencyDecimalDigits;
|
||||
model.Fiat = currencyPair.Right;
|
||||
}
|
||||
|
12
BTCPayServer/CorsPolicies.cs
Normal file
12
BTCPayServer/CorsPolicies.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public static class CorsPolicies
|
||||
{
|
||||
public const string All = "BTCPAY_ALL";
|
||||
}
|
||||
}
|
@ -197,9 +197,9 @@ namespace BTCPayServer.Data
|
||||
public IEnumerable<APIKeyData> APIKeys { get; set; }
|
||||
|
||||
#pragma warning disable CS0618
|
||||
public string GetDefaultCrypto()
|
||||
public string GetDefaultCrypto(BTCPayNetworkProvider networkProvider = null)
|
||||
{
|
||||
return DefaultCrypto ?? "BTC";
|
||||
return DefaultCrypto ?? (networkProvider == null ? "BTC" : GetSupportedPaymentMethods(networkProvider).First().PaymentId.CryptoCode);
|
||||
}
|
||||
public void SetDefaultCrypto(string defaultCryptoCurrency)
|
||||
{
|
||||
@ -281,23 +281,9 @@ namespace BTCPayServer.Data
|
||||
set;
|
||||
}
|
||||
|
||||
public void SetRateMultiplier(double rate)
|
||||
{
|
||||
RateRules = new List<RateRule_Obsolete>();
|
||||
RateRules.Add(new RateRule_Obsolete() { Multiplier = rate });
|
||||
}
|
||||
public decimal GetRateMultiplier()
|
||||
{
|
||||
decimal rate = 1.0m;
|
||||
if (RateRules == null || RateRules.Count == 0)
|
||||
return rate;
|
||||
foreach (var rule in RateRules)
|
||||
{
|
||||
rate = rule.Apply(null, rate);
|
||||
}
|
||||
return rate;
|
||||
}
|
||||
public decimal Spread { get; set; } = 0.0m;
|
||||
|
||||
[Obsolete]
|
||||
public List<RateRule_Obsolete> RateRules { get; set; } = new List<RateRule_Obsolete>();
|
||||
public string PreferredExchange { get; set; }
|
||||
|
||||
@ -336,15 +322,15 @@ namespace BTCPayServer.Data
|
||||
|
||||
public BTCPayServer.Rating.RateRules GetRateRules(BTCPayNetworkProvider networkProvider)
|
||||
{
|
||||
if (!RateScripting ||
|
||||
string.IsNullOrEmpty(RateScript) ||
|
||||
if (!RateScripting ||
|
||||
string.IsNullOrEmpty(RateScript) ||
|
||||
!BTCPayServer.Rating.RateRules.TryParse(RateScript, out var rules))
|
||||
{
|
||||
return GetDefaultRateRules(networkProvider);
|
||||
}
|
||||
else
|
||||
{
|
||||
rules.GlobalMultiplier = GetRateMultiplier();
|
||||
rules.Spread = Spread;
|
||||
return rules;
|
||||
}
|
||||
}
|
||||
@ -370,8 +356,37 @@ namespace BTCPayServer.Data
|
||||
builder.AppendLine($"X_X = {preferredExchange}(X_X);");
|
||||
|
||||
BTCPayServer.Rating.RateRules.TryParse(builder.ToString(), out var rules);
|
||||
rules.GlobalMultiplier = GetRateMultiplier();
|
||||
rules.Spread = Spread;
|
||||
return rules;
|
||||
}
|
||||
|
||||
[Obsolete("Use GetExcludedPaymentMethods instead")]
|
||||
public string[] ExcludedPaymentMethods { get; set; }
|
||||
|
||||
public IPaymentFilter GetExcludedPaymentMethods()
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
if (ExcludedPaymentMethods == null || ExcludedPaymentMethods.Length == 0)
|
||||
return PaymentFilter.Never();
|
||||
return PaymentFilter.Any(ExcludedPaymentMethods.Select(p => PaymentFilter.WhereIs(PaymentMethodId.Parse(p))).ToArray());
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
|
||||
public bool IsExcluded(PaymentMethodId paymentMethodId)
|
||||
{
|
||||
return GetExcludedPaymentMethods().Match(paymentMethodId);
|
||||
}
|
||||
|
||||
public void SetExcluded(PaymentMethodId paymentMethodId, bool value)
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
var methods = new HashSet<string>(ExcludedPaymentMethods ?? Array.Empty<string>());
|
||||
if (value)
|
||||
methods.Add(paymentMethodId.ToString());
|
||||
else
|
||||
methods.Remove(paymentMethodId.ToString());
|
||||
ExcludedPaymentMethods = methods.ToArray();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ namespace BTCPayServer
|
||||
|
||||
public BTCPayNetwork Network { get { return this._Network; } }
|
||||
|
||||
public DerivationStrategyBase DerivationStrategyBase { get { return this._DerivationStrategy; } }
|
||||
public DerivationStrategyBase DerivationStrategyBase => this._DerivationStrategy;
|
||||
|
||||
public PaymentMethodId PaymentId => new PaymentMethodId(Network.CryptoCode, PaymentTypes.BTCLike);
|
||||
|
||||
|
@ -137,6 +137,23 @@ namespace BTCPayServer
|
||||
request.PathBase.ToUriComponent());
|
||||
}
|
||||
|
||||
public static string GetCurrentUrl(this HttpRequest request)
|
||||
{
|
||||
return string.Concat(
|
||||
request.Scheme,
|
||||
"://",
|
||||
request.Host.ToUriComponent(),
|
||||
request.PathBase.ToUriComponent(),
|
||||
request.Path.ToUriComponent());
|
||||
}
|
||||
|
||||
public static string GetCurrentPath(this HttpRequest request)
|
||||
{
|
||||
return string.Concat(
|
||||
request.PathBase.ToUriComponent(),
|
||||
request.Path.ToUriComponent());
|
||||
}
|
||||
|
||||
public static string GetAbsoluteUri(this HttpRequest request, string redirectUrl)
|
||||
{
|
||||
bool isRelative =
|
||||
|
@ -202,7 +202,8 @@ namespace BTCPayServer.HostedServices
|
||||
PaymentSubtotals = dto.PaymentSubtotals,
|
||||
PaymentTotals = dto.PaymentTotals,
|
||||
AmountPaid = dto.AmountPaid,
|
||||
ExchangeRates = dto.ExchangeRates
|
||||
ExchangeRates = dto.ExchangeRates,
|
||||
|
||||
};
|
||||
|
||||
// We keep backward compatibility with bitpay by passing BTC info to the notification
|
||||
|
@ -53,6 +53,12 @@ namespace BTCPayServer.HostedServices
|
||||
settings.UnreachableStoreCheck = true;
|
||||
await _Settings.UpdateSetting(settings);
|
||||
}
|
||||
if (!settings.ConvertMultiplierToSpread)
|
||||
{
|
||||
await ConvertMultiplierToSpread();
|
||||
settings.ConvertMultiplierToSpread = true;
|
||||
await _Settings.UpdateSetting(settings);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -61,6 +67,31 @@ namespace BTCPayServer.HostedServices
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ConvertMultiplierToSpread()
|
||||
{
|
||||
using (var ctx = _DBContextFactory.CreateContext())
|
||||
{
|
||||
foreach (var store in await ctx.Stores.ToArrayAsync())
|
||||
{
|
||||
var blob = store.GetStoreBlob();
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
decimal multiplier = 1.0m;
|
||||
if (blob.RateRules != null && blob.RateRules.Count != 0)
|
||||
{
|
||||
foreach (var rule in blob.RateRules)
|
||||
{
|
||||
multiplier = rule.Apply(null, multiplier);
|
||||
}
|
||||
}
|
||||
blob.RateRules = null;
|
||||
blob.Spread = Math.Min(1.0m, Math.Max(0m, -(multiplier - 1.0m)));
|
||||
store.SetStoreBlob(blob);
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
}
|
||||
await ctx.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private Task UnreachableStoreCheck()
|
||||
{
|
||||
return _StoreRepository.CleanUnreachableStores();
|
||||
|
@ -157,6 +157,10 @@ namespace BTCPayServer.Hosting
|
||||
return bundle;
|
||||
});
|
||||
|
||||
services.AddCors(options=>
|
||||
{
|
||||
options.AddPolicy(CorsPolicies.All, p=>p.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
|
||||
});
|
||||
return services;
|
||||
}
|
||||
|
||||
|
@ -100,7 +100,7 @@ namespace BTCPayServer.Hosting
|
||||
(isJson || httpContext.Request.Query.ContainsKey("token")))
|
||||
return true;
|
||||
|
||||
if (path.Equals("/rates", StringComparison.OrdinalIgnoreCase) &&
|
||||
if (path.StartsWith("/rates", StringComparison.OrdinalIgnoreCase) &&
|
||||
httpContext.Request.Method == "GET")
|
||||
return true;
|
||||
|
||||
|
@ -155,6 +155,7 @@ namespace BTCPayServer.Hosting
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
app.UseCors();
|
||||
app.UsePayServer();
|
||||
app.UseStaticFiles();
|
||||
app.UseAuthentication();
|
||||
|
@ -27,6 +27,7 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
[Display(Name = "Hint address")]
|
||||
public string HintAddress { get; set; }
|
||||
public bool Confirmation { get; set; }
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
public string ServerUrl { get; set; }
|
||||
public string StatusMessage { get; internal set; }
|
||||
|
@ -24,5 +24,6 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
public string StatusMessage { get; set; }
|
||||
public string InternalLightningNode { get; internal set; }
|
||||
public bool SkipPortTest { get; set; }
|
||||
public bool Enabled { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
@ -44,9 +44,9 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
public string ScriptTest { get; set; }
|
||||
public CoinAverageExchange[] AvailableExchanges { get; set; }
|
||||
|
||||
[Display(Name = "Multiply the rate by... (Setting to 1.01 would apply a discount of 1% to the purchase)")]
|
||||
[Range(0.01, 10.0)]
|
||||
public double RateMultiplier
|
||||
[Display(Name = "Add a spread on exchange rate of ... %")]
|
||||
[Range(0.0, 100.0)]
|
||||
public double Spread
|
||||
{
|
||||
get;
|
||||
set;
|
||||
|
@ -19,6 +19,7 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
public string Crypto { get; set; }
|
||||
public string Value { get; set; }
|
||||
public WalletId WalletId { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
|
||||
public StoreViewModel()
|
||||
@ -83,6 +84,7 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
{
|
||||
public string CryptoCode { get; set; }
|
||||
public string Address { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
}
|
||||
public List<LightningNode> LightningNodes
|
||||
{
|
||||
|
74
BTCPayServer/Payments/IPaymentFilter.cs
Normal file
74
BTCPayServer/Payments/IPaymentFilter.cs
Normal file
@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Payments
|
||||
{
|
||||
public interface IPaymentFilter
|
||||
{
|
||||
bool Match(PaymentMethodId paymentMethodId);
|
||||
}
|
||||
|
||||
public class PaymentFilter
|
||||
{
|
||||
class NeverPaymentFilter : IPaymentFilter
|
||||
{
|
||||
|
||||
private static readonly NeverPaymentFilter _Instance = new NeverPaymentFilter();
|
||||
public static NeverPaymentFilter Instance
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Instance;
|
||||
}
|
||||
}
|
||||
public bool Match(PaymentMethodId paymentMethodId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
class CompositePaymentFilter : IPaymentFilter
|
||||
{
|
||||
private readonly IPaymentFilter[] _filters;
|
||||
|
||||
public CompositePaymentFilter(IPaymentFilter[] filters)
|
||||
{
|
||||
_filters = filters;
|
||||
}
|
||||
public bool Match(PaymentMethodId paymentMethodId)
|
||||
{
|
||||
return _filters.Any(f => f.Match(paymentMethodId));
|
||||
}
|
||||
}
|
||||
class PaymentIdFilter : IPaymentFilter
|
||||
{
|
||||
private readonly PaymentMethodId _paymentMethodId;
|
||||
|
||||
public PaymentIdFilter(PaymentMethodId paymentMethodId)
|
||||
{
|
||||
_paymentMethodId = paymentMethodId;
|
||||
}
|
||||
public bool Match(PaymentMethodId paymentMethodId)
|
||||
{
|
||||
return paymentMethodId == _paymentMethodId;
|
||||
}
|
||||
}
|
||||
public static IPaymentFilter Never()
|
||||
{
|
||||
return NeverPaymentFilter.Instance;
|
||||
}
|
||||
public static IPaymentFilter Any(IPaymentFilter[] filters)
|
||||
{
|
||||
if (filters == null)
|
||||
throw new ArgumentNullException(nameof(filters));
|
||||
return new CompositePaymentFilter(filters);
|
||||
}
|
||||
public static IPaymentFilter WhereIs(PaymentMethodId paymentMethodId)
|
||||
{
|
||||
if (paymentMethodId == null)
|
||||
throw new ArgumentNullException(nameof(paymentMethodId));
|
||||
return new PaymentIdFilter(paymentMethodId);
|
||||
}
|
||||
}
|
||||
}
|
@ -183,18 +183,24 @@ namespace BTCPayServer.Payments.Lightning.Lnd
|
||||
NodeId = resp.Identity_pubkey
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var node = await _rpcClient.GetNodeInfoAsync(resp.Identity_pubkey, cancellation);
|
||||
if (node.Node.Addresses == null || node.Node.Addresses.Count == 0)
|
||||
throw new Exception("Lnd External IP not set, make sure you use --externalip=$EXTERNALIP parameter on lnd");
|
||||
|
||||
var node = await _rpcClient.GetNodeInfoAsync(resp.Identity_pubkey, cancellation);
|
||||
if (node.Node.Addresses == null || node.Node.Addresses.Count == 0)
|
||||
throw new Exception("Lnd External IP not set, make sure you use --externalip=$EXTERNALIP parameter on lnd");
|
||||
var firstNodeInfo = node.Node.Addresses.First();
|
||||
var externalHostPort = firstNodeInfo.Addr.Split(':');
|
||||
|
||||
var firstNodeInfo = node.Node.Addresses.First();
|
||||
var externalHostPort = firstNodeInfo.Addr.Split(':');
|
||||
nodeInfo.Address = externalHostPort[0];
|
||||
nodeInfo.P2PPort = ConvertInv.ToInt32(externalHostPort[1]);
|
||||
|
||||
nodeInfo.Address = externalHostPort[0];
|
||||
nodeInfo.P2PPort = ConvertInv.ToInt32(externalHostPort[1]);
|
||||
|
||||
return nodeInfo;
|
||||
return nodeInfo;
|
||||
}
|
||||
catch (SwaggerException ex) when (!string.IsNullOrEmpty(ex.Response))
|
||||
{
|
||||
throw new Exception("LND threw an error: " + ex.Response);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<LightningInvoice> GetInvoice(string invoiceId, CancellationToken cancellation = default(CancellationToken))
|
||||
|
@ -138,6 +138,9 @@ namespace BTCPayServer.Rating
|
||||
return _Ask;
|
||||
}
|
||||
}
|
||||
|
||||
public decimal Center => (Ask + Bid) / 2.0m;
|
||||
|
||||
public BidAsk Inverse()
|
||||
{
|
||||
return new BidAsk(1.0m / Ask, 1.0m / Bid);
|
||||
|
@ -99,7 +99,20 @@ namespace BTCPayServer.Rating
|
||||
SyntaxNode root;
|
||||
RuleList ruleList;
|
||||
|
||||
public decimal GlobalMultiplier { get; set; } = 1.0m;
|
||||
decimal _Spread;
|
||||
public decimal Spread
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Spread;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value > 1.0m || value < 0.0m)
|
||||
throw new ArgumentOutOfRangeException(paramName: nameof(value), message: "The spread should be between 0 and 1");
|
||||
_Spread = value;
|
||||
}
|
||||
}
|
||||
|
||||
RateRules(SyntaxNode root)
|
||||
{
|
||||
@ -133,10 +146,12 @@ namespace BTCPayServer.Rating
|
||||
{
|
||||
if (currencyPair.Left == "X" || currencyPair.Right == "X")
|
||||
throw new ArgumentException(paramName: nameof(currencyPair), message: "Invalid X currency");
|
||||
if (currencyPair.Left == currencyPair.Right)
|
||||
return new RateRule(this, currencyPair, CreateExpression("1.0"));
|
||||
var candidate = FindBestCandidate(currencyPair);
|
||||
if (GlobalMultiplier != decimal.One)
|
||||
if (Spread != decimal.Zero)
|
||||
{
|
||||
candidate = CreateExpression($"({candidate}) * {GlobalMultiplier.ToString(CultureInfo.InvariantCulture)}");
|
||||
candidate = CreateExpression($"({candidate}) * ({(1.0m - Spread).ToString(CultureInfo.InvariantCulture)}, {(1.0m + Spread).ToString(CultureInfo.InvariantCulture)})");
|
||||
}
|
||||
return new RateRule(this, currencyPair, candidate);
|
||||
}
|
||||
@ -255,7 +270,7 @@ namespace BTCPayServer.Rating
|
||||
{
|
||||
case SyntaxKind.UnaryMinusExpression:
|
||||
var v = Values.Pop();
|
||||
if(v.Bid == v.Ask)
|
||||
if (v.Bid == v.Ask)
|
||||
{
|
||||
Values.Push(-v);
|
||||
}
|
||||
@ -336,7 +351,7 @@ namespace BTCPayServer.Rating
|
||||
{
|
||||
_TupleValues = new Stack<decimal>();
|
||||
base.VisitTupleExpression(node);
|
||||
if(_TupleValues.Count != 2)
|
||||
if (_TupleValues.Count != 2)
|
||||
{
|
||||
Errors.Add(RateRulesErrors.MissingArgument);
|
||||
}
|
||||
@ -504,7 +519,7 @@ namespace BTCPayServer.Rating
|
||||
|
||||
public bool Reevaluate()
|
||||
{
|
||||
_Value = null;
|
||||
_BidAsk = null;
|
||||
_EvaluatedNode = null;
|
||||
_Evaluated = null;
|
||||
Errors.Clear();
|
||||
@ -524,7 +539,7 @@ namespace BTCPayServer.Rating
|
||||
Errors.AddRange(calculate.Errors);
|
||||
return false;
|
||||
}
|
||||
_Value = calculate.Values.Pop().Bid;
|
||||
_BidAsk = calculate.Values.Pop();
|
||||
_EvaluatedNode = result;
|
||||
return true;
|
||||
}
|
||||
@ -563,12 +578,12 @@ namespace BTCPayServer.Rating
|
||||
return expression.NormalizeWhitespace("", "\n").ToString();
|
||||
}
|
||||
|
||||
decimal? _Value;
|
||||
public decimal? Value
|
||||
BidAsk _BidAsk;
|
||||
public BidAsk BidAsk
|
||||
{
|
||||
get
|
||||
{
|
||||
return _Value;
|
||||
return _BidAsk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,5 +9,6 @@ namespace BTCPayServer.Services
|
||||
{
|
||||
public bool UnreachableStoreCheck { get; set; }
|
||||
public bool DeprecatedLightningConnectionStringCheck { get; set; }
|
||||
public bool ConvertMultiplierToSpread { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ namespace BTCPayServer.Services.Rates
|
||||
public string Rule { get; set; }
|
||||
public string EvaluatedRule { get; set; }
|
||||
public HashSet<RateRulesErrors> Errors { get; set; }
|
||||
public decimal? Value { get; set; }
|
||||
public BidAsk BidAsk { get; set; }
|
||||
public bool Cached { get; internal set; }
|
||||
}
|
||||
|
||||
@ -172,11 +172,11 @@ namespace BTCPayServer.Services.Rates
|
||||
result.ExchangeExceptions.AddRange(query.Exceptions);
|
||||
foreach (var rule in query.ExchangeRates)
|
||||
{
|
||||
rateRule.ExchangeRates.Add(rule);
|
||||
rateRule.ExchangeRates.SetRate(rule.Exchange, rule.CurrencyPair, rule.BidAsk);
|
||||
}
|
||||
}
|
||||
rateRule.Reevaluate();
|
||||
result.Value = rateRule.Value;
|
||||
result.BidAsk = rateRule.BidAsk;
|
||||
result.Errors = rateRule.Errors;
|
||||
result.EvaluatedRule = rateRule.ToString(true);
|
||||
result.Rule = rateRule.ToString(false);
|
||||
|
@ -16,7 +16,7 @@
|
||||
<hr class="primary">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<form asp-route-returnUrl="@ViewData["ReturnUrl"]" method="post">
|
||||
<form asp-route-returnUrl="@ViewData["ReturnUrl"]" asp-route-logon="@ViewData["Logon"]" method="post">
|
||||
<h4>Create a new account.</h4>
|
||||
<hr />
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
|
@ -56,8 +56,7 @@
|
||||
<p><strong>Never</strong> trust anything but <code>id</code>, <strong>ignore</strong> the other fields completely, an attacker can spoof those, they are present only for backward compatibility reason:</p>
|
||||
<p>
|
||||
<ul>
|
||||
<li><strong>Build the invoice's url by yourself</strong> do not trust the <code>url</code> field, this can be spoofed to use attacker's server.</li>
|
||||
<li>Send a <code>GET</code> request to the invoice's url with <code>Content-Type: application/json</code></li>
|
||||
<li>Send a <code>GET</code> request to <code>https://btcpay.example.com/invoices/{invoiceId}</code> with <code>Content-Type: application/json</code></li>
|
||||
<li>Verify that the <code>orderId</code> is from your backend, that the <code>price</code> is correct and that <code>status</code> is either <code>confirmed</code> or <code>complete</code></li>
|
||||
<li>You can then ship your order</li>
|
||||
</ul>
|
||||
|
@ -33,45 +33,52 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="order-details">
|
||||
@if (Model.AvailableCryptos.Count > 1)
|
||||
{
|
||||
<div class="currency-selection">
|
||||
<div class="single-item-order__left">
|
||||
<div style="font-weight: 600;">
|
||||
{{$t("Pay with")}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="single-item-order__right">
|
||||
@if (Model.AvailableCryptos.Count > 1)
|
||||
{
|
||||
<div class="payment__currencies" onclick="openPaymentMethodDialog()">
|
||||
<img v-bind:src="srvModel.cryptoImage" />
|
||||
<span class="clickable_underline">{{srvModel.paymentMethodName}} ({{srvModel.cryptoCode}})</span>
|
||||
<span v-show="srvModel.isLightning">⚡</span>
|
||||
<span class="clickable_indicator fa fa-angle-right"></span>
|
||||
</div>
|
||||
<div id="vexPopupDialog">
|
||||
<ul class="vexmenu">
|
||||
@foreach (var crypto in Model.AvailableCryptos)
|
||||
{
|
||||
<li class="vexmenuitem">
|
||||
<a href="@crypto.Link" onclick="return closePaymentMethodDialog('@crypto.PaymentMethodId');">
|
||||
<img alt="@crypto.PaymentMethodName" src="@crypto.CryptoImage" />
|
||||
@crypto.PaymentMethodName
|
||||
@(crypto.IsLightning ? Html.Raw("⚡") : null)
|
||||
<span>@crypto.CryptoCode</span>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
<div class="payment__spinner">
|
||||
<partial name="Checkout-Spinner" />
|
||||
</div>
|
||||
<div class="currency-selection">
|
||||
<div class="single-item-order__left">
|
||||
<div style="font-weight: 600;">
|
||||
{{$t("Pay with")}}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="single-item-order__right">
|
||||
@if (Model.AvailableCryptos.Count > 1)
|
||||
{
|
||||
<div class="payment__currencies cursorPointer" onclick="openPaymentMethodDialog()">
|
||||
<img v-bind:src="srvModel.cryptoImage" />
|
||||
<span class="clickable_underline">{{srvModel.paymentMethodName}} ({{srvModel.cryptoCode}})</span>
|
||||
<span v-show="srvModel.isLightning">⚡</span>
|
||||
<span class="clickable_indicator fa fa-angle-right"></span>
|
||||
</div>
|
||||
<div id="vexPopupDialog">
|
||||
<ul class="vexmenu">
|
||||
@foreach (var crypto in Model.AvailableCryptos)
|
||||
{
|
||||
<li class="vexmenuitem">
|
||||
<a href="@crypto.Link" onclick="return closePaymentMethodDialog('@crypto.PaymentMethodId');">
|
||||
<img alt="@crypto.PaymentMethodName" src="@crypto.CryptoImage" />
|
||||
@crypto.PaymentMethodName
|
||||
@(crypto.IsLightning ? Html.Raw("⚡") : null)
|
||||
<span>@crypto.CryptoCode</span>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="payment__currencies">
|
||||
<img v-bind:src="srvModel.cryptoImage" />
|
||||
<span>{{srvModel.paymentMethodName}} ({{srvModel.cryptoCode}})</span>
|
||||
<span v-show="srvModel.isLightning">⚡</span>
|
||||
</div>
|
||||
|
||||
}
|
||||
<div class="payment__spinner">
|
||||
<partial name="Checkout-Spinner" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="single-item-order buyerTotalLine">
|
||||
<div class="single-item-order__left">
|
||||
<div class="single-item-order__left__name">
|
||||
|
@ -7,7 +7,7 @@
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<partial name="_StatusMessage" for="StatusMessage" />
|
||||
|
||||
|
||||
<a asp-controller="Account" asp-action="Register" asp-route-returnUrl="@Context.Request.GetCurrentPath()" asp-route-logon="false" class="btn btn-primary" role="button"><span class="fa fa-plus"></span> Create a new user</a>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<partial name="_StatusMessage" for="@TempData["StatusMessage"]" />
|
||||
<partial name="_StatusMessage" for="@TempData["TempDataProperty-StatusMessage"]" />
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
|
@ -1,7 +1,7 @@
|
||||
@model DerivationSchemeViewModel
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData["Title"] = "Add derivation scheme";
|
||||
ViewData["Title"] = $"{Model.CryptoCode} Derivation scheme";
|
||||
ViewData.AddActivePage(BTCPayServer.Views.Stores.StoreNavPages.Index);
|
||||
}
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<form method="post">
|
||||
@if (!Model.Confirmation)
|
||||
@if(!Model.Confirmation)
|
||||
{
|
||||
<div class="form-group">
|
||||
<h5>Derivation Scheme</h5>
|
||||
@ -33,7 +33,7 @@
|
||||
<div id="ledger-info" class="form-text text-muted" style="display: none;">
|
||||
<span>A ledger wallet is detected, which account do you want to use?</span>
|
||||
<ul>
|
||||
@for (int i = 0; i < 4; i++)
|
||||
@for(int i = 0; i < 4; i++)
|
||||
{
|
||||
<li><a class="ledger-info-recommended" data-ledgeraccount="@i" href="#">Account @i (<span>@Model.RootKeyPath.Derive(i, true)</span>)</a></li>
|
||||
}
|
||||
@ -77,7 +77,11 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<button name="command" type="submit" class="btn btn-primary">Continue</button>
|
||||
<div class="form-group">
|
||||
<label asp-for="Enabled"></label>
|
||||
<input asp-for="Enabled" type="checkbox" class="form-check" />
|
||||
</div>
|
||||
<button name="command" type="submit" class="btn btn-primary" value="save">Continue</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -87,6 +91,7 @@
|
||||
</div>
|
||||
<input type="hidden" asp-for="Confirmation" />
|
||||
<input type="hidden" asp-for="DerivationScheme" />
|
||||
<input type="hidden" asp-for="Enabled" />
|
||||
<div class="form-group">
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead>
|
||||
@ -96,7 +101,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var sample in Model.AddressSamples)
|
||||
@foreach(var sample in Model.AddressSamples)
|
||||
{
|
||||
<tr>
|
||||
<td>@sample.KeyPath</td>
|
||||
@ -116,7 +121,7 @@
|
||||
<input asp-for="HintAddress" class="form-control" />
|
||||
<span asp-validation-for="HintAddress" class="text-danger"></span>
|
||||
</div>
|
||||
<button name="command" type="submit" class="btn btn-primary">Confirm</button>
|
||||
<button name="command" type="submit" class="btn btn-primary" value="save">Confirm</button>
|
||||
}
|
||||
</form>
|
||||
</div>
|
||||
|
@ -62,6 +62,10 @@
|
||||
</p>
|
||||
}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Enabled"></label>
|
||||
<input asp-for="Enabled" type="checkbox" class="form-check" />
|
||||
</div>
|
||||
<button name="command" type="submit" value="save" class="btn btn-primary">Submit</button>
|
||||
<button name="command" type="submit" value="test" class="btn btn-secondary">Test connection</button>
|
||||
</form>
|
||||
|
@ -93,18 +93,22 @@
|
||||
X_X = gdax(X_X);
|
||||
</code>
|
||||
</pre>
|
||||
<p>With <code>DOGE_USD</code> will be expanded to <code>bittrex(DOGE_BTC) * gdax(BTC_USD)</code>. And <code>DOGE_CAD</code> will be expanded to <code>bittrex(DOGE_BTC) * quadrigacx(BTC_CAD)</code>. <br />
|
||||
However, we advise you to write it that way to increase coverage so that <code>DOGE_BTC</code> is also supported:</p>
|
||||
<p>
|
||||
With <code>DOGE_USD</code> will be expanded to <code>bittrex(DOGE_BTC) * gdax(BTC_USD)</code>. And <code>DOGE_CAD</code> will be expanded to <code>bittrex(DOGE_BTC) * quadrigacx(BTC_CAD)</code>. <br />
|
||||
However, we advise you to write it that way to increase coverage so that <code>DOGE_BTC</code> is also supported:
|
||||
</p>
|
||||
<pre>
|
||||
<code>
|
||||
<code>
|
||||
DOGE_X = DOGE_BTC * BTC_X
|
||||
DOGE_BTC = bittrex(DOGE_BTC)
|
||||
X_CAD = quadrigacx(X_CAD);
|
||||
X_X = gdax(X_X);
|
||||
</code>
|
||||
</pre>
|
||||
<p>It is worth noting that the inverses of those pairs are automatically supported as well.<br />
|
||||
It means that the rule <code>USD_DOGE = 1 / DOGE_USD</code> implicitely exists.</p>
|
||||
<p>
|
||||
It is worth noting that the inverses of those pairs are automatically supported as well.<br />
|
||||
It means that the rule <code>USD_DOGE = 1 / DOGE_USD</code> implicitely exists.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@ -132,9 +136,14 @@
|
||||
</div>
|
||||
}
|
||||
<div class="form-group">
|
||||
<label asp-for="RateMultiplier"></label>
|
||||
<input asp-for="RateMultiplier" class="form-control" />
|
||||
<span asp-validation-for="RateMultiplier" class="text-danger"></span>
|
||||
<label asp-for="Spread"></label>
|
||||
<div class="input-group">
|
||||
<input asp-for="Spread" class="form-control" />
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">%</span>
|
||||
</div>
|
||||
</div>
|
||||
<span asp-validation-for="Spread" class="text-danger"></span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<h5>Testing</h5>
|
||||
|
@ -5,6 +5,21 @@
|
||||
ViewData.AddActivePage(BTCPayServer.Views.Stores.StoreNavPages.Index);
|
||||
}
|
||||
|
||||
|
||||
|
||||
<style type="text/css">
|
||||
.smMaxWidth {
|
||||
max-width: 150px;
|
||||
}
|
||||
|
||||
@@media (min-width: 768px) {
|
||||
.smMaxWidth {
|
||||
max-width: 300px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<h4>@ViewData["Title"]</h4>
|
||||
<partial name="_StatusMessage" for="@TempData["TempDataProperty-StatusMessage"]" />
|
||||
|
||||
@ -70,7 +85,8 @@
|
||||
<tr>
|
||||
<th>Crypto</th>
|
||||
<th>Derivation Scheme</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
<th style="text-align:center;">Enabled</th>
|
||||
<th style="text-align:right;">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@ -78,7 +94,17 @@
|
||||
{
|
||||
<tr>
|
||||
<td>@scheme.Crypto</td>
|
||||
<td style="max-width:300px;overflow:hidden;">@scheme.Value</td>
|
||||
<td class="smMaxWidth text-truncate">@scheme.Value</td>
|
||||
<td style="text-align:center;">
|
||||
@if(scheme.Enabled)
|
||||
{
|
||||
<span class="fa fa-check"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="fa fa-times"></span>
|
||||
}
|
||||
</td>
|
||||
<td style="text-align:right">
|
||||
@if(!string.IsNullOrWhiteSpace(scheme.Value))
|
||||
{
|
||||
@ -106,6 +132,7 @@
|
||||
<tr>
|
||||
<th>Crypto</th>
|
||||
<th>Address</th>
|
||||
<th style="text-align:center;">Enabled</th>
|
||||
<th style="text-align:right">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -114,7 +141,17 @@
|
||||
{
|
||||
<tr>
|
||||
<td>@scheme.CryptoCode</td>
|
||||
<td>@scheme.Address</td>
|
||||
<td class="smMaxWidth text-truncate">@scheme.Address</td>
|
||||
<td style="text-align:center;">
|
||||
@if(scheme.Enabled)
|
||||
{
|
||||
<span class="fa fa-check"></span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="fa fa-times"></span>
|
||||
}
|
||||
</td>
|
||||
<td style="text-align:right"><a asp-action="AddLightningNode" asp-route-cryptoCode="@scheme.CryptoCode">Modify</a></td>
|
||||
</tr>
|
||||
}
|
||||
|
@ -40,9 +40,12 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cursorPointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.payment__currencies {
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.payment__currencies img {
|
||||
|
BIN
BTCPayServer/wwwroot/imlegacy/dash.png
Normal file
BIN
BTCPayServer/wwwroot/imlegacy/dash.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.1 KiB |
@ -29,6 +29,7 @@ In addition to Bitcoin, we support the following crypto currencies:
|
||||
* Feathercoin
|
||||
* Groestlcoin
|
||||
* Litecoin
|
||||
* Dash
|
||||
* Monacoin
|
||||
* Polis
|
||||
* UFO
|
||||
@ -72,4 +73,4 @@ On linux:
|
||||
|
||||
## Other dependencies
|
||||
|
||||
For more information see the documentation [How to deploy a BTCPay server instance](https://github.com/btcpayserver/btcpayserver-doc/blob/master/Deployment.md).
|
||||
For more information see the documentation [How to deploy a BTCPay server instance](https://github.com/btcpayserver/btcpayserver-doc/#deployment).
|
||||
|
Reference in New Issue
Block a user