diff --git a/BTCPayServer.Rating/RateRules.cs b/BTCPayServer.Rating/RateRules.cs index 2b47b34fa..c91c90d77 100644 --- a/BTCPayServer.Rating/RateRules.cs +++ b/BTCPayServer.Rating/RateRules.cs @@ -99,6 +99,8 @@ namespace BTCPayServer.Rating RuleList ruleList; decimal _Spread; + private const string ImplicitSatsRule = "SATS_X = SATS_BTC * BTC_X;\nSATS_BTC = 0.00000001;\n"; + public decimal Spread { get @@ -126,6 +128,7 @@ namespace BTCPayServer.Rating } public static bool TryParse(string str, out RateRules rules, out List errors) { + str = ImplicitSatsRule + str; rules = null; errors = null; var expression = CSharpSyntaxTree.ParseText(str, new CSharpParseOptions(LanguageVersion.Default).WithKind(SourceCodeKind.Script)); @@ -195,6 +198,7 @@ namespace BTCPayServer.Rating { return root.NormalizeWhitespace("", "\n") .ToFullString() + .Replace(ImplicitSatsRule, string.Empty, StringComparison.OrdinalIgnoreCase) .Replace("{\n", string.Empty, StringComparison.OrdinalIgnoreCase) .Replace("\n}", string.Empty, StringComparison.OrdinalIgnoreCase); } diff --git a/BTCPayServer.Tests/RateRulesTest.cs b/BTCPayServer.Tests/RateRulesTest.cs index 8a508cede..66fd9bade 100644 --- a/BTCPayServer.Tests/RateRulesTest.cs +++ b/BTCPayServer.Tests/RateRulesTest.cs @@ -56,6 +56,8 @@ namespace BTCPayServer.Tests (Pair: "BTC_CAD", Expected: "coinbase(BTC_CAD)"), (Pair: "DOGE_CAD", Expected: "bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1"), (Pair: "LTC_CAD", Expected: "coinaverage(LTC_CAD) * 1.02"), + (Pair: "SATS_CAD", Expected: "0.00000001 * coinbase(BTC_CAD)"), + (Pair: "Sats_USD", Expected: "0.00000001 * kraken(BTC_USD)") }; foreach (var test in tests) { @@ -102,6 +104,8 @@ namespace BTCPayServer.Tests (Pair: "BTC_CAD", Expected: "coinbase(BTC_CAD)", ExpectedExchangeRates: "coinbase(BTC_CAD)"), (Pair: "DOGE_CAD", Expected: "bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1", ExpectedExchangeRates: "bittrex(DOGE_BTC),coinbase(BTC_CAD)"), (Pair: "LTC_CAD", Expected: "coinaverage(LTC_CAD) * 1.02", ExpectedExchangeRates: "coinaverage(LTC_CAD)"), + (Pair: "SATS_USD", Expected: "0.00000001 * kraken(BTC_USD)", ExpectedExchangeRates: "kraken(BTC_USD)"), + (Pair: "SATS_EUR", Expected: "0.00000001 * coinbase(BTC_EUR)", ExpectedExchangeRates: "coinbase(BTC_EUR)") }; foreach (var test in tests2) { @@ -189,6 +193,37 @@ namespace BTCPayServer.Tests rule2.ExchangeRates.SetRate("coinaverage", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m)); Assert.True(rule2.Reevaluate()); Assert.Equal($"({(1m / 6100m).ToString(CultureInfo.InvariantCulture)}, {(1m / 6000m).ToString(CultureInfo.InvariantCulture)})", rule2.ToString(true)); + + // Make sure defining value in sats works + builder = new StringBuilder(); + builder.AppendLine("BTC_USD = kraken(BTC_USD)"); + builder.AppendLine("BTC_X = coinbase(BTC_X)"); + Assert.True(RateRules.TryParse(builder.ToString(), out rules)); + rule2 = rules.GetRuleFor(CurrencyPair.Parse("SATS_USD")); + rule2.ExchangeRates.SetRate("kraken", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m)); + Assert.True(rule2.Reevaluate()); + Assert.Equal("0.00000001 * (6000, 6100)", rule2.ToString(true)); + Assert.Equal(0.00006m, rule2.BidAsk.Bid); + rule2 = rules.GetRuleFor(CurrencyPair.Parse("USD_SATS")); + rule2.ExchangeRates.SetRate("kraken", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m)); + Assert.True(rule2.Reevaluate()); + Assert.Equal("1 / (0.00000001 * (6000, 6100))", rule2.ToString(true)); + Assert.Equal(1m / 0.000061m, rule2.BidAsk.Bid); + + // testing rounding + rule2 = rules.GetRuleFor(CurrencyPair.Parse("Sats_EUR")); + rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_EUR"), new BidAsk(1.23m, 2.34m)); + Assert.True(rule2.Reevaluate()); + Assert.Equal("0.00000001 * (1.23, 2.34)", rule2.ToString(true)); + Assert.Equal(0.0000000234m, rule2.BidAsk.Ask); + Assert.Equal(0.0000000123m, rule2.BidAsk.Bid); + + rule2 = rules.GetRuleFor(CurrencyPair.Parse("EUR_Sats")); + rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_EUR"), new BidAsk(1.23m, 2.34m)); + Assert.True(rule2.Reevaluate()); + Assert.Equal("1 / (0.00000001 * (1.23, 2.34))", rule2.ToString(true)); + Assert.Equal(1m / 0.0000000123m, rule2.BidAsk.Ask); + Assert.Equal(1m / 0.0000000234m, rule2.BidAsk.Bid); } } } diff --git a/BTCPayServer/Services/Rates/CurrencyNameTable.cs b/BTCPayServer/Services/Rates/CurrencyNameTable.cs index 78362bd34..d35f0ca8e 100644 --- a/BTCPayServer/Services/Rates/CurrencyNameTable.cs +++ b/BTCPayServer/Services/Rates/CurrencyNameTable.cs @@ -98,6 +98,12 @@ namespace BTCPayServer.Services.Rates { AddCurrency(_CurrencyProviders, network.CryptoCode, network.Divisibility, network.CryptoCode); } + + _CurrencyProviders.TryAdd("SATS", + new NumberFormatInfo() + { + CurrencySymbol = "sats", CurrencyDecimalDigits = 0, CurrencyPositivePattern = 3 + }); } return _CurrencyProviders.TryGet(currency.ToUpperInvariant()); } @@ -189,6 +195,15 @@ namespace BTCPayServer.Services.Rates } } + dico.TryAdd("SATS", new CurrencyData() + { + Code = "SATS", + Crypto = true, + Divisibility = 0, + Name = "Satoshis", + Symbol = "Sats", + }); + return dico.Values.ToArray(); }