diff --git a/.circleci/config.yml b/.circleci/config.yml index 19669605e..6d8f06492 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -49,8 +49,10 @@ jobs: LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag # sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-amd64 -f amd64.Dockerfile . + sudo docker build --pull --build-arg CONFIGURATION_NAME=Altcoins-Release -t $DOCKERHUB_REPO:$LATEST_TAG-altcoins-amd64 -f amd64.Dockerfile . sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-amd64 + sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-altcoins-amd64 arm32v7: machine: @@ -63,8 +65,10 @@ jobs: LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag # sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 -f arm32v7.Dockerfile . + sudo docker build --pull --build-arg CONFIGURATION_NAME=Altcoins-Release -t $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm32v7 -f arm32v7.Dockerfile . sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 + sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm32v7 arm64v8: machine: @@ -77,8 +81,10 @@ jobs: LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag # sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-arm64v8 -f arm64v8.Dockerfile . + sudo docker build --build-arg CONFIGURATION_NAME=Altcoins-Release --pull -t $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm64v8 -f arm64v8.Dockerfile . sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm64v8 + sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm64v8 multiarch: machine: @@ -99,6 +105,13 @@ jobs: sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 --os linux --arch arm --variant v7 sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-arm64v8 --os linux --arch arm64 --variant v8 sudo docker manifest push $DOCKERHUB_REPO:$LATEST_TAG -p + + + sudo docker manifest create --amend $DOCKERHUB_REPO:$LATEST_TAG-altcoins $DOCKERHUB_REPO:$LATEST_TAG-altcoins-amd64 $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm32v7 $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm64v8 + sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG-altcoins $DOCKERHUB_REPO:$LATEST_TAG-altcoins-amd64 --os linux --arch amd64 + sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG-altcoins $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm32v7 --os linux --arch arm --variant v7 + sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG-altcoins $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm64v8 --os linux --arch arm64 --variant v8 + sudo docker manifest push $DOCKERHUB_REPO:$LATEST_TAG-altcoins -p workflows: version: 2 diff --git a/.circleci/run-tests.sh b/.circleci/run-tests.sh index ba34c262b..f31cc6674 100755 --- a/.circleci/run-tests.sh +++ b/.circleci/run-tests.sh @@ -3,7 +3,7 @@ set -e cd ../BTCPayServer.Tests docker-compose -v -docker-compose down --v -docker-compose pull -docker-compose build -docker-compose run -e "TEST_FILTERS=$1" tests +docker-compose -f "docker-compose.altcoins.yml" down --v +docker-compose -f "docker-compose.altcoins.yml" pull +docker-compose -f "docker-compose.altcoins.yml" build +docker-compose -f "docker-compose.altcoins.yml" run -e "TEST_FILTERS=$1" tests diff --git a/.vscode/launch.json b/.vscode/launch.json index 83182cff2..f79718485 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,7 +17,7 @@ // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser "serverReadyAction": { "action": "openExternally", - "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + "pattern": "\\bListening on\\s+(https?://\\S+)" }, "env": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/BTCPayServer.Client/BTCPayServer.Client.csproj b/BTCPayServer.Client/BTCPayServer.Client.csproj index b3cd8c197..b136b2a06 100644 --- a/BTCPayServer.Client/BTCPayServer.Client.csproj +++ b/BTCPayServer.Client/BTCPayServer.Client.csproj @@ -1,4 +1,4 @@ - + netstandard2.1 diff --git a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.cs b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.cs index 5b8e7c2a6..dbd0ae0f4 100644 --- a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.cs +++ b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.cs @@ -45,6 +45,7 @@ namespace BTCPayServer _NBXplorerNetworkProvider = new NBXplorerNetworkProvider(networkType); NetworkType = networkType; InitBitcoin(); +#if ALTCOINS InitLiquid(); InitLiquidAssets(); InitLitecoin(); @@ -80,6 +81,7 @@ namespace BTCPayServer // Disabled because of https://twitter.com/Cryptopia_NZ/status/1085084168852291586 //InitBitcoinplus(); //InitUfo(); +#endif } /// diff --git a/BTCPayServer.Common/Altcoins/Liquid/BTCPayNetworkProvider.Liquid.cs b/BTCPayServer.Common/Altcoins/Liquid/BTCPayNetworkProvider.Liquid.cs index bef65cd3f..28537869c 100644 --- a/BTCPayServer.Common/Altcoins/Liquid/BTCPayNetworkProvider.Liquid.cs +++ b/BTCPayServer.Common/Altcoins/Liquid/BTCPayNetworkProvider.Liquid.cs @@ -1,3 +1,4 @@ +#if ALTCOINS using NBitcoin; using NBitcoin.Altcoins; using NBitcoin.Altcoins.Elements; @@ -34,3 +35,4 @@ namespace BTCPayServer } +#endif diff --git a/BTCPayServer.Common/Altcoins/Liquid/BTCPayNetworkProvider.LiquidAssets.cs b/BTCPayServer.Common/Altcoins/Liquid/BTCPayNetworkProvider.LiquidAssets.cs index a14c49c2b..ac729caac 100644 --- a/BTCPayServer.Common/Altcoins/Liquid/BTCPayNetworkProvider.LiquidAssets.cs +++ b/BTCPayServer.Common/Altcoins/Liquid/BTCPayNetworkProvider.LiquidAssets.cs @@ -1,3 +1,4 @@ +#if ALTCOINS using NBitcoin; namespace BTCPayServer @@ -81,3 +82,4 @@ namespace BTCPayServer } +#endif diff --git a/BTCPayServer.Common/Altcoins/Liquid/ElementsLikeBtcPayNetwork.cs b/BTCPayServer.Common/Altcoins/Liquid/ElementsLikeBtcPayNetwork.cs index 47321e83f..711e718e4 100644 --- a/BTCPayServer.Common/Altcoins/Liquid/ElementsLikeBtcPayNetwork.cs +++ b/BTCPayServer.Common/Altcoins/Liquid/ElementsLikeBtcPayNetwork.cs @@ -1,3 +1,4 @@ +#if ALTCOINS using System.Collections.Generic; using System.Linq; using NBitcoin; @@ -58,3 +59,4 @@ namespace BTCPayServer } } } +#endif diff --git a/BTCPayServer.Common/Altcoins/Liquid/LiquidExtensions.cs b/BTCPayServer.Common/Altcoins/Liquid/LiquidExtensions.cs new file mode 100644 index 000000000..91719e198 --- /dev/null +++ b/BTCPayServer.Common/Altcoins/Liquid/LiquidExtensions.cs @@ -0,0 +1,18 @@ +#if ALTCOINS +using System.Collections.Generic; +using System.Linq; + +namespace BTCPayServer +{ + public static class LiquidExtensions + { + public static IEnumerable GetAllElementsSubChains(this BTCPayNetworkProvider networkProvider) + { + var elementsBased = networkProvider.GetAll().OfType(); + var parentChains = elementsBased.Select(network => network.NetworkCryptoCode.ToUpperInvariant()).Distinct(); + return networkProvider.UnfilteredNetworks.GetAll().OfType() + .Where(network => parentChains.Contains(network.NetworkCryptoCode)).Select(network => network.CryptoCode.ToUpperInvariant()); + } + } +} +#endif diff --git a/BTCPayServer.Common/BTCPayServer.Common.csproj b/BTCPayServer.Common/BTCPayServer.Common.csproj index 6293fed52..26581c15f 100644 --- a/BTCPayServer.Common/BTCPayServer.Common.csproj +++ b/BTCPayServer.Common/BTCPayServer.Common.csproj @@ -1,4 +1,4 @@ - + diff --git a/BTCPayServer.Data/BTCPayServer.Data.csproj b/BTCPayServer.Data/BTCPayServer.Data.csproj index 7436b550d..f44139247 100644 --- a/BTCPayServer.Data/BTCPayServer.Data.csproj +++ b/BTCPayServer.Data/BTCPayServer.Data.csproj @@ -1,4 +1,4 @@ - + diff --git a/BTCPayServer.Rating/BTCPayServer.Rating.csproj b/BTCPayServer.Rating/BTCPayServer.Rating.csproj index 9ceef4b02..608f6d7ea 100644 --- a/BTCPayServer.Rating/BTCPayServer.Rating.csproj +++ b/BTCPayServer.Rating/BTCPayServer.Rating.csproj @@ -1,4 +1,4 @@ - + diff --git a/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs b/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs new file mode 100644 index 000000000..8d4cc2769 --- /dev/null +++ b/BTCPayServer.Tests/AltcoinTests/AltcoinTests.cs @@ -0,0 +1,1040 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Runtime.CompilerServices; +using System.Security; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using BTCPayServer.Client; +using BTCPayServer.Client.Models; +using BTCPayServer.Configuration; +using BTCPayServer.Controllers; +using BTCPayServer.Data; +using BTCPayServer.Events; +using BTCPayServer.HostedServices; +using BTCPayServer.Lightning; +using BTCPayServer.Models; +using BTCPayServer.Models.AccountViewModels; +using BTCPayServer.Models.AppViewModels; +using BTCPayServer.Models.InvoicingModels; +using BTCPayServer.Models.ServerViewModels; +using BTCPayServer.Models.StoreViewModels; +using BTCPayServer.Models.WalletViewModels; +using BTCPayServer.Payments; +using BTCPayServer.Payments.Bitcoin; +using BTCPayServer.Payments.Lightning; +using BTCPayServer.Rating; +using BTCPayServer.Security.Bitpay; +using BTCPayServer.Services; +using BTCPayServer.Services.Apps; +using BTCPayServer.Services.Invoices; +using BTCPayServer.Services.Rates; +using BTCPayServer.Tests.Logging; +using BTCPayServer.U2F.Models; +using BTCPayServer.Validation; +using ExchangeSharp; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using NBitcoin; +using NBitcoin.DataEncoders; +using NBitcoin.Payment; +using NBitpayClient; +using NBXplorer.DerivationStrategy; +using NBXplorer.Models; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Schema; +using OpenQA.Selenium; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; +using RatesViewModel = BTCPayServer.Models.StoreViewModels.RatesViewModel; + +namespace BTCPayServer.Tests +{ + public class AltcoinTests + { + public const int TestTimeout = 60_000; + public AltcoinTests(ITestOutputHelper helper) + { + Logs.Tester = new XUnitLog(helper) { Name = "Tests" }; + Logs.LogProvider = new XUnitLogProvider(helper); + } + + [Fact] + [Trait("Integration", "Integration")] + [Trait("Altcoins", "Altcoins")] + [Trait("Lightning", "Lightning")] + public async Task CanAddDerivationSchemes() + { + using (var tester = ServerTester.Create()) + { + tester.ActivateLTC(); + tester.ActivateLightning(); + await tester.StartAsync(); + var user = tester.NewAccount(); + user.GrantAccess(); + user.RegisterDerivationScheme("BTC"); + user.RegisterDerivationScheme("LTC"); + user.RegisterLightningNode("BTC", LightningConnectionType.CLightning); + var btcNetwork = tester.PayTester.Networks.GetNetwork("BTC"); + 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(); + var lightningVM = + (LightningNodeViewModel)Assert.IsType(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(controller.AddLightningNode(user.StoreId, "BTC")) + .Model; + Assert.False(lightningVM.Enabled); + + // Only Enabling/Disabling the payment method must redirect to store page + var derivationVM = (DerivationSchemeViewModel)Assert + .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; + Assert.True(derivationVM.Enabled); + derivationVM.Enabled = false; + Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") + .GetAwaiter().GetResult()); + derivationVM = (DerivationSchemeViewModel)Assert + .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; + Assert.False(derivationVM.Enabled); + + // Clicking next without changing anything should send to the confirmation screen + derivationVM = (DerivationSchemeViewModel)Assert + .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; + derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller + .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; + Assert.True(derivationVM.Confirmation); + + 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); + + // Removing the derivation scheme, should redirect to store page + var oldScheme = derivationVM.DerivationScheme; + derivationVM = (DerivationSchemeViewModel)Assert + .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; + derivationVM.DerivationScheme = null; + Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") + .GetAwaiter().GetResult()); + + // Setting it again should redirect to the confirmation page + derivationVM = (DerivationSchemeViewModel)Assert + .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; + derivationVM.DerivationScheme = oldScheme; + derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller + .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; + Assert.True(derivationVM.Confirmation); + + + //cobo vault file + var content = "{\"ExtPubKey\":\"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\"MasterFingerprint\":\"7a7563b5\",\"DerivationPath\":\"M\\/84'\\/0'\\/0'\",\"CoboVaultFirmwareVersion\":\"1.2.0(BTC-Only)\"}"; + derivationVM = (DerivationSchemeViewModel)Assert + .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; + derivationVM.WalletFile = TestUtils.GetFormFile("wallet3.json", content); + derivationVM.Enabled = true; + derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller + .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; + Assert.True(derivationVM.Confirmation); + Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") + .GetAwaiter().GetResult()); + + //wasabi wallet file + content = + "{\r\n \"EncryptedSecret\": \"6PYWBQ1zsukowsnTNA57UUx791aBuJusm7E4egXUmF5WGw3tcdG3cmTL57\",\r\n \"ChainCode\": \"waSIVbn8HaoovoQg/0t8IS1+ZCxGsJRGFT21i06nWnc=\",\r\n \"MasterFingerprint\": \"7a7563b5\",\r\n \"ExtPubKey\": \"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\r\n \"PasswordVerified\": false,\r\n \"MinGapLimit\": 21,\r\n \"AccountKeyPath\": \"84'/0'/0'\",\r\n \"BlockchainState\": {\r\n \"Network\": \"RegTest\",\r\n \"Height\": \"0\"\r\n },\r\n \"HdPubKeys\": []\r\n}"; + + derivationVM = (DerivationSchemeViewModel)Assert + .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; + derivationVM.WalletFile = TestUtils.GetFormFile("wallet4.json", content); + derivationVM.Enabled = true; + derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller + .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; + Assert.True(derivationVM.Confirmation); + Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") + .GetAwaiter().GetResult()); + + + // Can we upload coldcard settings? (Should fail, we are giving a mainnet file to a testnet network) + derivationVM = (DerivationSchemeViewModel)Assert + .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; + content = + "{\"keystore\": {\"ckcc_xpub\": \"xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw\", \"xpub\": \"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}"; + derivationVM.WalletFile = TestUtils.GetFormFile("wallet.json", content); + derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller + .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; + Assert.False(derivationVM + .Confirmation); // Should fail, we are giving a mainnet file to a testnet network + + // And with a good file? (upub) + content = + "{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"upub5DBYp1qGgsTrkzCptMGZc2x18pquLwGrBw6nS59T4NViZ4cni1mGowQzziy85K8vzkp1jVtWrSkLhqk9KDfvrGeB369wGNYf39kX8rQfiLn\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}"; + derivationVM = (DerivationSchemeViewModel)Assert + .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; + derivationVM.WalletFile = TestUtils.GetFormFile("wallet2.json", content); + derivationVM.Enabled = true; + derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller + .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; + Assert.True(derivationVM.Confirmation); + Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") + .GetAwaiter().GetResult()); + + + // Now let's check that no data has been lost in the process + var store = tester.PayTester.StoreRepository.FindStore(user.StoreId).GetAwaiter().GetResult(); + var onchainBTC = store.GetSupportedPaymentMethods(tester.PayTester.Networks) +#pragma warning disable CS0618 // Type or member is obsolete + .OfType().First(o => o.PaymentId.IsBTCOnChain); +#pragma warning restore CS0618 // Type or member is obsolete + DerivationSchemeSettings.TryParseFromWalletFile(content, onchainBTC.Network, out var expected); + Assert.Equal(expected.ToJson(), onchainBTC.ToJson()); + + // Let's check that the root hdkey and account key path are taken into account when making a PSBT + invoice = user.BitPay.CreateInvoice( + new Invoice() + { + Price = 1.5m, + Currency = "USD", + PosData = "posData", + OrderId = "orderId", + ItemDesc = "Some description", + FullNotifications = true + }, Facade.Merchant); + + tester.ExplorerNode.Generate(1); + var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo.First(c => c.CryptoCode == "BTC").Address, + tester.ExplorerNode.Network); + tester.ExplorerNode.SendToAddress(invoiceAddress, Money.Coins(1m)); + TestUtils.Eventually(() => + { + invoice = user.BitPay.GetInvoice(invoice.Id); + Assert.Equal("paid", invoice.Status); + }); + var wallet = tester.PayTester.GetController(); + var psbt = wallet.CreatePSBT(btcNetwork, onchainBTC, + new WalletSendModel() + { + Outputs = new List() + { + new WalletSendModel.TransactionOutput() + { + Amount = 0.5m, + DestinationAddress = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, btcNetwork.NBitcoinNetwork) + .ToString(), + } + }, + FeeSatoshiPerByte = 1 + }, default).GetAwaiter().GetResult(); + + Assert.NotNull(psbt); + + var root = new Mnemonic( + "usage fever hen zero slide mammal silent heavy donate budget pulse say brain thank sausage brand craft about save attract muffin advance illegal cabbage") + .DeriveExtKey().AsHDKeyCache(); + var account = root.Derive(new KeyPath("m/49'/0'/0'")); + Assert.All(psbt.PSBT.Inputs, input => + { + var keyPath = input.HDKeyPaths.Single(); + Assert.False(keyPath.Value.KeyPath.IsHardened); + Assert.Equal(account.Derive(keyPath.Value.KeyPath).GetPublicKey(), keyPath.Key); + Assert.Equal(keyPath.Value.MasterFingerprint, + onchainBTC.AccountKeySettings[0].AccountKey.GetPublicKey().GetHDFingerPrint()); + }); + } + } + + [Fact(Timeout = TestTimeout)] + [Trait("Integration", "Integration")] + [Trait("Altcoins", "Altcoins")] + [Trait("Lightning", "Lightning")] + public async Task CanCreateInvoiceWithSpecificPaymentMethods() + { + using (var tester = ServerTester.Create()) + { + tester.ActivateLightning(); + tester.ActivateLTC(); + await tester.StartAsync(); + await tester.EnsureChannelsSetup(); + var user = tester.NewAccount(); + user.GrantAccess(); + user.RegisterLightningNode("BTC", LightningConnectionType.Charge); + user.RegisterDerivationScheme("BTC"); + user.RegisterDerivationScheme("LTC"); + + var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(100, "BTC")); + Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count); + + + invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(100, "BTC") + { + SupportedTransactionCurrencies = new Dictionary() + { + {"BTC", new InvoiceSupportedTransactionCurrency() {Enabled = true}} + } + }); + + Assert.Single(invoice.SupportedTransactionCurrencies); + } + } + + [Fact(Timeout = TestTimeout)] + [Trait("Integration", "Integration")] + [Trait("Altcoins", "Altcoins")] + public async Task CanHaveLTCOnlyStore() + { + using (var tester = ServerTester.Create()) + { + tester.ActivateLTC(); + await tester.StartAsync(); + var user = tester.NewAccount(); + user.GrantAccess(); + user.RegisterDerivationScheme("LTC"); + + // First we try payment with a merchant having only BTC + var invoice = user.BitPay.CreateInvoice( + new Invoice() + { + Price = 500, + Currency = "USD", + PosData = "posData", + OrderId = "orderId", + ItemDesc = "Some description", + FullNotifications = true + }, Facade.Merchant); + + Assert.Single(invoice.CryptoInfo); + Assert.Equal("LTC", invoice.CryptoInfo[0].CryptoCode); + Assert.True(invoice.PaymentCodes.ContainsKey("LTC")); + Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("LTC")); + Assert.True(invoice.SupportedTransactionCurrencies["LTC"].Enabled); + Assert.True(invoice.PaymentSubtotals.ContainsKey("LTC")); + Assert.True(invoice.PaymentTotals.ContainsKey("LTC")); + var cashCow = tester.LTCExplorerNode; + var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network); + var firstPayment = Money.Coins(0.1m); + cashCow.SendToAddress(invoiceAddress, firstPayment); + TestUtils.Eventually(() => + { + invoice = user.BitPay.GetInvoice(invoice.Id); + Assert.Equal(firstPayment, invoice.CryptoInfo[0].Paid); + }); + + Assert.Single(invoice.CryptoInfo); // Only BTC should be presented + + var controller = tester.PayTester.GetController(null); + var checkout = + (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null) + .GetAwaiter().GetResult()).Value; + Assert.Single(checkout.AvailableCryptos); + Assert.Equal("LTC", checkout.CryptoCode); + + ////////////////////// + + // Despite it is called BitcoinAddress it should be LTC because BTC is not available + Assert.Null(invoice.BitcoinAddress); + Assert.NotEqual(1.0m, invoice.Rate); + Assert.NotEqual(invoice.BtcDue, invoice.CryptoInfo[0].Due); // Should be BTC rate + cashCow.SendToAddress(invoiceAddress, invoice.CryptoInfo[0].Due); + + TestUtils.Eventually(() => + { + invoice = user.BitPay.GetInvoice(invoice.Id); + Assert.Equal("paid", invoice.Status); + checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null) + .GetAwaiter().GetResult()).Value; + Assert.Equal("paid", checkout.Status); + }); + } + } + + + [Fact] + [Trait("Selenium", "Selenium")] + [Trait("Altcoins", "Altcoins")] + public async Task CanCreateRefunds() + { + using (var s = SeleniumTester.Create()) + { + s.Server.ActivateLTC(); + await s.StartAsync(); + var user = s.Server.NewAccount(); + await user.GrantAccessAsync(); + s.GoToLogin(); + s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password); + user.RegisterDerivationScheme("BTC"); + await s.Server.ExplorerNode.GenerateAsync(1); + + foreach (var multiCurrency in new[] { false, true }) + { + if (multiCurrency) + user.RegisterDerivationScheme("LTC"); + foreach (var rateSelection in new[] { "FiatText", "CurrentRateText", "RateThenText" }) + await CanCreateRefundsCore(s, user, multiCurrency, rateSelection); + } + } + } + + private static async Task CanCreateRefundsCore(SeleniumTester s, TestAccount user, bool multiCurrency, string rateSelection) + { + s.GoToHome(); + s.Server.PayTester.ChangeRate("BTC_USD", new Rating.BidAsk(5000.0m, 5100.0m)); + var invoice = await user.BitPay.CreateInvoiceAsync(new NBitpayClient.Invoice() + { + Currency = "USD", + Price = 5000.0m + }); + var info = invoice.CryptoInfo.First(o => o.CryptoCode == "BTC"); + var totalDue = decimal.Parse(info.TotalDue, CultureInfo.InvariantCulture); + var paid = totalDue + 0.1m; + await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(info.Address, Network.RegTest), Money.Coins(paid)); + await s.Server.ExplorerNode.GenerateAsync(1); + await TestUtils.EventuallyAsync(async () => + { + invoice = await user.BitPay.GetInvoiceAsync(invoice.Id); + Assert.Equal("confirmed", invoice.Status); + }); + + // BTC crash by 50% + s.Server.PayTester.ChangeRate("BTC_USD", new Rating.BidAsk(5000.0m / 2.0m, 5100.0m / 2.0m)); + s.GoToInvoice(invoice.Id); + s.Driver.FindElement(By.Id("refundlink")).Click(); + if (multiCurrency) + { + s.Driver.FindElement(By.Id("SelectedPaymentMethod")).SendKeys("BTC" + Keys.Enter); + s.Driver.FindElement(By.Id("ok")).Click(); + } + Assert.Contains("$5,500.00", s.Driver.PageSource); // Should propose reimburse in fiat + Assert.Contains("1.10000000 ₿", s.Driver.PageSource); // Should propose reimburse in BTC at the rate of before + Assert.Contains("2.20000000 ₿", s.Driver.PageSource); // Should propose reimburse in BTC at the current rate + s.Driver.FindElement(By.Id(rateSelection)).Click(); + s.Driver.FindElement(By.Id("ok")).Click(); + Assert.Contains("pull-payments", s.Driver.Url); + if (rateSelection == "FiatText") + Assert.Contains("$5,500.00", s.Driver.PageSource); + if (rateSelection == "CurrentRateText") + Assert.Contains("2.20000000 ₿", s.Driver.PageSource); + if (rateSelection == "RateThenText") + Assert.Contains("1.10000000 ₿", s.Driver.PageSource); + s.GoToHome(); + s.GoToInvoices(); + s.GoToInvoice(invoice.Id); + s.Driver.FindElement(By.Id("refundlink")).Click(); + Assert.Contains("pull-payments", s.Driver.Url); + } + + [Fact(Timeout = TestTimeout)] + [Trait("Altcoins", "Altcoins")] + [Trait("Lightning", "Lightning")] + public async Task CanUsePaymentMethodDropdown() + { + using (var s = SeleniumTester.Create()) + { + s.Server.ActivateLTC(); + s.Server.ActivateLightning(); + await s.StartAsync(); + s.GoToRegister(); + s.RegisterNewUser(); + var store = s.CreateNewStore(); + s.AddDerivationScheme("BTC"); + + //check that there is no dropdown since only one payment method is set + var invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com"); + s.GoToInvoiceCheckout(invoiceId); + s.Driver.FindElement(By.ClassName("payment__currencies_noborder")); + s.GoToHome(); + s.GoToStore(store.storeId); + s.AddDerivationScheme("LTC"); + s.AddLightningNode("BTC", LightningConnectionType.CLightning); + //there should be three now + invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com"); + s.GoToInvoiceCheckout(invoiceId); + var currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies")); + Assert.Contains("BTC", currencyDropdownButton.Text); + currencyDropdownButton.Click(); + + var elements = s.Driver.FindElement(By.ClassName("vex-content")).FindElements(By.ClassName("vexmenuitem")); + Assert.Equal(3, elements.Count); + elements.Single(element => element.Text.Contains("LTC")).Click(); + currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies")); + Assert.Contains("LTC", currencyDropdownButton.Text); + currencyDropdownButton.Click(); + + elements = s.Driver.FindElement(By.ClassName("vex-content")).FindElements(By.ClassName("vexmenuitem")); + elements.Single(element => element.Text.Contains("Lightning")).Click(); + + currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies")); + Assert.Contains("Lightning", currencyDropdownButton.Text); + + s.Driver.Quit(); + } + } + + [Fact(Timeout = TestTimeout)] + [Trait("Integration", "Integration")] + [Trait("Altcoins", "Altcoins")] + public async Task CanPayWithTwoCurrencies() + { + using (var tester = ServerTester.Create()) + { + tester.ActivateLTC(); + await tester.StartAsync(); + var user = tester.NewAccount(); + user.GrantAccess(); + user.RegisterDerivationScheme("BTC"); + // First we try payment with a merchant having only BTC + var invoice = user.BitPay.CreateInvoice( + new Invoice() + { + Price = 5000.0m, + Currency = "USD", + PosData = "posData", + OrderId = "orderId", + ItemDesc = "Some description", + FullNotifications = true + }, Facade.Merchant); + + var cashCow = tester.ExplorerNode; + cashCow.Generate(2); // get some money in case + var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network); + var firstPayment = Money.Coins(0.04m); + cashCow.SendToAddress(invoiceAddress, firstPayment); + TestUtils.Eventually(() => + { + invoice = user.BitPay.GetInvoice(invoice.Id); + Assert.True(invoice.BtcPaid == firstPayment); + }); + + Assert.Single(invoice.CryptoInfo); // Only BTC should be presented + + var controller = tester.PayTester.GetController(null); + var checkout = + (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null) + .GetAwaiter().GetResult()).Value; + Assert.Single(checkout.AvailableCryptos); + Assert.Equal("BTC", checkout.CryptoCode); + + Assert.Single(invoice.PaymentCodes); + Assert.Single(invoice.SupportedTransactionCurrencies); + Assert.Single(invoice.SupportedTransactionCurrencies); + Assert.Single(invoice.PaymentSubtotals); + Assert.Single(invoice.PaymentTotals); + Assert.True(invoice.PaymentCodes.ContainsKey("BTC")); + Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("BTC")); + Assert.True(invoice.SupportedTransactionCurrencies["BTC"].Enabled); + Assert.True(invoice.PaymentSubtotals.ContainsKey("BTC")); + Assert.True(invoice.PaymentTotals.ContainsKey("BTC")); + ////////////////////// + + // Retry now with LTC enabled + user.RegisterDerivationScheme("LTC"); + invoice = user.BitPay.CreateInvoice( + new Invoice() + { + Price = 5000.0m, + Currency = "USD", + PosData = "posData", + OrderId = "orderId", + ItemDesc = "Some description", + FullNotifications = true + }, Facade.Merchant); + + cashCow = tester.ExplorerNode; + invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network); + firstPayment = Money.Coins(0.04m); + cashCow.SendToAddress(invoiceAddress, firstPayment); + Logs.Tester.LogInformation("First payment sent to " + invoiceAddress); + TestUtils.Eventually(() => + { + invoice = user.BitPay.GetInvoice(invoice.Id); + Assert.True(invoice.BtcPaid == firstPayment); + }); + + cashCow = tester.LTCExplorerNode; + var ltcCryptoInfo = invoice.CryptoInfo.FirstOrDefault(c => c.CryptoCode == "LTC"); + Assert.NotNull(ltcCryptoInfo); + invoiceAddress = BitcoinAddress.Create(ltcCryptoInfo.Address, cashCow.Network); + var secondPayment = Money.Coins(decimal.Parse(ltcCryptoInfo.Due, CultureInfo.InvariantCulture)); + cashCow.Generate(4); // LTC is not worth a lot, so just to make sure we have money... + cashCow.SendToAddress(invoiceAddress, secondPayment); + Logs.Tester.LogInformation("Second payment sent to " + invoiceAddress); + TestUtils.Eventually(() => + { + invoice = user.BitPay.GetInvoice(invoice.Id); + Assert.Equal(Money.Zero, invoice.BtcDue); + var ltcPaid = invoice.CryptoInfo.First(c => c.CryptoCode == "LTC"); + Assert.Equal(Money.Zero, ltcPaid.Due); + Assert.Equal(secondPayment, ltcPaid.CryptoPaid); + Assert.Equal("paid", invoice.Status); + Assert.False((bool)((JValue)invoice.ExceptionStatus).Value); + }); + + controller = tester.PayTester.GetController(null); + checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, "LTC") + .GetAwaiter().GetResult()).Value; + Assert.Equal(2, checkout.AvailableCryptos.Count); + Assert.Equal("LTC", checkout.CryptoCode); + + + Assert.Equal(2, invoice.PaymentCodes.Count()); + Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count()); + Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count()); + Assert.Equal(2, invoice.PaymentSubtotals.Count()); + Assert.Equal(2, invoice.PaymentTotals.Count()); + Assert.True(invoice.PaymentCodes.ContainsKey("LTC")); + Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("LTC")); + Assert.True(invoice.SupportedTransactionCurrencies["LTC"].Enabled); + Assert.True(invoice.PaymentSubtotals.ContainsKey("LTC")); + Assert.True(invoice.PaymentTotals.ContainsKey("LTC")); + + + // Check if we can disable LTC + invoice = user.BitPay.CreateInvoice( + new Invoice() + { + Price = 5000.0m, + Currency = "USD", + PosData = "posData", + OrderId = "orderId", + ItemDesc = "Some description", + FullNotifications = true, + SupportedTransactionCurrencies = new Dictionary() + { + {"BTC", new InvoiceSupportedTransactionCurrency() {Enabled = true}} + } + }, Facade.Merchant); + + Assert.Single(invoice.CryptoInfo.Where(c => c.CryptoCode == "BTC")); + Assert.Empty(invoice.CryptoInfo.Where(c => c.CryptoCode == "LTC")); + } + } + + [Fact] + [Trait("Integration", "Integration")] + [Trait("Altcoins", "Altcoins")] + public async Task CanUsePoSApp() + { + using (var tester = ServerTester.Create()) + { + tester.ActivateLTC(); + await tester.StartAsync(); + var user = tester.NewAccount(); + user.GrantAccess(); + user.RegisterDerivationScheme("BTC"); + user.RegisterDerivationScheme("LTC"); + var apps = user.GetController(); + var vm = Assert.IsType(Assert.IsType(apps.CreateApp().Result).Model); + vm.Name = "test"; + vm.SelectedAppType = AppType.PointOfSale.ToString(); + Assert.IsType(apps.CreateApp(vm).Result); + var appId = Assert.IsType(Assert.IsType(apps.ListApps().Result).Model) + .Apps[0].Id; + var vmpos = Assert.IsType(Assert + .IsType(apps.UpdatePointOfSale(appId).Result).Model); + vmpos.Title = "hello"; + vmpos.Currency = "CAD"; + vmpos.ButtonText = "{0} Purchase"; + vmpos.CustomButtonText = "Nicolas Sexy Hair"; + vmpos.CustomTipText = "Wanna tip?"; + vmpos.CustomTipPercentages = "15,18,20"; + vmpos.Template = @" +apple: + price: 5.0 + title: good apple +orange: + price: 10.0 +donation: + price: 1.02 + custom: true +"; + Assert.IsType(apps.UpdatePointOfSale(appId, vmpos).Result); + vmpos = Assert.IsType(Assert + .IsType(apps.UpdatePointOfSale(appId).Result).Model); + Assert.Equal("hello", vmpos.Title); + + var publicApps = user.GetController(); + var vmview = + Assert.IsType(Assert + .IsType(publicApps.ViewPointOfSale(appId, PosViewType.Cart).Result).Model); + Assert.Equal("hello", vmview.Title); + Assert.Equal(3, vmview.Items.Length); + Assert.Equal("good apple", vmview.Items[0].Title); + Assert.Equal("orange", vmview.Items[1].Title); + Assert.Equal(10.0m, vmview.Items[1].Price.Value); + Assert.Equal("$5.00", vmview.Items[0].Price.Formatted); + Assert.Equal("{0} Purchase", vmview.ButtonText); + Assert.Equal("Nicolas Sexy Hair", vmview.CustomButtonText); + Assert.Equal("Wanna tip?", vmview.CustomTipText); + Assert.Equal("15,18,20", string.Join(',', vmview.CustomTipPercentages)); + Assert.IsType(publicApps + .ViewPointOfSale(appId, PosViewType.Cart, 0, null, null, null, null, "orange").Result); + + // + var invoices = user.BitPay.GetInvoices(); + var orangeInvoice = invoices.First(); + Assert.Equal(10.00m, orangeInvoice.Price); + Assert.Equal("CAD", orangeInvoice.Currency); + Assert.Equal("orange", orangeInvoice.ItemDesc); + + + Assert.IsType(publicApps + .ViewPointOfSale(appId, PosViewType.Cart, 0, null, null, null, null, "apple").Result); + + invoices = user.BitPay.GetInvoices(); + var appleInvoice = invoices.SingleOrDefault(invoice => invoice.ItemCode.Equals("apple")); + Assert.NotNull(appleInvoice); + Assert.Equal("good apple", appleInvoice.ItemDesc); + + + // testing custom amount + var action = Assert.IsType(publicApps + .ViewPointOfSale(appId, PosViewType.Cart, 6.6m, null, null, null, null, "donation").Result); + Assert.Equal(nameof(InvoiceController.Checkout), action.ActionName); + invoices = user.BitPay.GetInvoices(); + var donationInvoice = invoices.Single(i => i.Price == 6.6m); + Assert.NotNull(donationInvoice); + Assert.Equal("CAD", donationInvoice.Currency); + Assert.Equal("donation", donationInvoice.ItemDesc); + + foreach (var test in new[] + { + (Code: "EUR", ExpectedSymbol: "€", ExpectedDecimalSeparator: ",", ExpectedDivisibility: 2, + ExpectedThousandSeparator: "\xa0", ExpectedPrefixed: false, ExpectedSymbolSpace: true), + (Code: "INR", ExpectedSymbol: "₹", ExpectedDecimalSeparator: ".", ExpectedDivisibility: 2, + ExpectedThousandSeparator: ",", ExpectedPrefixed: true, ExpectedSymbolSpace: true), + (Code: "JPY", ExpectedSymbol: "¥", ExpectedDecimalSeparator: ".", ExpectedDivisibility: 0, + ExpectedThousandSeparator: ",", ExpectedPrefixed: true, ExpectedSymbolSpace: false), + (Code: "BTC", ExpectedSymbol: "₿", ExpectedDecimalSeparator: ".", ExpectedDivisibility: 8, + ExpectedThousandSeparator: ",", ExpectedPrefixed: false, ExpectedSymbolSpace: true), + }) + { + Logs.Tester.LogInformation($"Testing for {test.Code}"); + vmpos = Assert.IsType(Assert + .IsType(apps.UpdatePointOfSale(appId).Result).Model); + vmpos.Title = "hello"; + vmpos.Currency = test.Item1; + vmpos.ButtonText = "{0} Purchase"; + vmpos.CustomButtonText = "Nicolas Sexy Hair"; + vmpos.CustomTipText = "Wanna tip?"; + vmpos.Template = @" +apple: + price: 1000.0 + title: good apple +orange: + price: 10.0 +donation: + price: 1.02 + custom: true +"; + Assert.IsType(apps.UpdatePointOfSale(appId, vmpos).Result); + publicApps = user.GetController(); + vmview = Assert.IsType(Assert + .IsType(publicApps.ViewPointOfSale(appId, PosViewType.Cart).Result).Model); + Assert.Equal(test.Code, vmview.CurrencyCode); + Assert.Equal(test.ExpectedSymbol, + vmview.CurrencySymbol.Replace("¥", "¥")); // Hack so JPY test pass on linux as well); + Assert.Equal(test.ExpectedSymbol, + vmview.CurrencyInfo.CurrencySymbol + .Replace("¥", "¥")); // Hack so JPY test pass on linux as well); + Assert.Equal(test.ExpectedDecimalSeparator, vmview.CurrencyInfo.DecimalSeparator); + Assert.Equal(test.ExpectedThousandSeparator, vmview.CurrencyInfo.ThousandSeparator); + Assert.Equal(test.ExpectedPrefixed, vmview.CurrencyInfo.Prefixed); + Assert.Equal(test.ExpectedDivisibility, vmview.CurrencyInfo.Divisibility); + Assert.Equal(test.ExpectedSymbolSpace, vmview.CurrencyInfo.SymbolSpace); + } + + + //test inventory related features + vmpos = Assert.IsType(Assert + .IsType(apps.UpdatePointOfSale(appId).Result).Model); + vmpos.Title = "hello"; + vmpos.Currency = "BTC"; + vmpos.Template = @" +inventoryitem: + price: 1.0 + title: good apple + inventory: 1 +noninventoryitem: + price: 10.0"; + Assert.IsType(apps.UpdatePointOfSale(appId, vmpos).Result); + + //inventoryitem has 1 item available + Assert.IsType(publicApps + .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "inventoryitem").Result); + //we already bought all available stock so this should fail + await Task.Delay(100); + Assert.IsType(publicApps + .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "inventoryitem").Result); + + //inventoryitem has unlimited items available + Assert.IsType(publicApps + .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "noninventoryitem").Result); + Assert.IsType(publicApps + .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "noninventoryitem").Result); + + //verify invoices where created + invoices = user.BitPay.GetInvoices(); + Assert.Equal(2, invoices.Count(invoice => invoice.ItemCode.Equals("noninventoryitem"))); + var inventoryItemInvoice = + Assert.Single(invoices.Where(invoice => invoice.ItemCode.Equals("inventoryitem"))); + Assert.NotNull(inventoryItemInvoice); + + //let's mark the inventoryitem invoice as invalid, thsi should return the item to back in stock + var controller = tester.PayTester.GetController(user.UserId, user.StoreId); + var appService = tester.PayTester.GetService(); + var eventAggregator = tester.PayTester.GetService(); + Assert.IsType(await controller.ChangeInvoiceState(inventoryItemInvoice.Id, "invalid")); + //check that item is back in stock + TestUtils.Eventually(() => + { + vmpos = Assert.IsType(Assert + .IsType(apps.UpdatePointOfSale(appId).Result).Model); + Assert.Equal(1, + appService.Parse(vmpos.Template, "BTC").Single(item => item.Id == "inventoryitem").Inventory); + }, 10000); + + + //test payment methods option + + vmpos = Assert.IsType(Assert + .IsType(apps.UpdatePointOfSale(appId).Result).Model); + vmpos.Title = "hello"; + vmpos.Currency = "BTC"; + vmpos.Template = @" +btconly: + price: 1.0 + title: good apple + payment_methods: + - BTC +normal: + price: 1.0"; + Assert.IsType(apps.UpdatePointOfSale(appId, vmpos).Result); + Assert.IsType(publicApps + .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "btconly").Result); + Assert.IsType(publicApps + .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "normal").Result); + invoices = user.BitPay.GetInvoices(); + var normalInvoice = invoices.Single(invoice => invoice.ItemCode == "normal"); + var btcOnlyInvoice = invoices.Single(invoice => invoice.ItemCode == "btconly"); + Assert.Single(btcOnlyInvoice.CryptoInfo); + Assert.Equal("BTC", + btcOnlyInvoice.CryptoInfo.First().CryptoCode); + Assert.Equal(PaymentTypes.BTCLike.ToString(), + btcOnlyInvoice.CryptoInfo.First().PaymentType); + + Assert.Equal(2, normalInvoice.CryptoInfo.Length); + Assert.Contains( + normalInvoice.CryptoInfo, + s => PaymentTypes.BTCLike.ToString() == s.PaymentType && new[] { "BTC", "LTC" }.Contains( + s.CryptoCode)); + } + } + + [Fact] + [Trait("Fast", "Fast")] + [Trait("Altcoins", "Altcoins")] + public void CanCalculateCryptoDue2() + { +#pragma warning disable CS0618 + var dummy = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest).ToString(); + var networkProvider = new BTCPayNetworkProvider(NetworkType.Regtest); + var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[] + { + new BitcoinLikePaymentHandler(null, networkProvider, null, null, null), + new LightningLikePaymentHandler(null, null, networkProvider, null), + }); + var networkBTC = networkProvider.GetNetwork("BTC"); + var networkLTC = networkProvider.GetNetwork("LTC"); + InvoiceEntity invoiceEntity = new InvoiceEntity(); + invoiceEntity.Networks = networkProvider; + invoiceEntity.Payments = new System.Collections.Generic.List(); + invoiceEntity.ProductInformation = new ProductInformation() { Price = 100 }; + PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary(); + paymentMethods.Add(new PaymentMethod() { Network = networkBTC, CryptoCode = "BTC", Rate = 10513.44m, } + .SetPaymentMethodDetails( + new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod() + { + NextNetworkFee = Money.Coins(0.00000100m), + DepositAddress = dummy + })); + paymentMethods.Add(new PaymentMethod() { Network = networkLTC, CryptoCode = "LTC", Rate = 216.79m } + .SetPaymentMethodDetails( + new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod() + { + NextNetworkFee = Money.Coins(0.00010000m), + DepositAddress = dummy + })); + invoiceEntity.SetPaymentMethods(paymentMethods); + + var btc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike)); + var accounting = btc.Calculate(); + + invoiceEntity.Payments.Add( + new PaymentEntity() + { + Accounted = true, + CryptoCode = "BTC", + NetworkFee = 0.00000100m, + Network = networkProvider.GetNetwork("BTC"), + } + .SetCryptoPaymentData(new BitcoinLikePaymentData() + { + Network = networkProvider.GetNetwork("BTC"), + Output = new TxOut() { Value = Money.Coins(0.00151263m) } + })); + accounting = btc.Calculate(); + invoiceEntity.Payments.Add( + new PaymentEntity() + { + Accounted = true, + CryptoCode = "BTC", + NetworkFee = 0.00000100m, + Network = networkProvider.GetNetwork("BTC") + } + .SetCryptoPaymentData(new BitcoinLikePaymentData() + { + Network = networkProvider.GetNetwork("BTC"), + Output = new TxOut() { Value = accounting.Due } + })); + accounting = btc.Calculate(); + Assert.Equal(Money.Zero, accounting.Due); + Assert.Equal(Money.Zero, accounting.DueUncapped); + + var ltc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike)); + accounting = ltc.Calculate(); + + Assert.Equal(Money.Zero, accounting.Due); + // LTC might have over paid due to BTC paying above what it should (round 1 satoshi up) + Assert.True(accounting.DueUncapped < Money.Zero); + + var paymentMethod = InvoiceWatcher.GetNearestClearedPayment(paymentMethods, out var accounting2); + Assert.Equal(btc.CryptoCode, paymentMethod.CryptoCode); +#pragma warning restore CS0618 + } + + [Fact] + [Trait("Fast", "Fast")] + [Trait("Altcoins", "Altcoins")] + public void CanParseDerivationScheme() + { + var testnetNetworkProvider = new BTCPayNetworkProvider(NetworkType.Testnet); + var regtestNetworkProvider = new BTCPayNetworkProvider(NetworkType.Regtest); + var mainnetNetworkProvider = new BTCPayNetworkProvider(NetworkType.Mainnet); + var testnetParser = new DerivationSchemeParser(testnetNetworkProvider.GetNetwork("BTC")); + var mainnetParser = new DerivationSchemeParser(mainnetNetworkProvider.GetNetwork("BTC")); + NBXplorer.DerivationStrategy.DerivationStrategyBase result; + // Passing electrum stuff + // Passing a native segwit from mainnet to a testnet parser, means the testnet parser will try to convert it into segwit + result = testnetParser.Parse( + "zpub6nL6PUGurpU3DfPDSZaRS6WshpbNc9ctCFFzrCn54cssnheM31SZJZUcFHKtjJJNhAueMbh6ptFMfy1aeiMQJr3RJ4DDt1hAPx7sMTKV48t"); + Assert.Equal( + "tpubD93CJNkmGjLXnsBqE2zGDqfEh1Q8iJ8wueordy3SeWt1RngbbuxXCsqASuVWFywmfoCwUE1rSfNJbaH4cBNcbp8WcyZgPiiRSTazLGL8U9w", + result.ToString()); + result = mainnetParser.Parse( + "zpub6nL6PUGurpU3DfPDSZaRS6WshpbNc9ctCFFzrCn54cssnheM31SZJZUcFHKtjJJNhAueMbh6ptFMfy1aeiMQJr3RJ4DDt1hAPx7sMTKV48t"); + Assert.Equal( + "xpub68fZn8w5ZTP5X4zymr1B1vKsMtJUiudtN2DZHQzJJc87gW1tXh7S4SALCsQijUzXstg2reVyuZYFuPnTDKXNiNgDZNpNiC4BrVzaaGEaRHj", + result.ToString()); + // P2SH + result = testnetParser.Parse( + "upub57Wa4MvRPNyAipy1MCpERxcFpHR2ZatyikppkyeWkoRL6QJvLVMo39jYdcaJVxyvBURyRVmErBEA5oGicKBgk1j72GAXSPFH5tUDoGZ8nEu"); + Assert.Equal( + "tpubD6NzVbkrYhZ4YWjDJUACG9E8fJx2NqNY1iynTiPKEjJrzzRKAgha3nNnwGXr2BtvCJKJHW4nmG7rRqc2AGGy2AECgt16seMyV2FZivUmaJg-[p2sh]", + result.ToString()); + + result = mainnetParser.Parse( + "ypub6QqdH2c5z79681jUgdxjGJzGW9zpL4ryPCuhtZE4GpvrJoZqM823XQN6iSQeVbbbp2uCRQ9UgpeMcwiyV6qjvxTWVcxDn2XEAnioMUwsrQ5"); + Assert.Equal( + "xpub661MyMwAqRbcGiYMrHB74DtmLBrNPSsUU6PV7ALAtpYyFhkc6TrUuLhxhET4VgwgQPnPfvYvEAHojf7QmQRj8imudHFoC7hju4f9xxri8wR-[p2sh]", + result.ToString()); + + // if prefix not recognize, assume it is segwit + result = testnetParser.Parse( + "xpub661MyMwAqRbcGeVGU5e5KBcau1HHEUGf9Wr7k4FyLa8yRPNQrrVa7Ndrgg8Afbe2UYXMSL6tJBFd2JewwWASsePPLjkcJFL1tTVEs3UQ23X"); + Assert.Equal( + "tpubD6NzVbkrYhZ4YSg7vGdAX6wxE8NwDrmih9SR6cK7gUtsAg37w5LfFpJgviCxC6bGGT4G3uckqH5fiV9ZLN1gm5qgQLVuymzFUR5ed7U7ksu", + result.ToString()); + //////////////// + + var tpub = + "tpubD6NzVbkrYhZ4Wc65tjhmcKdWFauAo7bGLRTxvggygkNyp6SMGutJp7iociwsinU33jyNBp1J9j2hJH5yQsayfiS3LEU2ZqXodAcnaygra8o"; + + result = testnetParser.Parse(tpub); + Assert.Equal(tpub, result.ToString()); + testnetParser.HintScriptPubKey = BitcoinAddress + .Create("tb1q4s33amqm8l7a07zdxcunqnn3gcsjcfz3xc573l", testnetParser.Network).ScriptPubKey; + result = testnetParser.Parse(tpub); + Assert.Equal(tpub, result.ToString()); + + testnetParser.HintScriptPubKey = BitcoinAddress + .Create("2N2humNio3YTApSfY6VztQ9hQwDnhDvaqFQ", testnetParser.Network).ScriptPubKey; + result = testnetParser.Parse(tpub); + Assert.Equal($"{tpub}-[p2sh]", result.ToString()); + + testnetParser.HintScriptPubKey = BitcoinAddress + .Create("mwD8bHS65cdgUf6rZUUSoVhi3wNQFu1Nfi", testnetParser.Network).ScriptPubKey; + result = testnetParser.Parse(tpub); + Assert.Equal($"{tpub}-[legacy]", result.ToString()); + + testnetParser.HintScriptPubKey = BitcoinAddress + .Create("2N2humNio3YTApSfY6VztQ9hQwDnhDvaqFQ", testnetParser.Network).ScriptPubKey; + result = testnetParser.Parse($"{tpub}-[legacy]"); + Assert.Equal($"{tpub}-[p2sh]", result.ToString()); + + result = testnetParser.Parse(tpub); + Assert.Equal($"{tpub}-[p2sh]", result.ToString()); + + var regtestParser = new DerivationSchemeParser(regtestNetworkProvider.GetNetwork("BTC")); + var parsed = + regtestParser.Parse( + "xpub6DG1rMYXiQtCc6CfdLFD9CtxqhzzRh7j6Sq6EdE9abgYy3cfDRrniLLv2AdwqHL1exiLnnKR5XXcaoiiexf3Y9R6J6rxkJtqJHzNzMW9QMZ-[p2sh]"); + Assert.Equal( + "tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[p2sh]", + parsed.ToString()); + + // Let's make sure we can't generate segwit with dogecoin + regtestParser = new DerivationSchemeParser(regtestNetworkProvider.GetNetwork("DOGE")); + parsed = regtestParser.Parse( + "xpub6DG1rMYXiQtCc6CfdLFD9CtxqhzzRh7j6Sq6EdE9abgYy3cfDRrniLLv2AdwqHL1exiLnnKR5XXcaoiiexf3Y9R6J6rxkJtqJHzNzMW9QMZ-[p2sh]"); + Assert.Equal( + "tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[legacy]", + parsed.ToString()); + + regtestParser = new DerivationSchemeParser(regtestNetworkProvider.GetNetwork("DOGE")); + parsed = regtestParser.Parse( + "tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[p2sh]"); + Assert.Equal( + "tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[legacy]", + parsed.ToString()); + } + } +} diff --git a/BTCPayServer.Tests/ElementsTests.cs b/BTCPayServer.Tests/AltcoinTests/ElementsTests.cs similarity index 99% rename from BTCPayServer.Tests/ElementsTests.cs rename to BTCPayServer.Tests/AltcoinTests/ElementsTests.cs index 035cba800..5edff1848 100644 --- a/BTCPayServer.Tests/ElementsTests.cs +++ b/BTCPayServer.Tests/AltcoinTests/ElementsTests.cs @@ -66,6 +66,7 @@ namespace BTCPayServer.Tests Assert.NotNull(options.NetworkProvider.GetNetwork("USDT")); } + [Fact] [Trait("Altcoins", "Altcoins")] public async Task ElementsAssetsAreHandledCorrectly() diff --git a/BTCPayServer.Tests/BTCPayServer.Tests.csproj b/BTCPayServer.Tests/BTCPayServer.Tests.csproj index db839de1b..76ffc3d2d 100644 --- a/BTCPayServer.Tests/BTCPayServer.Tests.csproj +++ b/BTCPayServer.Tests/BTCPayServer.Tests.csproj @@ -1,11 +1,7 @@ - - + + - netcoreapp3.1 - $(TargetFrameworkOverride) false - NU1701,CA1816,CA1308,CA1810,CA2208 - 8.0 AB0AC1DD-9D26-485B-9416-56A33F268117 true @@ -21,6 +17,9 @@ $(DefineConstants);SHORT_TIMEOUT + + $(DefineConstants);ALTCOINS + @@ -33,11 +32,16 @@ runtime; build; native; contentfiles; analyzers - + + + Dockerfile + + Always + Always diff --git a/BTCPayServer.Tests/CheckoutUITests.cs b/BTCPayServer.Tests/CheckoutUITests.cs index 5eb52f8b7..e41104dab 100644 --- a/BTCPayServer.Tests/CheckoutUITests.cs +++ b/BTCPayServer.Tests/CheckoutUITests.cs @@ -103,53 +103,6 @@ namespace BTCPayServer.Tests } } - [Fact(Timeout = TestTimeout)] - [Trait("Altcoins", "Altcoins")] - [Trait("Lightning", "Lightning")] - public async Task CanUsePaymentMethodDropdown() - { - using (var s = SeleniumTester.Create()) - { - s.Server.ActivateLTC(); - s.Server.ActivateLightning(); - await s.StartAsync(); - s.GoToRegister(); - s.RegisterNewUser(); - var store = s.CreateNewStore(); - s.AddDerivationScheme("BTC"); - - //check that there is no dropdown since only one payment method is set - var invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com"); - s.GoToInvoiceCheckout(invoiceId); - s.Driver.FindElement(By.ClassName("payment__currencies_noborder")); - s.GoToHome(); - s.GoToStore(store.storeId); - s.AddDerivationScheme("LTC"); - s.AddLightningNode("BTC", LightningConnectionType.CLightning); - //there should be three now - invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com"); - s.GoToInvoiceCheckout(invoiceId); - var currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies")); - Assert.Contains("BTC", currencyDropdownButton.Text); - currencyDropdownButton.Click(); - - var elements = s.Driver.FindElement(By.ClassName("vex-content")).FindElements(By.ClassName("vexmenuitem")); - Assert.Equal(3, elements.Count); - elements.Single(element => element.Text.Contains("LTC")).Click(); - currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies")); - Assert.Contains("LTC", currencyDropdownButton.Text); - currencyDropdownButton.Click(); - - elements = s.Driver.FindElement(By.ClassName("vex-content")).FindElements(By.ClassName("vexmenuitem")); - elements.Single(element => element.Text.Contains("Lightning")).Click(); - - currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies")); - Assert.Contains("Lightning", currencyDropdownButton.Text); - - s.Driver.Quit(); - } - } - [Fact(Timeout = TestTimeout)] [Trait("Lightning", "Lightning")] public async Task CanUseLightningSatsFeature() diff --git a/BTCPayServer.Tests/Dockerfile b/BTCPayServer.Tests/Dockerfile index ea37b47b6..436a25906 100644 --- a/BTCPayServer.Tests/Dockerfile +++ b/BTCPayServer.Tests/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/core/sdk:3.1.101 AS builder +FROM mcr.microsoft.com/dotnet/core/sdk:3.1.202 AS builder RUN apt-get update && apt-get install -y --no-install-recommends chromium-driver \ && rm -rf /var/lib/apt/lists/* @@ -21,6 +21,9 @@ ENV SCREEN_HEIGHT 600 \ SCREEN_WIDTH 1200 COPY . . -RUN cd BTCPayServer.Tests && dotnet build /p:CI_TESTS=true /p:RazorCompileOnBuild=true + +ARG CONFIGURATION_NAME=Release +RUN cd BTCPayServer.Tests && dotnet build --configuration ${CONFIGURATION_NAME} /p:CI_TESTS=true /p:RazorCompileOnBuild=true WORKDIR /source/BTCPayServer.Tests +ENV CONFIGURATION_NAME=${CONFIGURATION_NAME} ENTRYPOINT ["./docker-entrypoint.sh"] diff --git a/BTCPayServer.Tests/README.md b/BTCPayServer.Tests/README.md index 6d7d00a61..4cc801818 100644 --- a/BTCPayServer.Tests/README.md +++ b/BTCPayServer.Tests/README.md @@ -31,6 +31,12 @@ docker-compose down --v You can run tests on `MySql` database instead of `Postgres` by setting environnement variable `TESTS_DB` equals to `MySql`. +# How to test altcoins + +Follow the above instruction except the `docker-compose` command should be `docker-compose -f docker-compose.altcoins.yml`. + +This will run monero, ltc and liquid dependencies. + ## How to manually test payments ### Using the test bitcoin-cli diff --git a/BTCPayServer.Tests/SeleniumTests.cs b/BTCPayServer.Tests/SeleniumTests.cs index 87129ca07..d214abb64 100644 --- a/BTCPayServer.Tests/SeleniumTests.cs +++ b/BTCPayServer.Tests/SeleniumTests.cs @@ -12,6 +12,7 @@ using BTCPayServer.Views.Wallets; using Microsoft.EntityFrameworkCore; using NBitcoin; using NBitcoin.Payment; +using NBitpayClient; using OpenQA.Selenium; using Xunit; using Xunit.Abstractions; @@ -686,80 +687,6 @@ namespace BTCPayServer.Tests } } - [Fact] - [Trait("Selenium", "Selenium")] - [Trait("Altcoins", "Altcoins")] - public async Task CanCreateRefunds() - { - using (var s = SeleniumTester.Create()) - { - s.Server.ActivateLTC(); - await s.StartAsync(); - var user = s.Server.NewAccount(); - await user.GrantAccessAsync(); - s.GoToLogin(); - s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password); - user.RegisterDerivationScheme("BTC"); - await s.Server.ExplorerNode.GenerateAsync(1); - - foreach (var multiCurrency in new[] { false, true }) - { - if (multiCurrency) - user.RegisterDerivationScheme("LTC"); - foreach (var rateSelection in new[] { "FiatText", "CurrentRateText", "RateThenText" }) - await CanCreateRefundsCore(s, user, multiCurrency, rateSelection); - } - } - } - - private static async Task CanCreateRefundsCore(SeleniumTester s, TestAccount user, bool multiCurrency, string rateSelection) - { - s.GoToHome(); - s.Server.PayTester.ChangeRate("BTC_USD", new Rating.BidAsk(5000.0m, 5100.0m)); - var invoice = await user.BitPay.CreateInvoiceAsync(new NBitpayClient.Invoice() - { - Currency = "USD", - Price = 5000.0m - }); - var info = invoice.CryptoInfo.First(o => o.CryptoCode == "BTC"); - var totalDue = decimal.Parse(info.TotalDue, CultureInfo.InvariantCulture); - var paid = totalDue + 0.1m; - await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(info.Address, Network.RegTest), Money.Coins(paid)); - await s.Server.ExplorerNode.GenerateAsync(1); - await TestUtils.EventuallyAsync(async () => - { - invoice = await user.BitPay.GetInvoiceAsync(invoice.Id); - Assert.Equal("confirmed", invoice.Status); - }); - - // BTC crash by 50% - s.Server.PayTester.ChangeRate("BTC_USD", new Rating.BidAsk(5000.0m / 2.0m, 5100.0m / 2.0m)); - s.GoToInvoice(invoice.Id); - s.Driver.FindElement(By.Id("refundlink")).Click(); - if (multiCurrency) - { - s.Driver.FindElement(By.Id("SelectedPaymentMethod")).SendKeys("BTC" + Keys.Enter); - s.Driver.FindElement(By.Id("ok")).Click(); - } - Assert.Contains("$5,500.00", s.Driver.PageSource); // Should propose reimburse in fiat - Assert.Contains("1.10000000 ₿", s.Driver.PageSource); // Should propose reimburse in BTC at the rate of before - Assert.Contains("2.20000000 ₿", s.Driver.PageSource); // Should propose reimburse in BTC at the current rate - s.Driver.FindElement(By.Id(rateSelection)).Click(); - s.Driver.FindElement(By.Id("ok")).Click(); - Assert.Contains("pull-payments", s.Driver.Url); - if (rateSelection == "FiatText") - Assert.Contains("$5,500.00", s.Driver.PageSource); - if (rateSelection == "CurrentRateText") - Assert.Contains("2.20000000 ₿", s.Driver.PageSource); - if (rateSelection == "RateThenText") - Assert.Contains("1.10000000 ₿", s.Driver.PageSource); - s.GoToHome(); - s.GoToInvoices(); - s.GoToInvoice(invoice.Id); - s.Driver.FindElement(By.Id("refundlink")).Click(); - Assert.Contains("pull-payments", s.Driver.Url); - } - [Fact] [Trait("Selenium", "Selenium")] public async Task CanUsePullPaymentsViaUI() diff --git a/BTCPayServer.Tests/ServerTester.cs b/BTCPayServer.Tests/ServerTester.cs index 9cb775455..698122649 100644 --- a/BTCPayServer.Tests/ServerTester.cs +++ b/BTCPayServer.Tests/ServerTester.cs @@ -63,7 +63,7 @@ namespace BTCPayServer.Tests PayTester.SSHConnection = GetEnvironment("TESTS_SSHCONNECTION", "root@127.0.0.1:21622"); PayTester.SocksEndpoint = GetEnvironment("TESTS_SOCKSENDPOINT", "localhost:9050"); } - +#if ALTCOINS public void ActivateLTC() { LTCExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_LTCRPCCONNECTION", "server=http://127.0.0.1:43783;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork("LTC").NBitcoinNetwork); @@ -78,7 +78,7 @@ namespace BTCPayServer.Tests PayTester.Chains.Add("LBTC"); PayTester.LBTCNBXplorerUri = LBTCExplorerClient.Address; } - +#endif public void ActivateLightning() { var btc = NetworkProvider.GetNetwork("BTC").NBitcoinNetwork; @@ -170,20 +170,21 @@ namespace BTCPayServer.Tests { get; set; } - +#if ALTCOINS public RPCClient LTCExplorerNode { get; set; } public RPCClient LBTCExplorerNode { get; set; } + public ExplorerClient LTCExplorerClient { get; set; } + public ExplorerClient LBTCExplorerClient { get; set; } +#endif public ExplorerClient ExplorerClient { get; set; } - public ExplorerClient LTCExplorerClient { get; set; } - public ExplorerClient LBTCExplorerClient { get; set; } readonly HttpClient _Http = new HttpClient(); diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index 6be6bdc52..6074d7318 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -203,87 +203,6 @@ namespace BTCPayServer.Tests Assert.False(attribute.IsValid("httpdsadsa.com")); } - [Fact] - [Trait("Fast", "Fast")] - public void CanCalculateCryptoDue2() - { -#pragma warning disable CS0618 - var dummy = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest).ToString(); - var networkProvider = new BTCPayNetworkProvider(NetworkType.Regtest); - var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[] - { - new BitcoinLikePaymentHandler(null, networkProvider, null, null, null), - new LightningLikePaymentHandler(null, null, networkProvider, null), - }); - var networkBTC = networkProvider.GetNetwork("BTC"); - var networkLTC = networkProvider.GetNetwork("LTC"); - InvoiceEntity invoiceEntity = new InvoiceEntity(); - invoiceEntity.Networks = networkProvider; - invoiceEntity.Payments = new System.Collections.Generic.List(); - invoiceEntity.ProductInformation = new ProductInformation() { Price = 100 }; - PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary(); - paymentMethods.Add(new PaymentMethod() { Network = networkBTC, CryptoCode = "BTC", Rate = 10513.44m, } - .SetPaymentMethodDetails( - new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod() - { - NextNetworkFee = Money.Coins(0.00000100m), - DepositAddress = dummy - })); - paymentMethods.Add(new PaymentMethod() { Network = networkLTC, CryptoCode = "LTC", Rate = 216.79m } - .SetPaymentMethodDetails( - new BTCPayServer.Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod() - { - NextNetworkFee = Money.Coins(0.00010000m), - DepositAddress = dummy - })); - invoiceEntity.SetPaymentMethods(paymentMethods); - - var btc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike)); - var accounting = btc.Calculate(); - - invoiceEntity.Payments.Add( - new PaymentEntity() - { - Accounted = true, - CryptoCode = "BTC", - NetworkFee = 0.00000100m, - Network = networkProvider.GetNetwork("BTC"), - } - .SetCryptoPaymentData(new BitcoinLikePaymentData() - { - Network = networkProvider.GetNetwork("BTC"), - Output = new TxOut() { Value = Money.Coins(0.00151263m) } - })); - accounting = btc.Calculate(); - invoiceEntity.Payments.Add( - new PaymentEntity() - { - Accounted = true, - CryptoCode = "BTC", - NetworkFee = 0.00000100m, - Network = networkProvider.GetNetwork("BTC") - } - .SetCryptoPaymentData(new BitcoinLikePaymentData() - { - Network = networkProvider.GetNetwork("BTC"), - Output = new TxOut() { Value = accounting.Due } - })); - accounting = btc.Calculate(); - Assert.Equal(Money.Zero, accounting.Due); - Assert.Equal(Money.Zero, accounting.DueUncapped); - - var ltc = invoiceEntity.GetPaymentMethod(new PaymentMethodId("LTC", PaymentTypes.BTCLike)); - accounting = ltc.Calculate(); - - Assert.Equal(Money.Zero, accounting.Due); - // LTC might have over paid due to BTC paying above what it should (round 1 satoshi up) - Assert.True(accounting.DueUncapped < Money.Zero); - - var paymentMethod = InvoiceWatcher.GetNearestClearedPayment(paymentMethods, out var accounting2); - Assert.Equal(btc.CryptoCode, paymentMethod.CryptoCode); -#pragma warning restore CS0618 - } - [Fact] [Trait("Fast", "Fast")] public void CanParseTorrc() @@ -1816,76 +1735,6 @@ namespace BTCPayServer.Tests } } - [Fact(Timeout = TestTimeout)] - [Trait("Integration", "Integration")] - [Trait("Altcoins", "Altcoins")] - public async Task CanHaveLTCOnlyStore() - { - using (var tester = ServerTester.Create()) - { - tester.ActivateLTC(); - await tester.StartAsync(); - var user = tester.NewAccount(); - user.GrantAccess(); - user.RegisterDerivationScheme("LTC"); - - // First we try payment with a merchant having only BTC - var invoice = user.BitPay.CreateInvoice( - new Invoice() - { - Price = 500, - Currency = "USD", - PosData = "posData", - OrderId = "orderId", - ItemDesc = "Some description", - FullNotifications = true - }, Facade.Merchant); - - Assert.Single(invoice.CryptoInfo); - Assert.Equal("LTC", invoice.CryptoInfo[0].CryptoCode); - Assert.True(invoice.PaymentCodes.ContainsKey("LTC")); - Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("LTC")); - Assert.True(invoice.SupportedTransactionCurrencies["LTC"].Enabled); - Assert.True(invoice.PaymentSubtotals.ContainsKey("LTC")); - Assert.True(invoice.PaymentTotals.ContainsKey("LTC")); - var cashCow = tester.LTCExplorerNode; - var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network); - var firstPayment = Money.Coins(0.1m); - cashCow.SendToAddress(invoiceAddress, firstPayment); - TestUtils.Eventually(() => - { - invoice = user.BitPay.GetInvoice(invoice.Id); - Assert.Equal(firstPayment, invoice.CryptoInfo[0].Paid); - }); - - Assert.Single(invoice.CryptoInfo); // Only BTC should be presented - - var controller = tester.PayTester.GetController(null); - var checkout = - (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null) - .GetAwaiter().GetResult()).Value; - Assert.Single(checkout.AvailableCryptos); - Assert.Equal("LTC", checkout.CryptoCode); - - ////////////////////// - - // Despite it is called BitcoinAddress it should be LTC because BTC is not available - Assert.Null(invoice.BitcoinAddress); - Assert.NotEqual(1.0m, invoice.Rate); - Assert.NotEqual(invoice.BtcDue, invoice.CryptoInfo[0].Due); // Should be BTC rate - cashCow.SendToAddress(invoiceAddress, invoice.CryptoInfo[0].Due); - - TestUtils.Eventually(() => - { - invoice = user.BitPay.GetInvoice(invoice.Id); - Assert.Equal("paid", invoice.Status); - checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null) - .GetAwaiter().GetResult()).Value; - Assert.Equal("paid", checkout.Status); - }); - } - } - [Fact(Timeout = TestTimeout)] [Trait("Integration", "Integration")] public async Task CanModifyRates() @@ -1953,145 +1802,6 @@ namespace BTCPayServer.Tests } } - [Fact(Timeout = TestTimeout)] - [Trait("Integration", "Integration")] - [Trait("Altcoins", "Altcoins")] - public async Task CanPayWithTwoCurrencies() - { - using (var tester = ServerTester.Create()) - { - tester.ActivateLTC(); - await tester.StartAsync(); - var user = tester.NewAccount(); - user.GrantAccess(); - user.RegisterDerivationScheme("BTC"); - // First we try payment with a merchant having only BTC - var invoice = user.BitPay.CreateInvoice( - new Invoice() - { - Price = 5000.0m, - Currency = "USD", - PosData = "posData", - OrderId = "orderId", - ItemDesc = "Some description", - FullNotifications = true - }, Facade.Merchant); - - var cashCow = tester.ExplorerNode; - cashCow.Generate(2); // get some money in case - var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network); - var firstPayment = Money.Coins(0.04m); - cashCow.SendToAddress(invoiceAddress, firstPayment); - TestUtils.Eventually(() => - { - invoice = user.BitPay.GetInvoice(invoice.Id); - Assert.True(invoice.BtcPaid == firstPayment); - }); - - Assert.Single(invoice.CryptoInfo); // Only BTC should be presented - - var controller = tester.PayTester.GetController(null); - var checkout = - (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null) - .GetAwaiter().GetResult()).Value; - Assert.Single(checkout.AvailableCryptos); - Assert.Equal("BTC", checkout.CryptoCode); - - Assert.Single(invoice.PaymentCodes); - Assert.Single(invoice.SupportedTransactionCurrencies); - Assert.Single(invoice.SupportedTransactionCurrencies); - Assert.Single(invoice.PaymentSubtotals); - Assert.Single(invoice.PaymentTotals); - Assert.True(invoice.PaymentCodes.ContainsKey("BTC")); - Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("BTC")); - Assert.True(invoice.SupportedTransactionCurrencies["BTC"].Enabled); - Assert.True(invoice.PaymentSubtotals.ContainsKey("BTC")); - Assert.True(invoice.PaymentTotals.ContainsKey("BTC")); - ////////////////////// - - // Retry now with LTC enabled - user.RegisterDerivationScheme("LTC"); - invoice = user.BitPay.CreateInvoice( - new Invoice() - { - Price = 5000.0m, - Currency = "USD", - PosData = "posData", - OrderId = "orderId", - ItemDesc = "Some description", - FullNotifications = true - }, Facade.Merchant); - - cashCow = tester.ExplorerNode; - invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network); - firstPayment = Money.Coins(0.04m); - cashCow.SendToAddress(invoiceAddress, firstPayment); - Logs.Tester.LogInformation("First payment sent to " + invoiceAddress); - TestUtils.Eventually(() => - { - invoice = user.BitPay.GetInvoice(invoice.Id); - Assert.True(invoice.BtcPaid == firstPayment); - }); - - cashCow = tester.LTCExplorerNode; - var ltcCryptoInfo = invoice.CryptoInfo.FirstOrDefault(c => c.CryptoCode == "LTC"); - Assert.NotNull(ltcCryptoInfo); - invoiceAddress = BitcoinAddress.Create(ltcCryptoInfo.Address, cashCow.Network); - var secondPayment = Money.Coins(decimal.Parse(ltcCryptoInfo.Due, CultureInfo.InvariantCulture)); - cashCow.Generate(4); // LTC is not worth a lot, so just to make sure we have money... - cashCow.SendToAddress(invoiceAddress, secondPayment); - Logs.Tester.LogInformation("Second payment sent to " + invoiceAddress); - TestUtils.Eventually(() => - { - invoice = user.BitPay.GetInvoice(invoice.Id); - Assert.Equal(Money.Zero, invoice.BtcDue); - var ltcPaid = invoice.CryptoInfo.First(c => c.CryptoCode == "LTC"); - Assert.Equal(Money.Zero, ltcPaid.Due); - Assert.Equal(secondPayment, ltcPaid.CryptoPaid); - Assert.Equal("paid", invoice.Status); - Assert.False((bool)((JValue)invoice.ExceptionStatus).Value); - }); - - controller = tester.PayTester.GetController(null); - checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, "LTC") - .GetAwaiter().GetResult()).Value; - Assert.Equal(2, checkout.AvailableCryptos.Count); - Assert.Equal("LTC", checkout.CryptoCode); - - - Assert.Equal(2, invoice.PaymentCodes.Count()); - Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count()); - Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count()); - Assert.Equal(2, invoice.PaymentSubtotals.Count()); - Assert.Equal(2, invoice.PaymentTotals.Count()); - Assert.True(invoice.PaymentCodes.ContainsKey("LTC")); - Assert.True(invoice.SupportedTransactionCurrencies.ContainsKey("LTC")); - Assert.True(invoice.SupportedTransactionCurrencies["LTC"].Enabled); - Assert.True(invoice.PaymentSubtotals.ContainsKey("LTC")); - Assert.True(invoice.PaymentTotals.ContainsKey("LTC")); - - - // Check if we can disable LTC - invoice = user.BitPay.CreateInvoice( - new Invoice() - { - Price = 5000.0m, - Currency = "USD", - PosData = "posData", - OrderId = "orderId", - ItemDesc = "Some description", - FullNotifications = true, - SupportedTransactionCurrencies = new Dictionary() - { - {"BTC", new InvoiceSupportedTransactionCurrency() {Enabled = true}} - } - }, Facade.Merchant); - - Assert.Single(invoice.CryptoInfo.Where(c => c.CryptoCode == "BTC")); - Assert.Empty(invoice.CryptoInfo.Where(c => c.CryptoCode == "LTC")); - } - } - [Fact] [Trait("Fast", "Fast")] public void HasCurrencyDataForNetworks() @@ -2127,305 +1837,6 @@ namespace BTCPayServer.Tests Assert.False(CurrencyValue.TryParse("1.501", out result)); } - [Fact] - [Trait("Fast", "Fast")] - public void CanParseDerivationScheme() - { - var testnetNetworkProvider = new BTCPayNetworkProvider(NetworkType.Testnet); - var regtestNetworkProvider = new BTCPayNetworkProvider(NetworkType.Regtest); - var mainnetNetworkProvider = new BTCPayNetworkProvider(NetworkType.Mainnet); - var testnetParser = new DerivationSchemeParser(testnetNetworkProvider.GetNetwork("BTC")); - var mainnetParser = new DerivationSchemeParser(mainnetNetworkProvider.GetNetwork("BTC")); - NBXplorer.DerivationStrategy.DerivationStrategyBase result; - // Passing electrum stuff - // Passing a native segwit from mainnet to a testnet parser, means the testnet parser will try to convert it into segwit - result = testnetParser.Parse( - "zpub6nL6PUGurpU3DfPDSZaRS6WshpbNc9ctCFFzrCn54cssnheM31SZJZUcFHKtjJJNhAueMbh6ptFMfy1aeiMQJr3RJ4DDt1hAPx7sMTKV48t"); - Assert.Equal( - "tpubD93CJNkmGjLXnsBqE2zGDqfEh1Q8iJ8wueordy3SeWt1RngbbuxXCsqASuVWFywmfoCwUE1rSfNJbaH4cBNcbp8WcyZgPiiRSTazLGL8U9w", - result.ToString()); - result = mainnetParser.Parse( - "zpub6nL6PUGurpU3DfPDSZaRS6WshpbNc9ctCFFzrCn54cssnheM31SZJZUcFHKtjJJNhAueMbh6ptFMfy1aeiMQJr3RJ4DDt1hAPx7sMTKV48t"); - Assert.Equal( - "xpub68fZn8w5ZTP5X4zymr1B1vKsMtJUiudtN2DZHQzJJc87gW1tXh7S4SALCsQijUzXstg2reVyuZYFuPnTDKXNiNgDZNpNiC4BrVzaaGEaRHj", - result.ToString()); - // P2SH - result = testnetParser.Parse( - "upub57Wa4MvRPNyAipy1MCpERxcFpHR2ZatyikppkyeWkoRL6QJvLVMo39jYdcaJVxyvBURyRVmErBEA5oGicKBgk1j72GAXSPFH5tUDoGZ8nEu"); - Assert.Equal( - "tpubD6NzVbkrYhZ4YWjDJUACG9E8fJx2NqNY1iynTiPKEjJrzzRKAgha3nNnwGXr2BtvCJKJHW4nmG7rRqc2AGGy2AECgt16seMyV2FZivUmaJg-[p2sh]", - result.ToString()); - - result = mainnetParser.Parse( - "ypub6QqdH2c5z79681jUgdxjGJzGW9zpL4ryPCuhtZE4GpvrJoZqM823XQN6iSQeVbbbp2uCRQ9UgpeMcwiyV6qjvxTWVcxDn2XEAnioMUwsrQ5"); - Assert.Equal( - "xpub661MyMwAqRbcGiYMrHB74DtmLBrNPSsUU6PV7ALAtpYyFhkc6TrUuLhxhET4VgwgQPnPfvYvEAHojf7QmQRj8imudHFoC7hju4f9xxri8wR-[p2sh]", - result.ToString()); - - // if prefix not recognize, assume it is segwit - result = testnetParser.Parse( - "xpub661MyMwAqRbcGeVGU5e5KBcau1HHEUGf9Wr7k4FyLa8yRPNQrrVa7Ndrgg8Afbe2UYXMSL6tJBFd2JewwWASsePPLjkcJFL1tTVEs3UQ23X"); - Assert.Equal( - "tpubD6NzVbkrYhZ4YSg7vGdAX6wxE8NwDrmih9SR6cK7gUtsAg37w5LfFpJgviCxC6bGGT4G3uckqH5fiV9ZLN1gm5qgQLVuymzFUR5ed7U7ksu", - result.ToString()); - //////////////// - - var tpub = - "tpubD6NzVbkrYhZ4Wc65tjhmcKdWFauAo7bGLRTxvggygkNyp6SMGutJp7iociwsinU33jyNBp1J9j2hJH5yQsayfiS3LEU2ZqXodAcnaygra8o"; - - result = testnetParser.Parse(tpub); - Assert.Equal(tpub, result.ToString()); - testnetParser.HintScriptPubKey = BitcoinAddress - .Create("tb1q4s33amqm8l7a07zdxcunqnn3gcsjcfz3xc573l", testnetParser.Network).ScriptPubKey; - result = testnetParser.Parse(tpub); - Assert.Equal(tpub, result.ToString()); - - testnetParser.HintScriptPubKey = BitcoinAddress - .Create("2N2humNio3YTApSfY6VztQ9hQwDnhDvaqFQ", testnetParser.Network).ScriptPubKey; - result = testnetParser.Parse(tpub); - Assert.Equal($"{tpub}-[p2sh]", result.ToString()); - - testnetParser.HintScriptPubKey = BitcoinAddress - .Create("mwD8bHS65cdgUf6rZUUSoVhi3wNQFu1Nfi", testnetParser.Network).ScriptPubKey; - result = testnetParser.Parse(tpub); - Assert.Equal($"{tpub}-[legacy]", result.ToString()); - - testnetParser.HintScriptPubKey = BitcoinAddress - .Create("2N2humNio3YTApSfY6VztQ9hQwDnhDvaqFQ", testnetParser.Network).ScriptPubKey; - result = testnetParser.Parse($"{tpub}-[legacy]"); - Assert.Equal($"{tpub}-[p2sh]", result.ToString()); - - result = testnetParser.Parse(tpub); - Assert.Equal($"{tpub}-[p2sh]", result.ToString()); - - var regtestParser = new DerivationSchemeParser(regtestNetworkProvider.GetNetwork("BTC")); - var parsed = - regtestParser.Parse( - "xpub6DG1rMYXiQtCc6CfdLFD9CtxqhzzRh7j6Sq6EdE9abgYy3cfDRrniLLv2AdwqHL1exiLnnKR5XXcaoiiexf3Y9R6J6rxkJtqJHzNzMW9QMZ-[p2sh]"); - Assert.Equal( - "tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[p2sh]", - parsed.ToString()); - - // Let's make sure we can't generate segwit with dogecoin - regtestParser = new DerivationSchemeParser(regtestNetworkProvider.GetNetwork("DOGE")); - parsed = regtestParser.Parse( - "xpub6DG1rMYXiQtCc6CfdLFD9CtxqhzzRh7j6Sq6EdE9abgYy3cfDRrniLLv2AdwqHL1exiLnnKR5XXcaoiiexf3Y9R6J6rxkJtqJHzNzMW9QMZ-[p2sh]"); - Assert.Equal( - "tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[legacy]", - parsed.ToString()); - - regtestParser = new DerivationSchemeParser(regtestNetworkProvider.GetNetwork("DOGE")); - parsed = regtestParser.Parse( - "tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[p2sh]"); - Assert.Equal( - "tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[legacy]", - parsed.ToString()); - } - - [Fact] - [Trait("Integration", "Integration")] - [Trait("Altcoins", "Altcoins")] - [Trait("Lightning", "Lightning")] - public async Task CanAddDerivationSchemes() - { - using (var tester = ServerTester.Create()) - { - tester.ActivateLTC(); - tester.ActivateLightning(); - await tester.StartAsync(); - var user = tester.NewAccount(); - user.GrantAccess(); - user.RegisterDerivationScheme("BTC"); - user.RegisterDerivationScheme("LTC"); - user.RegisterLightningNode("BTC", LightningConnectionType.CLightning); - var btcNetwork = tester.PayTester.Networks.GetNetwork("BTC"); - 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(); - var lightningVM = - (LightningNodeViewModel)Assert.IsType(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(controller.AddLightningNode(user.StoreId, "BTC")) - .Model; - Assert.False(lightningVM.Enabled); - - // Only Enabling/Disabling the payment method must redirect to store page - var derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - Assert.True(derivationVM.Enabled); - derivationVM.Enabled = false; - Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") - .GetAwaiter().GetResult()); - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - Assert.False(derivationVM.Enabled); - - // Clicking next without changing anything should send to the confirmation screen - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller - .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; - Assert.True(derivationVM.Confirmation); - - 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); - - // Removing the derivation scheme, should redirect to store page - var oldScheme = derivationVM.DerivationScheme; - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - derivationVM.DerivationScheme = null; - Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") - .GetAwaiter().GetResult()); - - // Setting it again should redirect to the confirmation page - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - derivationVM.DerivationScheme = oldScheme; - derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller - .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; - Assert.True(derivationVM.Confirmation); - - - //cobo vault file - var content = "{\"ExtPubKey\":\"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\"MasterFingerprint\":\"7a7563b5\",\"DerivationPath\":\"M\\/84'\\/0'\\/0'\",\"CoboVaultFirmwareVersion\":\"1.2.0(BTC-Only)\"}"; - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - derivationVM.WalletFile = TestUtils.GetFormFile("wallet3.json", content); - derivationVM.Enabled = true; - derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller - .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; - Assert.True(derivationVM.Confirmation); - Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") - .GetAwaiter().GetResult()); - - //wasabi wallet file - content = - "{\r\n \"EncryptedSecret\": \"6PYWBQ1zsukowsnTNA57UUx791aBuJusm7E4egXUmF5WGw3tcdG3cmTL57\",\r\n \"ChainCode\": \"waSIVbn8HaoovoQg/0t8IS1+ZCxGsJRGFT21i06nWnc=\",\r\n \"MasterFingerprint\": \"7a7563b5\",\r\n \"ExtPubKey\": \"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\r\n \"PasswordVerified\": false,\r\n \"MinGapLimit\": 21,\r\n \"AccountKeyPath\": \"84'/0'/0'\",\r\n \"BlockchainState\": {\r\n \"Network\": \"RegTest\",\r\n \"Height\": \"0\"\r\n },\r\n \"HdPubKeys\": []\r\n}"; - - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - derivationVM.WalletFile = TestUtils.GetFormFile("wallet4.json", content); - derivationVM.Enabled = true; - derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller - .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; - Assert.True(derivationVM.Confirmation); - Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") - .GetAwaiter().GetResult()); - - - // Can we upload coldcard settings? (Should fail, we are giving a mainnet file to a testnet network) - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - content = - "{\"keystore\": {\"ckcc_xpub\": \"xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw\", \"xpub\": \"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}"; - derivationVM.WalletFile = TestUtils.GetFormFile("wallet.json", content); - derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller - .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; - Assert.False(derivationVM - .Confirmation); // Should fail, we are giving a mainnet file to a testnet network - - // And with a good file? (upub) - content = - "{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"upub5DBYp1qGgsTrkzCptMGZc2x18pquLwGrBw6nS59T4NViZ4cni1mGowQzziy85K8vzkp1jVtWrSkLhqk9KDfvrGeB369wGNYf39kX8rQfiLn\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}"; - derivationVM = (DerivationSchemeViewModel)Assert - .IsType(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model; - derivationVM.WalletFile = TestUtils.GetFormFile("wallet2.json", content); - derivationVM.Enabled = true; - derivationVM = (DerivationSchemeViewModel)Assert.IsType(controller - .AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model; - Assert.True(derivationVM.Confirmation); - Assert.IsType(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC") - .GetAwaiter().GetResult()); - - - // Now let's check that no data has been lost in the process - var store = tester.PayTester.StoreRepository.FindStore(user.StoreId).GetAwaiter().GetResult(); - var onchainBTC = store.GetSupportedPaymentMethods(tester.PayTester.Networks) - .OfType().First(o => o.PaymentId.IsBTCOnChain); - DerivationSchemeSettings.TryParseFromWalletFile(content, onchainBTC.Network, out var expected); - Assert.Equal(expected.ToJson(), onchainBTC.ToJson()); - - // Let's check that the root hdkey and account key path are taken into account when making a PSBT - invoice = user.BitPay.CreateInvoice( - new Invoice() - { - Price = 1.5m, - Currency = "USD", - PosData = "posData", - OrderId = "orderId", - ItemDesc = "Some description", - FullNotifications = true - }, Facade.Merchant); - - tester.ExplorerNode.Generate(1); - var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo.First(c => c.CryptoCode == "BTC").Address, - tester.ExplorerNode.Network); - tester.ExplorerNode.SendToAddress(invoiceAddress, Money.Coins(1m)); - TestUtils.Eventually(() => - { - invoice = user.BitPay.GetInvoice(invoice.Id); - Assert.Equal("paid", invoice.Status); - }); - var wallet = tester.PayTester.GetController(); - var psbt = wallet.CreatePSBT(btcNetwork, onchainBTC, - new WalletSendModel() - { - Outputs = new List() - { - new WalletSendModel.TransactionOutput() - { - Amount = 0.5m, - DestinationAddress = new Key().PubKey.GetAddress(btcNetwork.NBitcoinNetwork) - .ToString(), - } - }, - FeeSatoshiPerByte = 1 - }, default).GetAwaiter().GetResult(); - - Assert.NotNull(psbt); - - var root = new Mnemonic( - "usage fever hen zero slide mammal silent heavy donate budget pulse say brain thank sausage brand craft about save attract muffin advance illegal cabbage") - .DeriveExtKey().AsHDKeyCache(); - var account = root.Derive(new KeyPath("m/49'/0'/0'")); - Assert.All(psbt.PSBT.Inputs, input => - { - var keyPath = input.HDKeyPaths.Single(); - Assert.False(keyPath.Value.KeyPath.IsHardened); - Assert.Equal(account.Derive(keyPath.Value.KeyPath).GetPublicKey(), keyPath.Key); - Assert.Equal(keyPath.Value.MasterFingerprint, - onchainBTC.AccountKeySettings[0].AccountKey.GetPublicKey().GetHDFingerPrint()); - }); - } - } - [Fact] [Trait("Integration", "Integration")] public async Task CanSetPaymentMethodLimits() @@ -2493,228 +1904,6 @@ namespace BTCPayServer.Tests } } - [Fact] - [Trait("Integration", "Integration")] - [Trait("Altcoins", "Altcoins")] - public async Task CanUsePoSApp() - { - using (var tester = ServerTester.Create()) - { - tester.ActivateLTC(); - await tester.StartAsync(); - var user = tester.NewAccount(); - user.GrantAccess(); - user.RegisterDerivationScheme("BTC"); - user.RegisterDerivationScheme("LTC"); - var apps = user.GetController(); - var vm = Assert.IsType(Assert.IsType(apps.CreateApp().Result).Model); - vm.Name = "test"; - vm.SelectedAppType = AppType.PointOfSale.ToString(); - Assert.IsType(apps.CreateApp(vm).Result); - var appId = Assert.IsType(Assert.IsType(apps.ListApps().Result).Model) - .Apps[0].Id; - var vmpos = Assert.IsType(Assert - .IsType(apps.UpdatePointOfSale(appId).Result).Model); - vmpos.Title = "hello"; - vmpos.Currency = "CAD"; - vmpos.ButtonText = "{0} Purchase"; - vmpos.CustomButtonText = "Nicolas Sexy Hair"; - vmpos.CustomTipText = "Wanna tip?"; - vmpos.CustomTipPercentages = "15,18,20"; - vmpos.Template = @" -apple: - price: 5.0 - title: good apple -orange: - price: 10.0 -donation: - price: 1.02 - custom: true -"; - Assert.IsType(apps.UpdatePointOfSale(appId, vmpos).Result); - vmpos = Assert.IsType(Assert - .IsType(apps.UpdatePointOfSale(appId).Result).Model); - Assert.Equal("hello", vmpos.Title); - - var publicApps = user.GetController(); - var vmview = - Assert.IsType(Assert - .IsType(publicApps.ViewPointOfSale(appId, PosViewType.Cart).Result).Model); - Assert.Equal("hello", vmview.Title); - Assert.Equal(3, vmview.Items.Length); - Assert.Equal("good apple", vmview.Items[0].Title); - Assert.Equal("orange", vmview.Items[1].Title); - Assert.Equal(10.0m, vmview.Items[1].Price.Value); - Assert.Equal("$5.00", vmview.Items[0].Price.Formatted); - Assert.Equal("{0} Purchase", vmview.ButtonText); - Assert.Equal("Nicolas Sexy Hair", vmview.CustomButtonText); - Assert.Equal("Wanna tip?", vmview.CustomTipText); - Assert.Equal("15,18,20", string.Join(',', vmview.CustomTipPercentages)); - Assert.IsType(publicApps - .ViewPointOfSale(appId, PosViewType.Cart, 0, null, null, null, null, "orange").Result); - - // - var invoices = user.BitPay.GetInvoices(); - var orangeInvoice = invoices.First(); - Assert.Equal(10.00m, orangeInvoice.Price); - Assert.Equal("CAD", orangeInvoice.Currency); - Assert.Equal("orange", orangeInvoice.ItemDesc); - - - Assert.IsType(publicApps - .ViewPointOfSale(appId, PosViewType.Cart, 0, null, null, null, null, "apple").Result); - - invoices = user.BitPay.GetInvoices(); - var appleInvoice = invoices.SingleOrDefault(invoice => invoice.ItemCode.Equals("apple")); - Assert.NotNull(appleInvoice); - Assert.Equal("good apple", appleInvoice.ItemDesc); - - - // testing custom amount - var action = Assert.IsType(publicApps - .ViewPointOfSale(appId, PosViewType.Cart, 6.6m, null, null, null, null, "donation").Result); - Assert.Equal(nameof(InvoiceController.Checkout), action.ActionName); - invoices = user.BitPay.GetInvoices(); - var donationInvoice = invoices.Single(i => i.Price == 6.6m); - Assert.NotNull(donationInvoice); - Assert.Equal("CAD", donationInvoice.Currency); - Assert.Equal("donation", donationInvoice.ItemDesc); - - foreach (var test in new[] - { - (Code: "EUR", ExpectedSymbol: "€", ExpectedDecimalSeparator: ",", ExpectedDivisibility: 2, - ExpectedThousandSeparator: "\xa0", ExpectedPrefixed: false, ExpectedSymbolSpace: true), - (Code: "INR", ExpectedSymbol: "₹", ExpectedDecimalSeparator: ".", ExpectedDivisibility: 2, - ExpectedThousandSeparator: ",", ExpectedPrefixed: true, ExpectedSymbolSpace: true), - (Code: "JPY", ExpectedSymbol: "¥", ExpectedDecimalSeparator: ".", ExpectedDivisibility: 0, - ExpectedThousandSeparator: ",", ExpectedPrefixed: true, ExpectedSymbolSpace: false), - (Code: "BTC", ExpectedSymbol: "₿", ExpectedDecimalSeparator: ".", ExpectedDivisibility: 8, - ExpectedThousandSeparator: ",", ExpectedPrefixed: false, ExpectedSymbolSpace: true), - }) - { - Logs.Tester.LogInformation($"Testing for {test.Code}"); - vmpos = Assert.IsType(Assert - .IsType(apps.UpdatePointOfSale(appId).Result).Model); - vmpos.Title = "hello"; - vmpos.Currency = test.Item1; - vmpos.ButtonText = "{0} Purchase"; - vmpos.CustomButtonText = "Nicolas Sexy Hair"; - vmpos.CustomTipText = "Wanna tip?"; - vmpos.Template = @" -apple: - price: 1000.0 - title: good apple -orange: - price: 10.0 -donation: - price: 1.02 - custom: true -"; - Assert.IsType(apps.UpdatePointOfSale(appId, vmpos).Result); - publicApps = user.GetController(); - vmview = Assert.IsType(Assert - .IsType(publicApps.ViewPointOfSale(appId, PosViewType.Cart).Result).Model); - Assert.Equal(test.Code, vmview.CurrencyCode); - Assert.Equal(test.ExpectedSymbol, - vmview.CurrencySymbol.Replace("¥", "¥")); // Hack so JPY test pass on linux as well); - Assert.Equal(test.ExpectedSymbol, - vmview.CurrencyInfo.CurrencySymbol - .Replace("¥", "¥")); // Hack so JPY test pass on linux as well); - Assert.Equal(test.ExpectedDecimalSeparator, vmview.CurrencyInfo.DecimalSeparator); - Assert.Equal(test.ExpectedThousandSeparator, vmview.CurrencyInfo.ThousandSeparator); - Assert.Equal(test.ExpectedPrefixed, vmview.CurrencyInfo.Prefixed); - Assert.Equal(test.ExpectedDivisibility, vmview.CurrencyInfo.Divisibility); - Assert.Equal(test.ExpectedSymbolSpace, vmview.CurrencyInfo.SymbolSpace); - } - - - //test inventory related features - vmpos = Assert.IsType(Assert - .IsType(apps.UpdatePointOfSale(appId).Result).Model); - vmpos.Title = "hello"; - vmpos.Currency = "BTC"; - vmpos.Template = @" -inventoryitem: - price: 1.0 - title: good apple - inventory: 1 -noninventoryitem: - price: 10.0"; - Assert.IsType(apps.UpdatePointOfSale(appId, vmpos).Result); - - //inventoryitem has 1 item available - Assert.IsType(publicApps - .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "inventoryitem").Result); - //we already bought all available stock so this should fail - await Task.Delay(100); - Assert.IsType(publicApps - .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "inventoryitem").Result); - - //inventoryitem has unlimited items available - Assert.IsType(publicApps - .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "noninventoryitem").Result); - Assert.IsType(publicApps - .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "noninventoryitem").Result); - - //verify invoices where created - invoices = user.BitPay.GetInvoices(); - Assert.Equal(2, invoices.Count(invoice => invoice.ItemCode.Equals("noninventoryitem"))); - var inventoryItemInvoice = - Assert.Single(invoices.Where(invoice => invoice.ItemCode.Equals("inventoryitem"))); - Assert.NotNull(inventoryItemInvoice); - - //let's mark the inventoryitem invoice as invalid, thsi should return the item to back in stock - var controller = tester.PayTester.GetController(user.UserId, user.StoreId); - var appService = tester.PayTester.GetService(); - var eventAggregator = tester.PayTester.GetService(); - Assert.IsType(await controller.ChangeInvoiceState(inventoryItemInvoice.Id, "invalid")); - //check that item is back in stock - TestUtils.Eventually(() => - { - vmpos = Assert.IsType(Assert - .IsType(apps.UpdatePointOfSale(appId).Result).Model); - Assert.Equal(1, - appService.Parse(vmpos.Template, "BTC").Single(item => item.Id == "inventoryitem").Inventory); - }, 10000); - - - //test payment methods option - - vmpos = Assert.IsType(Assert - .IsType(apps.UpdatePointOfSale(appId).Result).Model); - vmpos.Title = "hello"; - vmpos.Currency = "BTC"; - vmpos.Template = @" -btconly: - price: 1.0 - title: good apple - payment_methods: - - BTC -normal: - price: 1.0"; - Assert.IsType(apps.UpdatePointOfSale(appId, vmpos).Result); - Assert.IsType(publicApps - .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "btconly").Result); - Assert.IsType(publicApps - .ViewPointOfSale(appId, PosViewType.Cart, 1, null, null, null, null, "normal").Result); - invoices = user.BitPay.GetInvoices(); - var normalInvoice = invoices.Single(invoice => invoice.ItemCode == "normal"); - var btcOnlyInvoice = invoices.Single(invoice => invoice.ItemCode == "btconly"); - Assert.Single(btcOnlyInvoice.CryptoInfo); - Assert.Equal("BTC", - btcOnlyInvoice.CryptoInfo.First().CryptoCode); - Assert.Equal(PaymentTypes.BTCLike.ToString(), - btcOnlyInvoice.CryptoInfo.First().PaymentType); - - Assert.Equal(2, normalInvoice.CryptoInfo.Length); - Assert.Contains( - normalInvoice.CryptoInfo, - s => PaymentTypes.BTCLike.ToString() == s.PaymentType && new[] { "BTC", "LTC" }.Contains( - s.CryptoCode)); - } - } - - [Fact] [Trait("Fast", "Fast")] public async Task CanScheduleBackgroundTasks() @@ -3719,40 +2908,6 @@ normal: Assert.True(settings.AccountDerivation is DirectDerivationStrategy s3 && s3.Segwit); } - [Fact(Timeout = TestTimeout)] - [Trait("Integration", "Integration")] - [Trait("Altcoins", "Altcoins")] - [Trait("Lightning", "Lightning")] - public async Task CanCreateInvoiceWithSpecificPaymentMethods() - { - using (var tester = ServerTester.Create()) - { - tester.ActivateLightning(); - tester.ActivateLTC(); - await tester.StartAsync(); - await tester.EnsureChannelsSetup(); - var user = tester.NewAccount(); - user.GrantAccess(); - user.RegisterLightningNode("BTC", LightningConnectionType.Charge); - user.RegisterDerivationScheme("BTC"); - user.RegisterDerivationScheme("LTC"); - - var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(100, "BTC")); - Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count); - - - invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(100, "BTC") - { - SupportedTransactionCurrencies = new Dictionary() - { - {"BTC", new InvoiceSupportedTransactionCurrency() {Enabled = true}} - } - }); - - Assert.Single(invoice.SupportedTransactionCurrencies); - } - } - [Fact(Timeout = TestTimeout)] [Trait("Integration", "Integration")] diff --git a/BTCPayServer.Tests/docker-compose.altcoins.yml b/BTCPayServer.Tests/docker-compose.altcoins.yml new file mode 100644 index 000000000..326b1e9f2 --- /dev/null +++ b/BTCPayServer.Tests/docker-compose.altcoins.yml @@ -0,0 +1,419 @@ +version: "3" + +# Run `docker-compose up dev` for bootstrapping your development environment +# Doing so will expose NBXplorer, Bitcoind RPC and postgres port to the host so that tests can Run, +# The Visual Studio launch setting `Docker-regtest` is configured to use this environment. +services: + + tests: + build: + context: .. + dockerfile: BTCPayServer.Tests/Dockerfile + args: + CONFIGURATION_NAME: Altcoins-Release + environment: + TESTS_BTCRPCCONNECTION: server=http://bitcoind:43782;ceiwHEbqWI83:DwubwWsoo3 + TESTS_LTCRPCCONNECTION: server=http://litecoind:43782;ceiwHEbqWI83:DwubwWsoo3 + TESTS_BTCNBXPLORERURL: http://nbxplorer:32838/ + TESTS_LTCNBXPLORERURL: http://nbxplorer:32838/ + TESTS_DB: "Postgres" + TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver + TESTS_HOSTNAME: tests + TESTS_RUN_EXTERNAL_INTEGRATION: ${TESTS_RUN_EXTERNAL_INTEGRATION:-false} + TESTS_AzureBlobStorageConnectionString: ${TESTS_AzureBlobStorageConnectionString:-none} + TEST_MERCHANTLIGHTNINGD: "type=clightning;server=unix://etc/merchant_lightningd_datadir/lightning-rpc" + TEST_CUSTOMERLIGHTNINGD: "type=clightning;server=unix://etc/customer_lightningd_datadir/lightning-rpc" + TEST_MERCHANTCHARGE: "type=charge;server=http://lightning-charged:9112/;api-token=foiewnccewuify" + TEST_MERCHANTLND: "https://lnd:lnd@merchant_lnd:8080/" + TESTS_INCONTAINER: "true" + TESTS_SSHCONNECTION: "root@sshd:22" + TESTS_SSHPASSWORD: "" + TESTS_SSHKEYFILE: "" + TESTS_SOCKSENDPOINT: "tor:9050" + expose: + - "80" + links: + - dev + extra_hosts: + - "tests:127.0.0.1" + volumes: + - "sshd_datadir:/root/.ssh" + - "customer_lightningd_datadir:/etc/customer_lightningd_datadir" + - "merchant_lightningd_datadir:/etc/merchant_lightningd_datadir" + + # The dev container is not actually used, it is just handy to run `docker-compose up dev` to start all services + dev: + image: alpine:3.7 + command: [ "/bin/sh", "-c", "trap : TERM INT; while :; do echo Ready to code and debug like a rockstar!!!; sleep 2073600; done & wait" ] + links: + - nbxplorer + - postgres + - customer_lightningd + - merchant_lightningd + - lightning-charged + - customer_lnd + - merchant_lnd + - sshd + - tor + - monero_wallet + + sshd: + build: + context: . + dockerfile: sshd.Dockerfile + ports: + - "21622:22" + expose: + - 22 + volumes: + - "sshd_datadir:/root/.ssh" + + devlnd: + image: btcpayserver/bitcoin:0.19.0.1 + environment: + BITCOIN_NETWORK: regtest + BITCOIN_EXTRA_ARGS: | + deprecatedrpc=signrawtransaction + connect=bitcoind:39388 + links: + - nbxplorer + - postgres + - customer_lnd + - merchant_lnd + nbxplorer: + image: nicolasdorier/nbxplorer:2.1.35 + restart: unless-stopped + ports: + - "32838:32838" + expose: + - "32838" + environment: + NBXPLORER_NETWORK: regtest + NBXPLORER_CHAINS: "btc,ltc,lbtc" + NBXPLORER_BTCRPCURL: http://bitcoind:43782/ + NBXPLORER_BTCNODEENDPOINT: bitcoind:39388 + NBXPLORER_BTCRPCUSER: ceiwHEbqWI83 + NBXPLORER_BTCRPCPASSWORD: DwubwWsoo3 + NBXPLORER_LTCRPCURL: http://litecoind:43782/ + NBXPLORER_LTCNODEENDPOINT: litecoind:39388 + NBXPLORER_LTCRPCUSER: ceiwHEbqWI83 + NBXPLORER_LTCRPCPASSWORD: DwubwWsoo3 + NBXPLORER_LBTCRPCURL: "http://elementsd-liquid:19332/" + NBXPLORER_LBTCNODEENDPOINT: "elementsd-liquid:19444" + NBXPLORER_LBTCRPCUSER: "liquid" + NBXPLORER_LBTCRPCPASSWORD: "liquid" + NBXPLORER_BIND: 0.0.0.0:32838 + NBXPLORER_MINGAPSIZE: 5 + NBXPLORER_MAXGAPSIZE: 10 + NBXPLORER_VERBOSE: 1 + NBXPLORER_NOAUTH: 1 + links: + - bitcoind + - litecoind + - elementsd-liquid + + + bitcoind: + restart: unless-stopped + image: btcpayserver/bitcoin:0.19.0.1 + environment: + BITCOIN_NETWORK: regtest + BITCOIN_EXTRA_ARGS: |- + rpcuser=ceiwHEbqWI83 + rpcpassword=DwubwWsoo3 + rpcport=43782 + rpcbind=0.0.0.0:43782 + port=39388 + whitelist=0.0.0.0/0 + zmqpubrawblock=tcp://0.0.0.0:28332 + zmqpubrawtx=tcp://0.0.0.0:28333 + deprecatedrpc=signrawtransaction + ports: + - "43782:43782" + - "39388:39388" + expose: + - "43782" # RPC + - "39388" # P2P + - "28332" # ZMQ + - "28333" # ZMQ + volumes: + - "bitcoin_datadir:/data" + + customer_lightningd: + image: btcpayserver/lightning:v0.8.2-dev + stop_signal: SIGKILL + restart: unless-stopped + environment: + EXPOSE_TCP: "true" + LIGHTNINGD_CHAIN: "btc" + LIGHTNINGD_NETWORK: "regtest" + LIGHTNINGD_OPT: | + bitcoin-datadir=/etc/bitcoin + bitcoin-rpcconnect=bitcoind + announce-addr=customer_lightningd + log-level=debug + funding-confirms=1 + dev-fast-gossip + dev-bitcoind-poll=1 + ports: + - "30992:9835" # api port + expose: + - "9735" # server port + - "9835" # api port + volumes: + - "bitcoin_datadir:/etc/bitcoin" + - "customer_lightningd_datadir:/root/.lightning" + links: + - bitcoind + + lightning-charged: + image: shesek/lightning-charge:0.4.19-standalone + restart: unless-stopped + environment: + NETWORK: regtest + API_TOKEN: foiewnccewuify + BITCOIND_RPCCONNECT: bitcoind + volumes: + - "bitcoin_datadir:/etc/bitcoin" + - "lightning_charge_datadir:/data" + - "merchant_lightningd_datadir:/etc/lightning" + expose: + - "9112" # Charge + - "9735" # Lightning + ports: + - "54938:9112" # Charge + links: + - bitcoind + - merchant_lightningd + + merchant_lightningd: + image: btcpayserver/lightning:v0.8.2-dev + stop_signal: SIGKILL + environment: + EXPOSE_TCP: "true" + LIGHTNINGD_CHAIN: "btc" + LIGHTNINGD_NETWORK: "regtest" + LIGHTNINGD_OPT: | + bitcoin-datadir=/etc/bitcoin + bitcoin-rpcconnect=bitcoind + announce-addr=merchant_lightningd + funding-confirms=1 + log-level=debug + dev-fast-gossip + dev-bitcoind-poll=1 + ports: + - "30993:9835" # api port + expose: + - "9735" # server port + - "9835" # api port + volumes: + - "bitcoin_datadir:/etc/bitcoin" + - "merchant_lightningd_datadir:/root/.lightning" + links: + - bitcoind + + litecoind: + restart: unless-stopped + image: nicolasdorier/docker-litecoin:0.16.3 + environment: + BITCOIN_EXTRA_ARGS: |- + rpcuser=ceiwHEbqWI83 + rpcpassword=DwubwWsoo3 + regtest=1 + rpcport=43782 + port=39388 + whitelist=0.0.0.0/0 + ports: + - "43783:43782" + expose: + - "43782" # RPC + - "39388" # P2P + + elementsd-liquid: + restart: always + container_name: btcpayserver_elementsd_liquid + image: btcpayserver/elements:0.18.1.7 + environment: + ELEMENTS_CHAIN: elementsregtest + ELEMENTS_EXTRA_ARGS: | + mainchainrpcport=43782 + mainchainrpchost=bitcoind + mainchainrpcuser=liquid + mainchainrpcpassword=liquid + rpcport=19332 + rpcbind=0.0.0.0:19332 + rpcauth=liquid:c8bf1a8961d97f224cb21224aaa8235d$$402f4a8907683d057b8c58a42940b6e54d1638322a42986ae28ebb844e603ae6 + port=19444 + whitelist=0.0.0.0/0 + validatepegin=0 + initialfreecoins=210000000000000 + con_dyna_deploy_start=99999999999 + expose: + - "19332" + - "19444" + ports: + - "19332:19332" + - "19444:19444" + volumes: + - "elementsd_liquid_datadir:/data" + + postgres: + image: postgres:9.6.5 + ports: + - "39372:5432" + expose: + - "5432" + + merchant_lnd: + image: btcpayserver/lnd:v0.10.2-beta + restart: unless-stopped + environment: + LND_CHAIN: "btc" + LND_ENVIRONMENT: "regtest" + LND_EXPLORERURL: "http://nbxplorer:32838/" + LND_EXTRA_ARGS: | + restlisten=0.0.0.0:8080 + rpclisten=127.0.0.1:10008 + rpclisten=0.0.0.0:10009 + bitcoin.node=bitcoind + bitcoind.rpchost=bitcoind:43782 + bitcoind.zmqpubrawblock=tcp://bitcoind:28332 + bitcoind.zmqpubrawtx=tcp://bitcoind:28333 + externalip=merchant_lnd:9735 + bitcoin.defaultchanconfs=1 + no-macaroons=1 + debuglevel=debug + trickledelay=1000 + ports: + - "35531:8080" + expose: + - "9735" + volumes: + - "merchant_lnd_datadir:/data" + - "bitcoin_datadir:/deps/.bitcoin" + links: + - bitcoind + + customer_lnd: + image: btcpayserver/lnd:v0.10.2-beta + restart: unless-stopped + environment: + LND_CHAIN: "btc" + LND_ENVIRONMENT: "regtest" + LND_EXPLORERURL: "http://nbxplorer:32838/" + LND_EXTRA_ARGS: | + restlisten=0.0.0.0:8080 + rpclisten=127.0.0.1:10008 + rpclisten=0.0.0.0:10009 + bitcoin.node=bitcoind + bitcoind.rpchost=bitcoind:43782 + bitcoind.zmqpubrawblock=tcp://bitcoind:28332 + bitcoind.zmqpubrawtx=tcp://bitcoind:28333 + externalip=customer_lnd:10009 + bitcoin.defaultchanconfs=1 + no-macaroons=1 + debuglevel=debug + trickledelay=1000 + ports: + - "35532:8080" + expose: + - "8080" + - "10009" + volumes: + - "customer_lnd_datadir:/root/.lnd" + - "bitcoin_datadir:/deps/.bitcoin" + links: + - bitcoind + + tor: + restart: unless-stopped + image: btcpayserver/tor:0.4.1.5 + container_name: tor + environment: + TOR_PASSWORD: btcpayserver + ports: + - "9050:9050" # SOCKS + - "9051:9051" # Tor Control + volumes: + - "tor_datadir:/home/tor/.tor" + - "torrcdir:/usr/local/etc/tor" + - "tor_servicesdir:/var/lib/tor/hidden_services" + monerod: + image: btcpayserver/monero:0.15.0.1-amd64 + restart: unless-stopped + container_name: xmr_monerod + entrypoint: sleep 999999 +# entrypoint: monerod --fixed-difficulty 200 --rpc-bind-ip=0.0.0.0 --confirm-external-bind --rpc-bind-port=18081 --block-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/block?cryptoCode=xmr&hash=%s" --testnet --no-igd --hide-my-port --offline + volumes: + - "monero_data:/home/monero/.bitmonero" + ports: + - "18081:18081" + monero_wallet: + image: btcpayserver/monero:0.15.0.1-amd64 + restart: unless-stopped + container_name: xmr_wallet_rpc + entrypoint: monero-wallet-rpc --testnet --rpc-bind-ip=0.0.0.0 --disable-rpc-login --confirm-external-bind --rpc-bind-port=18082 --non-interactive --trusted-daemon --daemon-address=monerod:18081 --wallet-file=/wallet/wallet.keys --password-file=/wallet/password --tx-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/tx?cryptoCode=xmr&hash=%s" + ports: + - "18082:18082" + volumes: + - "./monero_wallet:/wallet" + depends_on: + - monerod + + litecoind: + restart: unless-stopped + image: nicolasdorier/docker-litecoin:0.16.3 + environment: + BITCOIN_EXTRA_ARGS: |- + rpcuser=ceiwHEbqWI83 + rpcpassword=DwubwWsoo3 + regtest=1 + rpcport=43782 + port=39388 + whitelist=0.0.0.0/0 + ports: + - "43783:43782" + expose: + - "43782" # RPC + - "39388" # P2P + + elementsd-liquid: + restart: always + container_name: btcpayserver_elementsd_liquid + image: btcpayserver/elements:0.18.1.7 + environment: + ELEMENTS_CHAIN: elementsregtest + ELEMENTS_EXTRA_ARGS: | + mainchainrpcport=43782 + mainchainrpchost=bitcoind + mainchainrpcuser=liquid + mainchainrpcpassword=liquid + rpcport=19332 + rpcbind=0.0.0.0:19332 + rpcauth=liquid:c8bf1a8961d97f224cb21224aaa8235d$$402f4a8907683d057b8c58a42940b6e54d1638322a42986ae28ebb844e603ae6 + port=19444 + whitelist=0.0.0.0/0 + validatepegin=0 + initialfreecoins=210000000000000 + con_dyna_deploy_start=99999999999 + expose: + - "19332" + - "19444" + ports: + - "19332:19332" + - "19444:19444" + volumes: + - "elementsd_liquid_datadir:/data" +volumes: + sshd_datadir: + bitcoin_datadir: + elementsd_liquid_datadir: + customer_lightningd_datadir: + merchant_lightningd_datadir: + lightning_charge_datadir: + customer_lnd_datadir: + merchant_lnd_datadir: + tor_datadir: + torrcdir: + tor_servicesdir: + monero_data: diff --git a/BTCPayServer.Tests/docker-compose.monero.yml b/BTCPayServer.Tests/docker-compose.monero.yml deleted file mode 100644 index 9baa859a7..000000000 --- a/BTCPayServer.Tests/docker-compose.monero.yml +++ /dev/null @@ -1,28 +0,0 @@ -version: "3" - -services: - - monerod: - image: btcpayserver/monero:0.15.0.1-amd64 - restart: unless-stopped - container_name: xmr_monerod - entrypoint: sleep 999999 -# entrypoint: monerod --fixed-difficulty 200 --rpc-bind-ip=0.0.0.0 --confirm-external-bind --rpc-bind-port=18081 --block-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/block?cryptoCode=xmr&hash=%s" --testnet --no-igd --hide-my-port --offline - volumes: - - "monero_data:/home/monero/.bitmonero" - ports: - - "18081:18081" - monero_wallet: - image: btcpayserver/monero:0.15.0.1-amd64 - restart: unless-stopped - container_name: xmr_wallet_rpc - entrypoint: monero-wallet-rpc --testnet --rpc-bind-ip=0.0.0.0 --disable-rpc-login --confirm-external-bind --rpc-bind-port=18082 --non-interactive --trusted-daemon --daemon-address=monerod:18081 --wallet-file=/wallet/wallet.keys --password-file=/wallet/password --tx-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/tx?cryptoCode=xmr&hash=%s" - ports: - - "18082:18082" - volumes: - - "./monero_wallet:/wallet" - depends_on: - - monerod - -volumes: - monero_data: diff --git a/BTCPayServer.Tests/docker-compose.yml b/BTCPayServer.Tests/docker-compose.yml index 588bfce6b..dc90f8358 100644 --- a/BTCPayServer.Tests/docker-compose.yml +++ b/BTCPayServer.Tests/docker-compose.yml @@ -9,11 +9,11 @@ services: build: context: .. dockerfile: BTCPayServer.Tests/Dockerfile + args: + CONFIGURATION_NAME: Release environment: TESTS_BTCRPCCONNECTION: server=http://bitcoind:43782;ceiwHEbqWI83:DwubwWsoo3 - TESTS_LTCRPCCONNECTION: server=http://litecoind:43782;ceiwHEbqWI83:DwubwWsoo3 TESTS_BTCNBXPLORERURL: http://nbxplorer:32838/ - TESTS_LTCNBXPLORERURL: http://nbxplorer:32838/ TESTS_DB: "Postgres" TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver TESTS_HOSTNAME: tests @@ -86,19 +86,11 @@ services: - "32838" environment: NBXPLORER_NETWORK: regtest - NBXPLORER_CHAINS: "btc,ltc,lbtc" + NBXPLORER_CHAINS: "btc" NBXPLORER_BTCRPCURL: http://bitcoind:43782/ NBXPLORER_BTCNODEENDPOINT: bitcoind:39388 NBXPLORER_BTCRPCUSER: ceiwHEbqWI83 NBXPLORER_BTCRPCPASSWORD: DwubwWsoo3 - NBXPLORER_LTCRPCURL: http://litecoind:43782/ - NBXPLORER_LTCNODEENDPOINT: litecoind:39388 - NBXPLORER_LTCRPCUSER: ceiwHEbqWI83 - NBXPLORER_LTCRPCPASSWORD: DwubwWsoo3 - NBXPLORER_LBTCRPCURL: "http://elementsd-liquid:19332/" - NBXPLORER_LBTCNODEENDPOINT: "elementsd-liquid:19444" - NBXPLORER_LBTCRPCUSER: "liquid" - NBXPLORER_LBTCRPCPASSWORD: "liquid" NBXPLORER_BIND: 0.0.0.0:32838 NBXPLORER_MINGAPSIZE: 5 NBXPLORER_MAXGAPSIZE: 10 @@ -106,8 +98,6 @@ services: NBXPLORER_NOAUTH: 1 links: - bitcoind - - litecoind - - elementsd-liquid bitcoind: @@ -209,51 +199,6 @@ services: links: - bitcoind - litecoind: - restart: unless-stopped - image: nicolasdorier/docker-litecoin:0.16.3 - environment: - BITCOIN_EXTRA_ARGS: |- - rpcuser=ceiwHEbqWI83 - rpcpassword=DwubwWsoo3 - regtest=1 - rpcport=43782 - port=39388 - whitelist=0.0.0.0/0 - ports: - - "43783:43782" - expose: - - "43782" # RPC - - "39388" # P2P - - elementsd-liquid: - restart: always - container_name: btcpayserver_elementsd_liquid - image: btcpayserver/elements:0.18.1.7 - environment: - ELEMENTS_CHAIN: elementsregtest - ELEMENTS_EXTRA_ARGS: | - mainchainrpcport=43782 - mainchainrpchost=bitcoind - mainchainrpcuser=liquid - mainchainrpcpassword=liquid - rpcport=19332 - rpcbind=0.0.0.0:19332 - rpcauth=liquid:c8bf1a8961d97f224cb21224aaa8235d$$402f4a8907683d057b8c58a42940b6e54d1638322a42986ae28ebb844e603ae6 - port=19444 - whitelist=0.0.0.0/0 - validatepegin=0 - initialfreecoins=210000000000000 - con_dyna_deploy_start=99999999999 - expose: - - "19332" - - "19444" - ports: - - "19332:19332" - - "19444:19444" - volumes: - - "elementsd_liquid_datadir:/data" - postgres: image: postgres:9.6.5 ports: @@ -336,7 +281,6 @@ services: - "torrcdir:/usr/local/etc/tor" - "tor_servicesdir:/var/lib/tor/hidden_services" - volumes: sshd_datadir: bitcoin_datadir: diff --git a/BTCPayServer.Tests/docker-entrypoint.sh b/BTCPayServer.Tests/docker-entrypoint.sh index 5f11b488c..a73e5f7be 100755 --- a/BTCPayServer.Tests/docker-entrypoint.sh +++ b/BTCPayServer.Tests/docker-entrypoint.sh @@ -6,4 +6,4 @@ if [ ! -z "$TEST_FILTERS" ]; then FILTERS="--filter $TEST_FILTERS" fi -dotnet test $FILTERS --no-build -v n < /dev/null +dotnet test -c ${CONFIGURATION_NAME} $FILTERS --no-build -v n < /dev/null diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index 41730078f..192fd97ae 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -1,10 +1,7 @@ - + - - false - $(DefineConstants);RAZOR_RUNTIME_COMPILE - + Exe @@ -21,6 +18,10 @@ + + true + PreserveNewest + @@ -28,6 +29,21 @@ + + + + + + + + + + + + + + + diff --git a/BTCPayServer/Configuration/BTCPayServerOptions.cs b/BTCPayServer/Configuration/BTCPayServerOptions.cs index b596bb90d..2eae346b7 100644 --- a/BTCPayServer/Configuration/BTCPayServerOptions.cs +++ b/BTCPayServer/Configuration/BTCPayServerOptions.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using NBitcoin; using Serilog.Events; +using TwentyTwenty.Storage; namespace BTCPayServer.Configuration { @@ -90,11 +91,14 @@ namespace BTCPayServer.Configuration var networkProvider = new BTCPayNetworkProvider(NetworkType); var filtered = networkProvider.Filter(supportedChains.ToArray()); - var elementsBased = filtered.GetAll().OfType(); - var parentChains = elementsBased.Select(network => network.NetworkCryptoCode.ToUpperInvariant()).Distinct(); - var allSubChains = networkProvider.GetAll().OfType() - .Where(network => parentChains.Contains(network.NetworkCryptoCode)).Select(network => network.CryptoCode.ToUpperInvariant()); - supportedChains.AddRange(allSubChains); +#if ALTCOINS + supportedChains.AddRange(filtered.GetAllElementsSubChains()); +#endif +#if !ALTCOINS + var onlyBTC = supportedChains.Count == 1 && supportedChains.First() == "BTC"; + if (!onlyBTC) + throw new ConfigException($"This build of BTCPay Server does not support altcoins"); +#endif NetworkProvider = networkProvider.Filter(supportedChains.ToArray()); foreach (var chain in supportedChains) { diff --git a/BTCPayServer/Contracts/ISyncSummaryProvider.cs b/BTCPayServer/Contracts/ISyncSummaryProvider.cs new file mode 100644 index 000000000..44cdc918c --- /dev/null +++ b/BTCPayServer/Contracts/ISyncSummaryProvider.cs @@ -0,0 +1,10 @@ +namespace BTCPayServer.Contracts +{ + public interface ISyncSummaryProvider + { + bool AllAvailable(); + + string Partial { get; } + } + +} diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index a55965bb5..99384dcde 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -520,7 +520,9 @@ namespace BTCPayServer.Controllers Value = value, WalletId = new WalletId(store.Id, paymentMethodId.CryptoCode), Enabled = !excludeFilters.Match(paymentMethodId) && strategy != null, +#if ALTCOINS Collapsed = network is ElementsBTCPayNetwork elementsBTCPayNetwork && elementsBTCPayNetwork.NetworkCryptoCode != elementsBTCPayNetwork.CryptoCode && string.IsNullOrEmpty(value) +#endif }); break; case LightningPaymentType _: diff --git a/BTCPayServer/Extensions/MoneyExtensions.cs b/BTCPayServer/Extensions/MoneyExtensions.cs index 736975d12..a764774b3 100644 --- a/BTCPayServer/Extensions/MoneyExtensions.cs +++ b/BTCPayServer/Extensions/MoneyExtensions.cs @@ -16,6 +16,7 @@ namespace BTCPayServer return money.ToDecimal(MoneyUnit.BTC); case MoneyBag mb: return mb.Select(money => money.GetValue(network)).Sum(); +#if ALTCOINS case AssetMoney assetMoney: if (network is ElementsBTCPayNetwork elementsBTCPayNetwork) { @@ -24,6 +25,7 @@ namespace BTCPayServer : 0; } throw new NotSupportedException("IMoney type not supported"); +#endif default: throw new NotSupportedException("IMoney type not supported"); } diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index 99bed5819..746b1f3da 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -2,6 +2,7 @@ using System; using System.IO; using System.Threading; using BTCPayServer.Configuration; +using BTCPayServer.Contracts; using BTCPayServer.Controllers; using BTCPayServer.Data; using BTCPayServer.HostedServices; @@ -16,7 +17,6 @@ using BTCPayServer.Security; using BTCPayServer.Security.Bitpay; using BTCPayServer.Security.GreenField; using BTCPayServer.Services; -using BTCPayServer.Services.Altcoins.Monero; using BTCPayServer.Services.Apps; using BTCPayServer.Services.Fees; using BTCPayServer.Services.Invoices; @@ -47,7 +47,9 @@ using NBXplorer.DerivationStrategy; using Newtonsoft.Json; using NicolasDorier.RateLimits; using Serilog; - +#if ALTCOINS +using BTCPayServer.Services.Altcoins.Monero; +#endif namespace BTCPayServer.Hosting { public static class BTCPayServerServices @@ -75,7 +77,9 @@ namespace BTCPayServer.Hosting services.RegisterJsonConverter(n => new ClaimDestinationJsonConverter(n)); services.AddPayJoinServices(); +#if ALTCOINS services.AddMoneroLike(); +#endif services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); @@ -177,6 +181,7 @@ namespace BTCPayServer.Hosting services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); diff --git a/BTCPayServer/Payments/PaymentTypes.cs b/BTCPayServer/Payments/PaymentTypes.cs index e83cad53c..391c79c30 100644 --- a/BTCPayServer/Payments/PaymentTypes.cs +++ b/BTCPayServer/Payments/PaymentTypes.cs @@ -1,5 +1,7 @@ using System; +#if ALTCOINS using BTCPayServer.Services.Altcoins.Monero.Payments; +#endif using BTCPayServer.Services.Invoices; using NBitcoin; using Newtonsoft.Json.Linq; @@ -32,9 +34,11 @@ namespace BTCPayServer.Payments case "offchain": type = PaymentTypes.LightningLike; break; +#if ALTCOINS case "monerolike": type = MoneroPaymentType.Instance; break; +#endif default: type = null; return false; diff --git a/BTCPayServer/Properties/launchSettings.json b/BTCPayServer/Properties/launchSettings.json index 813391655..79d84c25c 100644 --- a/BTCPayServer/Properties/launchSettings.json +++ b/BTCPayServer/Properties/launchSettings.json @@ -1,13 +1,12 @@ { "profiles": { - "Docker-Regtest": { + "Bitcoin": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { "BTCPAY_NETWORK": "regtest", "BTCPAY_LAUNCHSETTINGS": "true", "BTCPAY_BUNDLEJSCSS": "false", - "BTCPAY_LTCEXPLORERURL": "http://127.0.0.1:32838/", "BTCPAY_BTCLIGHTNING": "type=clightning;server=tcp://127.0.0.1:30993", "BTCPAY_BTCEXTERNALLNDGRPC": "type=lnd-grpc;server=https://lnd:lnd@127.0.0.1:53280/;allowinsecure=true", "BTCPAY_BTCEXTERNALLNDREST": "type=lnd-rest;server=https://lnd:lnd@127.0.0.1:53280/lnd-rest/btc/;allowinsecure=true;macaroonfilepath=D:\\admin.macaroon", @@ -16,7 +15,7 @@ "BTCPAY_ALLOW-ADMIN-REGISTRATION": "true", "BTCPAY_DISABLE-REGISTRATION": "false", "ASPNETCORE_ENVIRONMENT": "Development", - "BTCPAY_CHAINS": "btc,ltc", + "BTCPAY_CHAINS": "btc", "BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver", "BTCPAY_DEBUGLOG": "debug.log", "BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc", @@ -24,7 +23,38 @@ }, "applicationUrl": "http://127.0.0.1:14142/" }, - "Docker-Regtest-https": { + "Bitcoin-HTTPS": { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "BTCPAY_NETWORK": "regtest", + "BTCPAY_LAUNCHSETTINGS": "true", + "BTCPAY_PORT": "14142", + "BTCPAY_HttpsUseDefaultCertificate": "true", + "BTCPAY_BUNDLEJSCSS": "false", + "BTCPAY_BTCLIGHTNING": "type=clightning;server=tcp://127.0.0.1:30993", + "BTCPAY_BTCEXTERNALLNDGRPC": "type=lnd-grpc;server=https://lnd:lnd@127.0.0.1:53280/;allowinsecure=true", + "BTCPAY_BTCEXTERNALLNDREST": "type=lnd-rest;server=https://lnd:lnd@127.0.0.1:53280/lnd-rest/btc/;allowinsecure=true", + "BTCPAY_BTCEXTERNALLNDSEEDBACKUP": "../BTCPayServer.Tests/TestData/LndSeedBackup/walletunlock.json", + "BTCPAY_BTCEXTERNALSPARK": "server=/spark/btc/;cookiefile=fake", + "BTCPAY_BTCEXTERNALCHARGE": "server=https://127.0.0.1:53280/mycharge/btc/;cookiefilepath=fake", + "BTCPAY_EXTERNALCONFIGURATOR": "passwordfile=testpwd;server=/configurator", + "BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/", + "BTCPAY_ALLOW-ADMIN-REGISTRATION": "true", + "BTCPAY_DISABLE-REGISTRATION": "false", + "ASPNETCORE_ENVIRONMENT": "Development", + "BTCPAY_CHAINS": "btc", + "BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver", + "BTCPAY_EXTERNALSERVICES": "totoservice:totolink;", + "BTCPAY_SSHCONNECTION": "root@127.0.0.1:21622", + "BTCPAY_SSHPASSWORD": "opD3i2282D", + "BTCPAY_DEBUGLOG": "debug.log", + "BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc", + "BTCPAY_SOCKSENDPOINT": "localhost:9050" + }, + "applicationUrl": "https://localhost:14142/" + }, + "Altcoins-HTTPS": { "commandName": "Project", "launchBrowser": true, "environmentVariables": { diff --git a/BTCPayServer/Services/Altcoins/Monero/Configuration/MoneroLikeConfiguration.cs b/BTCPayServer/Services/Altcoins/Monero/Configuration/MoneroLikeConfiguration.cs index 3d6a60a35..01bc77803 100644 --- a/BTCPayServer/Services/Altcoins/Monero/Configuration/MoneroLikeConfiguration.cs +++ b/BTCPayServer/Services/Altcoins/Monero/Configuration/MoneroLikeConfiguration.cs @@ -1,3 +1,4 @@ +#if ALTCOINS using System; using System.Collections.Generic; @@ -16,3 +17,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.Configuration public string WalletDirectory { get; set; } } } +#endif diff --git a/BTCPayServer/Services/Altcoins/Monero/MoneroLikeExtensions.cs b/BTCPayServer/Services/Altcoins/Monero/MoneroLikeExtensions.cs index b8926a728..527e2829d 100644 --- a/BTCPayServer/Services/Altcoins/Monero/MoneroLikeExtensions.cs +++ b/BTCPayServer/Services/Altcoins/Monero/MoneroLikeExtensions.cs @@ -1,3 +1,4 @@ +#if ALTCOINS using System; using System.Linq; using BTCPayServer.Configuration; @@ -23,6 +24,7 @@ namespace BTCPayServer.Services.Altcoins.Monero serviceCollection.AddSingleton(); serviceCollection.AddSingleton(provider => provider.GetService()); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); return serviceCollection; } @@ -67,3 +69,4 @@ namespace BTCPayServer.Services.Altcoins.Monero } } } +#endif diff --git a/BTCPayServer/Services/Altcoins/Monero/MoneroStoreNavExtension.cs b/BTCPayServer/Services/Altcoins/Monero/MoneroStoreNavExtension.cs index c0f9ff3c7..375951f14 100644 --- a/BTCPayServer/Services/Altcoins/Monero/MoneroStoreNavExtension.cs +++ b/BTCPayServer/Services/Altcoins/Monero/MoneroStoreNavExtension.cs @@ -1,3 +1,4 @@ +#if ALTCOINS using BTCPayServer.Contracts; namespace BTCPayServer.Services.Altcoins.Monero @@ -7,3 +8,4 @@ namespace BTCPayServer.Services.Altcoins.Monero public string Partial { get; } = "Monero/StoreNavMoneroExtension"; } } +#endif diff --git a/BTCPayServer/Services/Altcoins/Monero/Payments/MoneroLikeOnChainPaymentMethodDetails.cs b/BTCPayServer/Services/Altcoins/Monero/Payments/MoneroLikeOnChainPaymentMethodDetails.cs index 5c343a10e..d71173b34 100644 --- a/BTCPayServer/Services/Altcoins/Monero/Payments/MoneroLikeOnChainPaymentMethodDetails.cs +++ b/BTCPayServer/Services/Altcoins/Monero/Payments/MoneroLikeOnChainPaymentMethodDetails.cs @@ -1,3 +1,4 @@ +#if ALTCOINS using BTCPayServer.Payments; namespace BTCPayServer.Services.Altcoins.Monero.Payments @@ -34,3 +35,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments public decimal NextNetworkFee { get; set; } } } +#endif diff --git a/BTCPayServer/Services/Altcoins/Monero/Payments/MoneroLikePaymentData.cs b/BTCPayServer/Services/Altcoins/Monero/Payments/MoneroLikePaymentData.cs index d12a88f2a..e572fe2dd 100644 --- a/BTCPayServer/Services/Altcoins/Monero/Payments/MoneroLikePaymentData.cs +++ b/BTCPayServer/Services/Altcoins/Monero/Payments/MoneroLikePaymentData.cs @@ -1,3 +1,4 @@ +#if ALTCOINS using BTCPayServer.Client.Models; using BTCPayServer.Payments; using BTCPayServer.Services.Altcoins.Monero.Utils; @@ -65,3 +66,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments } } } +#endif diff --git a/BTCPayServer/Services/Altcoins/Monero/Payments/MoneroLikePaymentMethodHandler.cs b/BTCPayServer/Services/Altcoins/Monero/Payments/MoneroLikePaymentMethodHandler.cs index d0c9da666..5ad4f0825 100644 --- a/BTCPayServer/Services/Altcoins/Monero/Payments/MoneroLikePaymentMethodHandler.cs +++ b/BTCPayServer/Services/Altcoins/Monero/Payments/MoneroLikePaymentMethodHandler.cs @@ -1,3 +1,4 @@ +#if ALTCOINS using System; using System.Collections.Generic; using System.Globalization; @@ -123,3 +124,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments } } } +#endif diff --git a/BTCPayServer/Services/Altcoins/Monero/Payments/MoneroPaymentType.cs b/BTCPayServer/Services/Altcoins/Monero/Payments/MoneroPaymentType.cs index e16eaff2a..184d6c1d6 100644 --- a/BTCPayServer/Services/Altcoins/Monero/Payments/MoneroPaymentType.cs +++ b/BTCPayServer/Services/Altcoins/Monero/Payments/MoneroPaymentType.cs @@ -1,3 +1,4 @@ +#if ALTCOINS using System.Globalization; using BTCPayServer.Payments; using BTCPayServer.Services.Invoices; @@ -54,3 +55,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments public override string InvoiceViewPaymentPartialName { get; } = "Monero/ViewMoneroLikePaymentData"; } } +#endif diff --git a/BTCPayServer/Services/Altcoins/Monero/Payments/MoneroSupportedPaymentMethod.cs b/BTCPayServer/Services/Altcoins/Monero/Payments/MoneroSupportedPaymentMethod.cs index 8790ec0d4..5233b283a 100644 --- a/BTCPayServer/Services/Altcoins/Monero/Payments/MoneroSupportedPaymentMethod.cs +++ b/BTCPayServer/Services/Altcoins/Monero/Payments/MoneroSupportedPaymentMethod.cs @@ -1,3 +1,4 @@ +#if ALTCOINS using BTCPayServer.Payments; namespace BTCPayServer.Services.Altcoins.Monero.Payments @@ -10,3 +11,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments public PaymentMethodId PaymentId => new PaymentMethodId(CryptoCode, MoneroPaymentType.Instance); } } +#endif diff --git a/BTCPayServer/Services/Altcoins/Monero/RPC/MoneroDaemonCallbackController.cs b/BTCPayServer/Services/Altcoins/Monero/RPC/MoneroDaemonCallbackController.cs index 9db2d24e3..f812bcfd8 100644 --- a/BTCPayServer/Services/Altcoins/Monero/RPC/MoneroDaemonCallbackController.cs +++ b/BTCPayServer/Services/Altcoins/Monero/RPC/MoneroDaemonCallbackController.cs @@ -1,3 +1,4 @@ +#if ALTCOINS using BTCPayServer.Filters; using Microsoft.AspNetCore.Mvc; @@ -36,3 +37,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.RPC } } +#endif diff --git a/BTCPayServer/Services/Altcoins/Monero/RPC/MoneroEvent.cs b/BTCPayServer/Services/Altcoins/Monero/RPC/MoneroEvent.cs index 4821730ec..77250b4ab 100644 --- a/BTCPayServer/Services/Altcoins/Monero/RPC/MoneroEvent.cs +++ b/BTCPayServer/Services/Altcoins/Monero/RPC/MoneroEvent.cs @@ -1,3 +1,4 @@ +#if ALTCOINS namespace BTCPayServer.Services.Altcoins.Monero.RPC { public class MoneroEvent @@ -13,3 +14,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.RPC } } } +#endif diff --git a/BTCPayServer/Services/Altcoins/Monero/Services/MoneroLikeSummaryUpdaterHostedService.cs b/BTCPayServer/Services/Altcoins/Monero/Services/MoneroLikeSummaryUpdaterHostedService.cs index 5bdd511a6..91c9210d4 100644 --- a/BTCPayServer/Services/Altcoins/Monero/Services/MoneroLikeSummaryUpdaterHostedService.cs +++ b/BTCPayServer/Services/Altcoins/Monero/Services/MoneroLikeSummaryUpdaterHostedService.cs @@ -1,3 +1,4 @@ +#if ALTCOINS using System; using System.Threading; using System.Threading.Tasks; @@ -64,3 +65,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services } } } +#endif diff --git a/BTCPayServer/Services/Altcoins/Monero/Services/MoneroListener.cs b/BTCPayServer/Services/Altcoins/Monero/Services/MoneroListener.cs index 8f3bbca69..95d9e0fb8 100644 --- a/BTCPayServer/Services/Altcoins/Monero/Services/MoneroListener.cs +++ b/BTCPayServer/Services/Altcoins/Monero/Services/MoneroListener.cs @@ -1,3 +1,4 @@ +#if ALTCOINS using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -374,3 +375,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services } } } +#endif diff --git a/BTCPayServer/Services/Altcoins/Monero/Services/MoneroRPCProvider.cs b/BTCPayServer/Services/Altcoins/Monero/Services/MoneroRPCProvider.cs index 63552dfef..a0d08cd64 100644 --- a/BTCPayServer/Services/Altcoins/Monero/Services/MoneroRPCProvider.cs +++ b/BTCPayServer/Services/Altcoins/Monero/Services/MoneroRPCProvider.cs @@ -1,3 +1,4 @@ +#if ALTCOINS using System; using System.Collections.Concurrent; using System.Collections.Immutable; @@ -117,3 +118,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services } } } +#endif diff --git a/BTCPayServer/Services/Altcoins/Monero/Services/MoneroSyncSummaryProvider.cs b/BTCPayServer/Services/Altcoins/Monero/Services/MoneroSyncSummaryProvider.cs new file mode 100644 index 000000000..b1beb68f6 --- /dev/null +++ b/BTCPayServer/Services/Altcoins/Monero/Services/MoneroSyncSummaryProvider.cs @@ -0,0 +1,24 @@ +#if ALTCOINS +using System.Linq; +using BTCPayServer.Contracts; + +namespace BTCPayServer.Services.Altcoins.Monero.Services +{ + public class MoneroSyncSummaryProvider : ISyncSummaryProvider + { + private readonly MoneroRPCProvider _moneroRpcProvider; + + public MoneroSyncSummaryProvider(MoneroRPCProvider moneroRpcProvider) + { + _moneroRpcProvider = moneroRpcProvider; + } + + public bool AllAvailable() + { + return _moneroRpcProvider.Summaries.All(pair => pair.Value.WalletAvailable); + } + + public string Partial { get; } = "Monero/MoneroSyncSummary"; + } +} +#endif diff --git a/BTCPayServer/Services/Altcoins/Monero/UI/MoneroLikeStoreController.cs b/BTCPayServer/Services/Altcoins/Monero/UI/MoneroLikeStoreController.cs index 697aaa7fc..5c9fbfc58 100644 --- a/BTCPayServer/Services/Altcoins/Monero/UI/MoneroLikeStoreController.cs +++ b/BTCPayServer/Services/Altcoins/Monero/UI/MoneroLikeStoreController.cs @@ -1,3 +1,4 @@ +#if ALTCOINS using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -302,3 +303,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI } } } +#endif diff --git a/BTCPayServer/Services/Altcoins/Monero/UI/MoneroPaymentViewModel.cs b/BTCPayServer/Services/Altcoins/Monero/UI/MoneroPaymentViewModel.cs index 5b1f0ac82..8ef7efbdc 100644 --- a/BTCPayServer/Services/Altcoins/Monero/UI/MoneroPaymentViewModel.cs +++ b/BTCPayServer/Services/Altcoins/Monero/UI/MoneroPaymentViewModel.cs @@ -1,3 +1,4 @@ +#if ALTCOINS using System; namespace BTCPayServer.Services.Altcoins.Monero.UI @@ -13,3 +14,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI public string TransactionLink { get; set; } } } +#endif diff --git a/BTCPayServer/Services/BTCPayServerEnvironment.cs b/BTCPayServer/Services/BTCPayServerEnvironment.cs index fbd146a55..8cf1100d7 100644 --- a/BTCPayServer/Services/BTCPayServerEnvironment.cs +++ b/BTCPayServer/Services/BTCPayServerEnvironment.cs @@ -72,6 +72,9 @@ namespace BTCPayServer.Services { StringBuilder txt = new StringBuilder(); txt.Append($"@Copyright BTCPayServer v{Version}"); +#if ALTCOINS + txt.Append($" (altcoins)"); +#endif if (!Environment.IsProduction() || !Build.Equals("Release", StringComparison.OrdinalIgnoreCase)) { txt.Append($" Environment: {Environment.EnvironmentName} Build: {Build}"); diff --git a/BTCPayServer/Services/NBXSyncSummaryProvider.cs b/BTCPayServer/Services/NBXSyncSummaryProvider.cs new file mode 100644 index 000000000..32d4451af --- /dev/null +++ b/BTCPayServer/Services/NBXSyncSummaryProvider.cs @@ -0,0 +1,22 @@ +using BTCPayServer.Contracts; +using BTCPayServer.HostedServices; + +namespace BTCPayServer.Services +{ + public class NBXSyncSummaryProvider : ISyncSummaryProvider + { + private readonly NBXplorerDashboard _nbXplorerDashboard; + + public NBXSyncSummaryProvider(NBXplorerDashboard nbXplorerDashboard) + { + _nbXplorerDashboard = nbXplorerDashboard; + } + + public bool AllAvailable() + { + return _nbXplorerDashboard.IsFullySynched(); + } + + public string Partial { get; } = "NBXSyncSummary"; + } +} diff --git a/BTCPayServer/Views/Shared/LayoutPartials/SyncModal.cshtml b/BTCPayServer/Views/Shared/LayoutPartials/SyncModal.cshtml index 8315623b7..5c5102200 100644 --- a/BTCPayServer/Views/Shared/LayoutPartials/SyncModal.cshtml +++ b/BTCPayServer/Views/Shared/LayoutPartials/SyncModal.cshtml @@ -1,7 +1,6 @@ -@using BTCPayServer.Services.Altcoins.Monero.Services -@inject BTCPayServer.HostedServices.NBXplorerDashboard dashboard -@inject MoneroRPCProvider MoneroRpcProvider -@if (!dashboard.IsFullySynched() || !MoneroRpcProvider.Summaries.All(pair => pair.Value.WalletAvailable)) +@using BTCPayServer.Contracts +@inject IEnumerable SyncSummaryProviders; +@if(SyncSummaryProviders.Any(provider => !provider.AllAvailable())) { -