diff --git a/BTCPayServer.Tests/BTCPayServer.Tests.csproj b/BTCPayServer.Tests/BTCPayServer.Tests.csproj index b717e0bfc..6570a680c 100644 --- a/BTCPayServer.Tests/BTCPayServer.Tests.csproj +++ b/BTCPayServer.Tests/BTCPayServer.Tests.csproj @@ -5,6 +5,7 @@ false NU1701,CA1816,CA1308,CA1810,CA2208 + 7.2 diff --git a/BTCPayServer.Tests/BTCPayServerTester.cs b/BTCPayServer.Tests/BTCPayServerTester.cs index 3f0b16985..6501d4b2d 100644 --- a/BTCPayServer.Tests/BTCPayServerTester.cs +++ b/BTCPayServer.Tests/BTCPayServerTester.cs @@ -120,7 +120,7 @@ namespace BTCPayServer.Tests .Build(); _Host.Start(); InvoiceRepository = (InvoiceRepository)_Host.Services.GetService(typeof(InvoiceRepository)); - + StoreRepository = (StoreRepository)_Host.Services.GetService(typeof(StoreRepository)); var rateProvider = (BTCPayRateProviderFactory)_Host.Services.GetService(typeof(BTCPayRateProviderFactory)); rateProvider.DirectProviders.Clear(); @@ -152,6 +152,7 @@ namespace BTCPayServer.Tests internal set; } public InvoiceRepository InvoiceRepository { get; private set; } + public StoreRepository StoreRepository { get; private set; } public Uri IntegratedLightning { get; internal set; } public bool InContainer { get; internal set; } diff --git a/BTCPayServer.Tests/ChargeTester.cs b/BTCPayServer.Tests/ChargeTester.cs index 4eb5479bf..18b08582c 100644 --- a/BTCPayServer.Tests/ChargeTester.cs +++ b/BTCPayServer.Tests/ChargeTester.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using BTCPayServer.Payments.Lightning; using BTCPayServer.Payments.Lightning.Charge; using BTCPayServer.Payments.Lightning.CLightning; using NBitcoin; @@ -15,7 +16,7 @@ namespace BTCPayServer.Tests { this._Parent = serverTester; var url = serverTester.GetEnvironment(environmentName, defaultValue); - Client = new ChargeClient(new Uri(url), network); + Client = (ChargeClient)LightningClientFactory.CreateClient(url, network); P2PHost = _Parent.GetEnvironment(environmentName + "_HOST", defaultHost); } public ChargeClient Client { get; set; } diff --git a/BTCPayServer.Tests/Lnd/LndMockTester.cs b/BTCPayServer.Tests/Lnd/LndMockTester.cs new file mode 100644 index 000000000..83ab6efa6 --- /dev/null +++ b/BTCPayServer.Tests/Lnd/LndMockTester.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; +using BTCPayServer.Payments.Lightning.Lnd; +using NBitcoin; + +namespace BTCPayServer.Tests.Lnd +{ + public class LndMockTester + { + private ServerTester _Parent; + + public LndMockTester(ServerTester serverTester, string environmentName, string defaultValue, string defaultHost, Network network) + { + this._Parent = serverTester; + var url = serverTester.GetEnvironment(environmentName, defaultValue); + + Swagger = new LndSwaggerClient(new LndRestSettings(new Uri(url)) { AllowInsecure = true }); + Client = new LndInvoiceClient(Swagger); + P2PHost = _Parent.GetEnvironment(environmentName + "_HOST", defaultHost); + } + + public LndSwaggerClient Swagger { get; set; } + public LndInvoiceClient Client { get; set; } + public string P2PHost { get; } + } +} diff --git a/BTCPayServer.Tests/Lnd/UnitTests.cs b/BTCPayServer.Tests/Lnd/UnitTests.cs new file mode 100644 index 000000000..40f75b910 --- /dev/null +++ b/BTCPayServer.Tests/Lnd/UnitTests.cs @@ -0,0 +1,246 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using BTCPayServer.Payments.Lightning; +using BTCPayServer.Payments.Lightning.Lnd; +using NBitcoin; +using NBitcoin.RPC; +using Xunit; +using Xunit.Abstractions; +using System.Linq; +using System.Threading; +using NBitpayClient; +using System.Globalization; +using Xunit.Sdk; + +namespace BTCPayServer.Tests.Lnd +{ + // this depends for now on `docker-compose up devlnd` + public class UnitTests + { + private readonly ITestOutputHelper output; + + public UnitTests(ITestOutputHelper output) + { + this.output = output; + initializeEnvironment(); + + MerchantLnd = new LndSwaggerClient(new LndRestSettings(new Uri("https://127.0.0.1:53280")) { AllowInsecure = true }); + InvoiceClient = new LndInvoiceClient(MerchantLnd); + + CustomerLnd = new LndSwaggerClient(new LndRestSettings(new Uri("https://127.0.0.1:53281")) { AllowInsecure = true }); + } + + private LndSwaggerClient MerchantLnd { get; set; } + private LndInvoiceClient InvoiceClient { get; set; } + + private LndSwaggerClient CustomerLnd { get; set; } + + [Fact] + public async Task GetInfo() + { + var res = await InvoiceClient.GetInfo(); + output.WriteLine("Result: " + res.ToJson()); + } + + [Fact] + public async Task CreateInvoice() + { + var res = await InvoiceClient.CreateInvoice(10000, "Hello world", TimeSpan.FromSeconds(3600)); + output.WriteLine("Result: " + res.ToJson()); + } + + [Fact] + public async Task GetInvoice() + { + var createInvoice = await InvoiceClient.CreateInvoice(10000, "Hello world", TimeSpan.FromSeconds(3600)); + var getInvoice = await InvoiceClient.GetInvoice(createInvoice.Id); + + Assert.Equal(createInvoice.BOLT11, getInvoice.BOLT11); + } + + [Fact] + public void Play() + { + var seq = new System.Buffers.ReadOnlySequence(new ReadOnlyMemory(new byte[1000])); + var seq2 = seq.Slice(3); + var pos = seq2.GetPosition(0); + } + + // integration tests + [Fact] + public async Task TestWaitListenInvoice() + { + var merchantInvoice = await InvoiceClient.CreateInvoice(10000, "Hello world", TimeSpan.FromSeconds(3600)); + var merchantInvoice2 = await InvoiceClient.CreateInvoice(10000, "Hello world", TimeSpan.FromSeconds(3600)); + + var waitToken = default(CancellationToken); + var listener = await InvoiceClient.Listen(waitToken); + var waitTask = listener.WaitInvoice(waitToken); + + await EnsureLightningChannelAsync(); + var payResponse = await CustomerLnd.SendPaymentSyncAsync(new LnrpcSendRequest + { + Payment_request = merchantInvoice.BOLT11 + }); + + var invoice = await waitTask; + Assert.True(invoice.PaidAt.HasValue); + + var waitTask2 = listener.WaitInvoice(waitToken); + + payResponse = await CustomerLnd.SendPaymentSyncAsync(new LnrpcSendRequest + { + Payment_request = merchantInvoice2.BOLT11 + }); + + invoice = await waitTask2; + Assert.True(invoice.PaidAt.HasValue); + + var waitTask3 = listener.WaitInvoice(waitToken); + await Task.Delay(100); + listener.Dispose(); + Assert.Throws(() => waitTask3.GetAwaiter().GetResult()); + } + + [Fact] + public async Task CreateLndInvoiceAndPay() + { + var merchantInvoice = await InvoiceClient.CreateInvoice(10000, "Hello world", TimeSpan.FromSeconds(3600)); + + await EnsureLightningChannelAsync(); + + await EventuallyAsync(async () => + { + var payResponse = await CustomerLnd.SendPaymentSyncAsync(new LnrpcSendRequest + { + Payment_request = merchantInvoice.BOLT11 + }); + + var invoice = await InvoiceClient.GetInvoice(merchantInvoice.Id); + Assert.True(invoice.PaidAt.HasValue); + }); + } + + private async Task EventuallyAsync(Func act) + { + CancellationTokenSource cts = new CancellationTokenSource(20000); + while (true) + { + try + { + await act(); + break; + } + catch (XunitException) when (!cts.Token.IsCancellationRequested) + { + await Task.Delay(500); + } + } + } + + public async Task EnsureLightningChannelAsync() + { + var merchantInfo = await WaitLNSynched(); + var merchantNodeAddress = new LnrpcLightningAddress + { + Pubkey = merchantInfo.NodeId, + Host = "merchant_lnd:9735" + }; + + while (true) + { + // if channel is pending generate blocks until confirmed + var pendingResponse = await CustomerLnd.PendingChannelsAsync(); + if (pendingResponse.Pending_open_channels? + .Any(a => a.Channel?.Remote_node_pub == merchantNodeAddress.Pubkey) == true) + { + ExplorerNode.Generate(1); + await WaitLNSynched(); + continue; + } + + // check if channel is established + var chanResponse = await CustomerLnd.ListChannelsAsync(null, null, null, null); + LnrpcChannel channelToMerchant = null; + if (chanResponse != null && chanResponse.Channels != null) + { + channelToMerchant = chanResponse.Channels + .Where(a => a.Remote_pubkey == merchantNodeAddress.Pubkey) + .FirstOrDefault(); + } + + if (channelToMerchant == null) + { + // create new channel + var isConnected = await CustomerLnd.ListPeersAsync(); + if (isConnected.Peers == null || + !isConnected.Peers.Any(a => a.Pub_key == merchantInfo.NodeId)) + { + var connectResp = await CustomerLnd.ConnectPeerAsync(new LnrpcConnectPeerRequest + { + Addr = merchantNodeAddress + }); + } + + var addressResponse = await CustomerLnd.NewWitnessAddressAsync(); + var address = BitcoinAddress.Create(addressResponse.Address, Network.RegTest); + await ExplorerNode.SendToAddressAsync(address, Money.Coins(0.2m)); + ExplorerNode.Generate(1); + await WaitLNSynched(); + + var channelReq = new LnrpcOpenChannelRequest + { + Local_funding_amount = 16777215.ToString(CultureInfo.InvariantCulture), + Node_pubkey_string = merchantInfo.NodeId + }; + var channelResp = await CustomerLnd.OpenChannelSyncAsync(channelReq); + } + else + { + // channel exists, return it + ExplorerNode.Generate(1); + await WaitLNSynched(); + return channelToMerchant; + } + } + } + + private async Task WaitLNSynched() + { + while (true) + { + var merchantInfo = await InvoiceClient.GetInfo(); + var blockCount = await ExplorerNode.GetBlockCountAsync(); + if (merchantInfo.BlockHeight != blockCount) + { + await Task.Delay(500); + } + else + { + return merchantInfo; + } + } + } + + + + + // + private void initializeEnvironment() + { + NetworkProvider = new BTCPayNetworkProvider(NetworkType.Regtest); + ExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_BTCRPCCONNECTION", "server=http://127.0.0.1:43782;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork("BTC").NBitcoinNetwork); + } + + public BTCPayNetworkProvider NetworkProvider { get; private set; } + public RPCClient ExplorerNode { get; set; } + + internal string GetEnvironment(string variable, string defaultValue) + { + var var = Environment.GetEnvironmentVariable(variable); + return String.IsNullOrEmpty(var) ? defaultValue : var; + } + } +} diff --git a/BTCPayServer.Tests/ServerTester.cs b/BTCPayServer.Tests/ServerTester.cs index 62bf76d4e..d28b51088 100644 --- a/BTCPayServer.Tests/ServerTester.cs +++ b/BTCPayServer.Tests/ServerTester.cs @@ -20,6 +20,8 @@ using System.Threading; using System.Globalization; using BTCPayServer.Payments.Lightning.CLightning; using BTCPayServer.Payments.Lightning.Charge; +using BTCPayServer.Tests.Lnd; +using BTCPayServer.Payments.Lightning; namespace BTCPayServer.Tests { @@ -47,10 +49,12 @@ namespace BTCPayServer.Tests LTCExplorerClient = new ExplorerClient(NetworkProvider.GetNetwork("LTC").NBXplorerNetwork, new Uri(GetEnvironment("TESTS_LTCNBXPLORERURL", "http://127.0.0.1:32838/"))); var btc = NetworkProvider.GetNetwork("BTC").NBitcoinNetwork; - CustomerLightningD = new CLightningRPCClient(new Uri(GetEnvironment("TEST_CUSTOMERLIGHTNINGD", "tcp://127.0.0.1:30992/")), btc); - MerchantLightningD = new CLightningRPCClient(new Uri(GetEnvironment("TEST_MERCHANTLIGHTNINGD", "tcp://127.0.0.1:30993/")), btc); + CustomerLightningD = (CLightningRPCClient)LightningClientFactory.CreateClient(GetEnvironment("TEST_CUSTOMERLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30992/"), btc); + MerchantLightningD = (CLightningRPCClient)LightningClientFactory.CreateClient(GetEnvironment("TEST_MERCHANTLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30993/"), btc); - MerchantCharge = new ChargeTester(this, "TEST_MERCHANTCHARGE", "http://api-token:foiewnccewuify@127.0.0.1:54938/", "merchant_lightningd", btc); + MerchantCharge = new ChargeTester(this, "TEST_MERCHANTCHARGE", "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify", "merchant_lightningd", btc); + + MerchantLnd = new LndMockTester(this, "TEST_MERCHANTLND", "https://lnd:lnd@127.0.0.1:53280/", "merchant_lnd", btc); PayTester = new BTCPayServerTester(Path.Combine(_Directory, "pay")) { @@ -78,41 +82,54 @@ namespace BTCPayServer.Tests /// /// Connect a customer LN node to the merchant LN node /// - public void PrepareLightning() + public void PrepareLightning(LightningConnectionType lndBackend) { - PrepareLightningAsync().GetAwaiter().GetResult(); + ILightningInvoiceClient client = MerchantCharge.Client; + if (lndBackend == LightningConnectionType.LndREST) + client = MerchantLnd.Client; + + PrepareLightningAsync(client).GetAwaiter().GetResult(); } + private static readonly string[] SKIPPED_STATES = + { "ONCHAIN", "CHANNELD_SHUTTING_DOWN", "CLOSINGD_SIGEXCHANGE", "CLOSINGD_COMPLETE", "FUNDING_SPEND_SEEN" }; + /// /// Connect a customer LN node to the merchant LN node /// /// - public async Task PrepareLightningAsync() + private async Task PrepareLightningAsync(ILightningInvoiceClient client) { + bool awaitingLocking = false; while (true) { - var skippedStates = new[] { "ONCHAIN", "CHANNELD_SHUTTING_DOWN", "CLOSINGD_SIGEXCHANGE", "CLOSINGD_COMPLETE", "FUNDING_SPEND_SEEN" }; - var channel = (await CustomerLightningD.ListPeersAsync()) + var merchantInfo = await WaitLNSynched(client, CustomerLightningD, MerchantLightningD); + + var peers = await CustomerLightningD.ListPeersAsync(); + var filteringToTargetedPeers = peers.Where(a => a.Id == merchantInfo.NodeId); + var channel = filteringToTargetedPeers .SelectMany(p => p.Channels) - .Where(c => !skippedStates.Contains(c.State ?? "")) + .Where(c => !SKIPPED_STATES.Contains(c.State ?? "")) .FirstOrDefault(); + switch (channel?.State) { case null: - var merchantInfo = await WaitLNSynched(); - var clightning = new NodeInfo(merchantInfo.Id, MerchantCharge.P2PHost, merchantInfo.Port); - await CustomerLightningD.ConnectAsync(clightning); var address = await CustomerLightningD.NewAddressAsync(); - await ExplorerNode.SendToAddressAsync(address, Money.Coins(0.2m)); + await ExplorerNode.SendToAddressAsync(address, Money.Coins(0.5m)); ExplorerNode.Generate(1); - await WaitLNSynched(); + await WaitLNSynched(client, CustomerLightningD, MerchantLightningD); await Task.Delay(1000); - await CustomerLightningD.FundChannelAsync(clightning, Money.Satoshis(16777215)); + + var merchantNodeInfo = new NodeInfo(merchantInfo.NodeId, merchantInfo.Address, merchantInfo.P2PPort); + await CustomerLightningD.ConnectAsync(merchantNodeInfo); + await CustomerLightningD.FundChannelAsync(merchantNodeInfo, Money.Satoshis(16777215)); break; case "CHANNELD_AWAITING_LOCKIN": - ExplorerNode.Generate(1); - await WaitLNSynched(); + ExplorerNode.Generate(awaitingLocking ? 1 : 10); + await WaitLNSynched(client, CustomerLightningD, MerchantLightningD); + awaitingLocking = true; break; case "CHANNELD_NORMAL": return; @@ -122,23 +139,29 @@ namespace BTCPayServer.Tests } } - private async Task WaitLNSynched() + private async Task WaitLNSynched(params ILightningInvoiceClient[] clients) { while (true) { - var merchantInfo = await MerchantCharge.Client.GetInfoAsync(); var blockCount = await ExplorerNode.GetBlockCountAsync(); - if (merchantInfo.BlockHeight != blockCount) - { - await Task.Delay(1000); - } - else - { - return merchantInfo; - } + var synching = clients.Select(c => WaitLNSynchedCore(blockCount, c)).ToArray(); + await Task.WhenAll(synching); + if (synching.All(c => c.Result != null)) + return synching[0].Result; + await Task.Delay(1000); } } + private async Task WaitLNSynchedCore(int blockCount, ILightningInvoiceClient client) + { + var merchantInfo = await client.GetInfo(); + if (merchantInfo.BlockHeight == blockCount) + { + return merchantInfo; + } + return null; + } + public void SendLightningPayment(Invoice invoice) { SendLightningPaymentAsync(invoice).GetAwaiter().GetResult(); @@ -152,8 +175,10 @@ namespace BTCPayServer.Tests } public CLightningRPCClient CustomerLightningD { get; set; } + public CLightningRPCClient MerchantLightningD { get; private set; } public ChargeTester MerchantCharge { get; private set; } + public LndMockTester MerchantLnd { get; set; } internal string GetEnvironment(string variable, string defaultValue) { @@ -189,9 +214,14 @@ namespace BTCPayServer.Tests { get; set; } + public List Stores { get; internal set; } = new List(); public void Dispose() { + foreach(var store in Stores) + { + Xunit.Assert.True(PayTester.StoreRepository.DeleteStore(store).GetAwaiter().GetResult()); + } if (PayTester != null) PayTester.Dispose(); } diff --git a/BTCPayServer.Tests/TestAccount.cs b/BTCPayServer.Tests/TestAccount.cs index 085d68349..ca156ae73 100644 --- a/BTCPayServer.Tests/TestAccount.cs +++ b/BTCPayServer.Tests/TestAccount.cs @@ -65,6 +65,7 @@ namespace BTCPayServer.Tests var store = this.GetController(); await store.CreateStore(new CreateStoreViewModel() { Name = "Test Store" }); StoreId = store.CreatedStoreId; + parent.Stores.Add(StoreId); } public BTCPayNetwork SupportedNetwork { get; set; } @@ -117,7 +118,7 @@ namespace BTCPayServer.Tests { get; set; } - + public void RegisterLightningNode(string cryptoCode, LightningConnectionType connectionType) { RegisterLightningNodeAsync(cryptoCode, connectionType).GetAwaiter().GetResult(); @@ -126,15 +127,24 @@ namespace BTCPayServer.Tests public async Task RegisterLightningNodeAsync(string cryptoCode, LightningConnectionType connectionType) { var storeController = this.GetController(); + + string connectionString = null; + if (connectionType == LightningConnectionType.Charge) + connectionString = "type=charge;server=" + parent.MerchantCharge.Client.Uri.AbsoluteUri; + else if (connectionType == LightningConnectionType.CLightning) + connectionString = "type=clightning;server=" + parent.MerchantLightningD.Address.AbsoluteUri; + else if (connectionType == LightningConnectionType.LndREST) + connectionString = $"type=lnd-rest;server={parent.MerchantLnd.Swagger.BaseUrl};allowinsecure=true"; + else + throw new NotSupportedException(connectionType.ToString()); + await storeController.AddLightningNode(StoreId, new LightningNodeViewModel() { - Url = connectionType == LightningConnectionType.Charge ? parent.MerchantCharge.Client.Uri.AbsoluteUri : - connectionType == LightningConnectionType.CLightning ? parent.MerchantLightningD.Address.AbsoluteUri - : throw new NotSupportedException(connectionType.ToString()), + ConnectionString = connectionString, SkipPortTest = true }, "save", "BTC"); if (storeController.ModelState.ErrorCount != 0) Assert.False(true, storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage); } -} + } } diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index a01f94d54..217333774 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -39,6 +39,7 @@ using BTCPayServer.Models; using BTCPayServer.Rating; using BTCPayServer.Validation; using ExchangeSharp; +using System.Security.Cryptography.X509Certificates; namespace BTCPayServer.Tests { @@ -322,7 +323,7 @@ namespace BTCPayServer.Tests [Fact] public void RoundupCurrenciesCorrectly() { - foreach(var test in new[] + foreach (var test in new[] { (0.0005m, "$0.0005 (USD)"), (0.001m, "$0.001 (USD)"), @@ -410,14 +411,21 @@ namespace BTCPayServer.Tests var testResult = storeController.AddLightningNode(user.StoreId, new LightningNodeViewModel() { - Url = tester.MerchantCharge.Client.Uri.AbsoluteUri + ConnectionString = "type=charge;server=" + tester.MerchantCharge.Client.Uri.AbsoluteUri, + SkipPortTest = true // We can't test this as the IP can't be resolved by the test host :( }, "test", "BTC").GetAwaiter().GetResult(); Assert.DoesNotContain("Error", ((LightningNodeViewModel)Assert.IsType(testResult).Model).StatusMessage, StringComparison.OrdinalIgnoreCase); Assert.True(storeController.ModelState.IsValid); Assert.IsType(storeController.AddLightningNode(user.StoreId, new LightningNodeViewModel() { - Url = tester.MerchantCharge.Client.Uri.AbsoluteUri + ConnectionString = "type=charge;server=" + tester.MerchantCharge.Client.Uri.AbsoluteUri + }, "save", "BTC").GetAwaiter().GetResult()); + + // Make sure old connection string format does not work + Assert.IsType(storeController.AddLightningNode(user.StoreId, new LightningNodeViewModel() + { + ConnectionString = tester.MerchantCharge.Client.Uri.AbsoluteUri }, "save", "BTC").GetAwaiter().GetResult()); var storeVm = Assert.IsType(Assert.IsType(storeController.UpdateStore()).Model); @@ -429,106 +437,148 @@ namespace BTCPayServer.Tests public void CanParseLightningURL() { LightningConnectionString conn = null; - Assert.True(LightningConnectionString.TryParse("/test/a", out conn)); - Assert.Equal("unix://test/a", conn.ToString()); - Assert.Equal("unix://test/a", conn.ToUri(true).AbsoluteUri); - Assert.Equal("unix://test/a", conn.ToUri(false).AbsoluteUri); - Assert.Equal(LightningConnectionType.CLightning, conn.ConnectionType); - - Assert.True(LightningConnectionString.TryParse("unix://test/a", out conn)); - Assert.Equal("unix://test/a", conn.ToString()); - Assert.Equal("unix://test/a", conn.ToUri(true).AbsoluteUri); - Assert.Equal("unix://test/a", conn.ToUri(false).AbsoluteUri); - Assert.Equal(LightningConnectionType.CLightning, conn.ConnectionType); - - Assert.True(LightningConnectionString.TryParse("unix://test/a", out conn)); - Assert.Equal("unix://test/a", conn.ToString()); - Assert.Equal("unix://test/a", conn.ToUri(true).AbsoluteUri); - Assert.Equal("unix://test/a", conn.ToUri(false).AbsoluteUri); - Assert.Equal(LightningConnectionType.CLightning, conn.ConnectionType); - - Assert.True(LightningConnectionString.TryParse("tcp://test/a", out conn)); - Assert.Equal("tcp://test/a", conn.ToString()); - Assert.Equal("tcp://test/a", conn.ToUri(true).AbsoluteUri); - Assert.Equal("tcp://test/a", conn.ToUri(false).AbsoluteUri); - Assert.Equal(LightningConnectionType.CLightning, conn.ConnectionType); - - Assert.True(LightningConnectionString.TryParse("http://aaa:bbb@test/a", out conn)); - Assert.Equal("http://aaa:bbb@test/a", conn.ToString()); - Assert.Equal("http://aaa:bbb@test/a", conn.ToUri(true).AbsoluteUri); - Assert.Equal("http://test/a", conn.ToUri(false).AbsoluteUri); - Assert.Equal(LightningConnectionType.Charge, conn.ConnectionType); - Assert.Equal("aaa", conn.Username); - Assert.Equal("bbb", conn.Password); - - Assert.False(LightningConnectionString.TryParse("lol://aaa:bbb@test/a", out conn)); - Assert.False(LightningConnectionString.TryParse("https://test/a", out conn)); - Assert.False(LightningConnectionString.TryParse("unix://dwewoi:dwdwqd@test/a", out conn)); - } - - [Fact] - public void CanSendLightningPayment2() - { - using (var tester = ServerTester.Create()) + Assert.True(LightningConnectionString.TryParse("/test/a", true, out conn)); + for (int i = 0; i < 2; i++) { - tester.Start(); - tester.PrepareLightning(); - var user = tester.NewAccount(); - user.GrantAccess(); - user.RegisterLightningNode("BTC", LightningConnectionType.CLightning); - user.RegisterDerivationScheme("BTC"); - - var invoice = user.BitPay.CreateInvoice(new Invoice() - { - Price = 0.01m, - Currency = "USD", - PosData = "posData", - OrderId = "orderId", - ItemDesc = "Some description" - }); - - tester.SendLightningPayment(invoice); - - Eventually(() => - { - var localInvoice = user.BitPay.GetInvoice(invoice.Id); - Assert.Equal("complete", localInvoice.Status); - Assert.Equal("False", localInvoice.ExceptionStatus.ToString()); - }); + if (i == 1) + Assert.True(LightningConnectionString.TryParse(conn.ToString(), false, out conn)); + Assert.Equal(i == 0, conn.IsLegacy); + Assert.Equal("type=clightning;server=unix://test/a", conn.ToString()); + Assert.Equal("unix://test/a", conn.ToUri(true).AbsoluteUri); + Assert.Equal("unix://test/a", conn.ToUri(false).AbsoluteUri); + Assert.Equal(LightningConnectionType.CLightning, conn.ConnectionType); } + + Assert.True(LightningConnectionString.TryParse("unix://test/a", true, out conn)); + for (int i = 0; i < 2; i++) + { + if (i == 1) + Assert.True(LightningConnectionString.TryParse(conn.ToString(), false, out conn)); + Assert.Equal("type=clightning;server=unix://test/a", conn.ToString()); + Assert.Equal("unix://test/a", conn.ToUri(true).AbsoluteUri); + Assert.Equal("unix://test/a", conn.ToUri(false).AbsoluteUri); + Assert.Equal(LightningConnectionType.CLightning, conn.ConnectionType); + } + + Assert.True(LightningConnectionString.TryParse("unix://test/a", true, out conn)); + for (int i = 0; i < 2; i++) + { + if (i == 1) + Assert.True(LightningConnectionString.TryParse(conn.ToString(), false, out conn)); + Assert.Equal("type=clightning;server=unix://test/a", conn.ToString()); + Assert.Equal("unix://test/a", conn.ToUri(true).AbsoluteUri); + Assert.Equal("unix://test/a", conn.ToUri(false).AbsoluteUri); + Assert.Equal(LightningConnectionType.CLightning, conn.ConnectionType); + } + + Assert.True(LightningConnectionString.TryParse("tcp://test/a", true, out conn)); + for (int i = 0; i < 2; i++) + { + if (i == 1) + Assert.True(LightningConnectionString.TryParse(conn.ToString(), false, out conn)); + Assert.Equal("type=clightning;server=tcp://test/a", conn.ToString()); + Assert.Equal("tcp://test/a", conn.ToUri(true).AbsoluteUri); + Assert.Equal("tcp://test/a", conn.ToUri(false).AbsoluteUri); + Assert.Equal(LightningConnectionType.CLightning, conn.ConnectionType); + } + + Assert.True(LightningConnectionString.TryParse("http://aaa:bbb@test/a", true, out conn)); + for (int i = 0; i < 2; i++) + { + if (i == 1) + Assert.True(LightningConnectionString.TryParse(conn.ToString(), false, out conn)); + Assert.Equal("type=charge;server=http://aaa:bbb@test/a", conn.ToString()); + Assert.Equal("http://aaa:bbb@test/a", conn.ToUri(true).AbsoluteUri); + Assert.Equal("http://test/a", conn.ToUri(false).AbsoluteUri); + Assert.Equal(LightningConnectionType.Charge, conn.ConnectionType); + Assert.Equal("aaa", conn.Username); + Assert.Equal("bbb", conn.Password); + } + + Assert.True(LightningConnectionString.TryParse("http://api-token:bbb@test/a", true, out conn)); + for (int i = 0; i < 2; i++) + { + if (i == 1) + Assert.True(LightningConnectionString.TryParse(conn.ToString(), false, out conn)); + Assert.Equal("type=charge;server=http://test/a;api-token=bbb", conn.ToString()); + } + + Assert.False(LightningConnectionString.TryParse("lol://aaa:bbb@test/a", true, out conn)); + Assert.False(LightningConnectionString.TryParse("https://test/a", true, out conn)); + Assert.False(LightningConnectionString.TryParse("unix://dwewoi:dwdwqd@test/a", true, out conn)); + Assert.False(LightningConnectionString.TryParse("tcp://test/a", false, out conn)); + Assert.False(LightningConnectionString.TryParse("type=charge;server=http://aaa:bbb@test/a;unk=lol", false, out conn)); + Assert.False(LightningConnectionString.TryParse("type=charge;server=tcp://aaa:bbb@test/a", false, out conn)); + Assert.False(LightningConnectionString.TryParse("type=charge", false, out conn)); + Assert.False(LightningConnectionString.TryParse("type=clightning", false, out conn)); + Assert.True(LightningConnectionString.TryParse("type=clightning;server=tcp://aaa:bbb@test/a", false, out conn)); + Assert.True(LightningConnectionString.TryParse("type=clightning;server=/aaa:bbb@test/a", false, out conn)); + Assert.True(LightningConnectionString.TryParse("type=clightning;server=unix://aaa:bbb@test/a", false, out conn)); + Assert.False(LightningConnectionString.TryParse("type=clightning;server=wtf://aaa:bbb@test/a", false, out conn)); + + var macaroon = "0201036c6e640247030a10b0dbbde28f009f83d330bde05075ca251201301a160a0761646472657373120472656164120577726974651a170a08696e766f6963657312047265616412057772697465000006200ae088692e67cf14e767c3d2a4a67ce489150bf810654ff980e1b7a7e263d5e8"; + + var certthumbprint = "c51bb1d402306d0da00e85581b32aa56166bcbab7eb888ff925d7167eb436d06"; + + // We get this format from "openssl x509 -noout -fingerprint -sha256 -inform pem -in " + var certthumbprint2 = "C5:1B:B1:D4:02:30:6D:0D:A0:0E:85:58:1B:32:AA:56:16:6B:CB:AB:7E:B8:88:FF:92:5D:71:67:EB:43:6D:06"; + + var lndUri = $"type=lnd-rest;server=https://lnd:lnd@127.0.0.1:53280/;macaroon={macaroon};certthumbprint={certthumbprint}"; + var lndUri2 = $"type=lnd-rest;server=https://lnd:lnd@127.0.0.1:53280/;macaroon={macaroon};certthumbprint={certthumbprint2}"; + + var certificateHash = new X509Certificate2(Encoders.Hex.DecodeData("2d2d2d2d2d424547494e2043455254494649434154452d2d2d2d2d0a4d494942396a4343415a7967417749424167495156397a62474252724e54716b4e4b55676d72524d377a414b42676771686b6a4f50515144416a41784d5238770a485159445651514b45785a73626d5167595856306232646c626d56795958526c5a43426a5a584a304d51347744415944565151444577564754304e56557a41650a467730784f4441304d6a55794d7a517a4d6a4261467730784f5441324d6a41794d7a517a4d6a42614d444578487a416442674e5642416f54466d78755a4342680a645852765a3256755a584a686447566b49474e6c636e5178446a414d42674e5642414d5442555a50513156544d466b77457759484b6f5a497a6a3043415159490a4b6f5a497a6a304441516344516741454b7557424568564f75707965434157476130766e713262712f59396b41755a78616865646d454553482b753936436d450a397577486b4b2b4a7667547a66385141783550513741357254637155374b57595170303175364f426c5443426b6a414f42674e56485138424166384542414d430a4171517744775944565230544151482f42415577417745422f7a427642674e56485245456144426d6767564754304e565534494a6247396a5957786f62334e300a6877522f4141414268784141414141414141414141414141414141414141414268775373474f69786877514b41457342687753702f717473687754417141724c0a687753702f6d4a72687753702f754f77687753702f714e59687753702f6874436877514b70514157687753702f6c42514d416f4743437147534d343942414d430a413067414d45554349464866716d595a5043647a4a5178386b47586859473834394c31766541364c784d6f7a4f5774356d726835416945413662756e51556c710a6558553070474168776c3041654d726a4d4974394c7652736179756162565a593278343d0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a")) + .GetCertHash(System.Security.Cryptography.HashAlgorithmName.SHA256); + + + Assert.True(LightningConnectionString.TryParse(lndUri, false, out conn)); + Assert.True(LightningConnectionString.TryParse(lndUri2, false, out var conn2)); + Assert.Equal(conn2.ToString(), conn.ToString()); + Assert.Equal(lndUri, conn.ToString()); + Assert.Equal(LightningConnectionType.LndREST, conn.ConnectionType); + Assert.Equal(macaroon, Encoders.Hex.EncodeData(conn.Macaroon)); + Assert.Equal(certthumbprint.Replace(":", "", StringComparison.OrdinalIgnoreCase).ToLowerInvariant(), Encoders.Hex.EncodeData(conn.CertificateThumbprint)); + Assert.True(certificateHash.SequenceEqual(conn.CertificateThumbprint)); + + // AllowInsecure can be set to allow http + Assert.False(LightningConnectionString.TryParse($"type=lnd-rest;server=http://127.0.0.1:53280/;macaroon={macaroon};allowinsecure=false", false, out conn2)); + Assert.True(LightningConnectionString.TryParse($"type=lnd-rest;server=http://127.0.0.1:53280/;macaroon={macaroon};allowinsecure=true", false, out conn2)); + Assert.True(LightningConnectionString.TryParse($"type=lnd-rest;server=http://127.0.0.1:53280/;macaroon={macaroon};allowinsecure=true", false, out conn2)); } [Fact] - public void CanSendLightningPayment() + public void CanSendLightningPaymentCLightning() { + ProcessLightningPayment(LightningConnectionType.CLightning); + } + + [Fact] + public void CanSendLightningPaymentCharge() + { + ProcessLightningPayment(LightningConnectionType.Charge); + } + + [Fact] + public void CanSendLightningPaymentLnd() + { + ProcessLightningPayment(LightningConnectionType.LndREST); + } + + void ProcessLightningPayment(LightningConnectionType type) + { + // For easier debugging and testing + // LightningLikePaymentHandler.LIGHTNING_TIMEOUT = int.MaxValue; using (var tester = ServerTester.Create()) { tester.Start(); - tester.PrepareLightning(); var user = tester.NewAccount(); user.GrantAccess(); - user.RegisterLightningNode("BTC", LightningConnectionType.Charge); + user.RegisterLightningNode("BTC", type); user.RegisterDerivationScheme("BTC"); + + tester.PrepareLightning(type); - var invoice = user.BitPay.CreateInvoice(new Invoice() - { - Price = 0.01m, - Currency = "USD", - PosData = "posData", - OrderId = "orderId", - ItemDesc = "Some description" - }); - - tester.SendLightningPayment(invoice); - - Eventually(() => - { - var localInvoice = user.BitPay.GetInvoice(invoice.Id); - Assert.Equal("complete", localInvoice.Status); - Assert.Equal("False", localInvoice.ExceptionStatus.ToString()); - }); - + Task.WaitAll(CanSendLightningPaymentCore(tester, user)); Task.WaitAll(Enumerable.Range(0, 5) .Select(_ => CanSendLightningPaymentCore(tester, user)) @@ -538,7 +588,10 @@ namespace BTCPayServer.Tests async Task CanSendLightningPaymentCore(ServerTester tester, TestAccount user) { - await Task.Delay(TimeSpan.FromSeconds(RandomUtils.GetUInt32() % 5)); + // TODO: If this parameter is less than 1 second we start having concurrency problems + await Task.Delay(TimeSpan.FromMilliseconds(1000)); + // + var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice() { Price = 0.01m, @@ -1344,8 +1397,9 @@ namespace BTCPayServer.Tests Assert.NotNull(vm.SelectedAppType); Assert.Null(vm.Name); vm.Name = "test"; + vm.SelectedAppType = AppType.PointOfSale.ToString(); var redirectToAction = Assert.IsType(apps.CreateApp(vm).Result); - Assert.Equal(nameof(apps.ListApps), redirectToAction.ActionName); + Assert.Equal(nameof(apps.UpdatePointOfSale), redirectToAction.ActionName); var appList = Assert.IsType(Assert.IsType(apps.ListApps().Result).Model); var appList2 = Assert.IsType(Assert.IsType(apps2.ListApps().Result).Model); Assert.Single(appList.Apps); diff --git a/BTCPayServer.Tests/UnitTestPeusa.cs b/BTCPayServer.Tests/UnitTestPeusa.cs new file mode 100644 index 000000000..14b4e3717 --- /dev/null +++ b/BTCPayServer.Tests/UnitTestPeusa.cs @@ -0,0 +1,48 @@ +using System; +using NBitcoin; +using Xunit; + +namespace BTCPayServer.Tests +{ + // Helper class for testing functionality and generating data needed during coding/debuging + public class UnitTestPeusa + { + // Unit test that generates temorary checkout Bitpay page + // https://forkbitpay.slack.com/archives/C7M093Z55/p1508293682000217 + + // Testnet of Bitpay down + //[Fact] + //public void BitpayCheckout() + //{ + // var key = new Key(Encoders.Hex.DecodeData("7b70a06f35562873e3dcb46005ed0fe78e1991ad906e56adaaafa40ba861e056")); + // var url = new Uri("https://test.bitpay.com/"); + // var btcpay = new Bitpay(key, url); + // var invoice = btcpay.CreateInvoice(new Invoice() + // { + + // Price = 5.0, + // Currency = "USD", + // PosData = "posData", + // OrderId = "cdfd8a5f-6928-4c3b-ba9b-ddf438029e73", + // ItemDesc = "Hello from the otherside" + // }, Facade.Merchant); + + // // go to invoice.Url + // Console.WriteLine(invoice.Url); + //} + + // Generating Extended public key to use on http://localhost:14142/stores/{storeId} + [Fact] + public void GeneratePubkey() + { + var network = Network.RegTest; + + ExtKey masterKey = new ExtKey(); + Console.WriteLine("Master key : " + masterKey.ToString(network)); + ExtPubKey masterPubKey = masterKey.Neuter(); + + ExtPubKey pubkey = masterPubKey.Derive(0); + Console.WriteLine("PubKey " + 0 + " : " + pubkey.ToString(network)); + } + } +} diff --git a/BTCPayServer.Tests/docker-compose.yml b/BTCPayServer.Tests/docker-compose.yml index b12b8c10b..2e093e43e 100644 --- a/BTCPayServer.Tests/docker-compose.yml +++ b/BTCPayServer.Tests/docker-compose.yml @@ -17,9 +17,10 @@ services: TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver TESTS_PORT: 80 TESTS_HOSTNAME: tests - TEST_MERCHANTLIGHTNINGD: "/etc/merchant_lightningd_datadir/lightning-rpc" - TEST_CUSTOMERLIGHTNINGD: "/etc/customer_lightningd_datadir/lightning-rpc" - TEST_MERCHANTCHARGE: http://api-token:foiewnccewuify@lightning-charged:9112/ + TEST_MERCHANTLIGHTNINGD: "type=clightning;server=/etc/merchant_lightningd_datadir/lightning-rpc" + TEST_CUSTOMERLIGHTNINGD: "type=clightning;server=/etc/customer_lightningd_datadir/lightning-rpc" + TEST_MERCHANTCHARGE: "type=charge;server=https://lightning-charged:9112/;api-token=foiewnccewuify;allowinsecure=true" + TEST_MERCHANTLND: "type=lnd-rest;server=https://lnd:lnd@127.0.0.1:53280/;allowinsecure=true" TESTS_INCONTAINER: "true" expose: - "80" @@ -44,9 +45,25 @@ services: - customer_lightningd - merchant_lightningd - lightning-charged + - customer_lnd + - merchant_lnd + + devlnd: + image: nicolasdorier/docker-bitcoin:0.16.0 + environment: + BITCOIN_EXTRA_ARGS: | + regtest=1 + connect=bitcoind:39388 + links: + - nbxplorer + - postgres + - customer_lnd + - merchant_lnd + + nbxplorer: - image: nicolasdorier/nbxplorer:1.0.2.8 + image: nicolasdorier/nbxplorer:1.0.2.14 ports: - "32838:32838" expose: @@ -80,8 +97,13 @@ services: rpcport=43782 port=39388 whitelist=0.0.0.0/0 + zmqpubrawtx=tcp://0.0.0.0:28332 + zmqpubrawblock=tcp://0.0.0.0:28332 + zmqpubrawtxlock=tcp://0.0.0.0:28332 + zmqpubhashblock=tcp://0.0.0.0:28332 ports: - "43782:43782" + - "28332:28332" expose: - "43782" # RPC - "39388" # P2P @@ -89,7 +111,7 @@ services: - "bitcoin_datadir:/data" customer_lightningd: - image: nicolasdorier/clightning:0.0.0.20-dev + image: nicolasdorier/clightning:v0.6-dev environment: EXPOSE_TCP: "true" LIGHTNINGD_OPT: | @@ -112,7 +134,7 @@ services: - bitcoind lightning-charged: - image: shesek/lightning-charge:0.3.12 + image: shesek/lightning-charge:0.3.15 environment: NETWORK: regtest API_TOKEN: foiewnccewuify @@ -131,7 +153,7 @@ services: - merchant_lightningd merchant_lightningd: - image: nicolasdorier/clightning:0.0.0.20-dev + image: nicolasdorier/clightning:v0.6-dev environment: EXPOSE_TCP: "true" LIGHTNINGD_OPT: | @@ -177,8 +199,59 @@ services: expose: - "5432" + merchant_lnd: + image: btcpayserver/lnd:0.4.2.0 + environment: + LND_CHAIN: "btc" + LND_ENVIRONMENT: "regtest" + LND_EXTRA_ARGS: | + restlisten=0.0.0.0:8080 + bitcoin.node=bitcoind + bitcoind.rpchost=bitcoind:43782 + bitcoind.zmqpath=tcp://bitcoind:28332 + externalip=merchant_lnd:9735 + no-macaroons=1 + debuglevel=debug + noencryptwallet=1 + ports: + - "53280:8080" + expose: + - "9735" + volumes: + - "merchant_lnd_datadir:/data" + - "bitcoin_datadir:/deps/.bitcoin" + links: + - bitcoind + + customer_lnd: + image: btcpayserver/lnd:0.4.2.0 + environment: + LND_CHAIN: "btc" + LND_ENVIRONMENT: "regtest" + LND_EXTRA_ARGS: | + restlisten=0.0.0.0:8080 + bitcoin.node=bitcoind + bitcoind.rpchost=bitcoind:43782 + bitcoind.zmqpath=tcp://bitcoind:28332 + externalip=customer_lnd:10009 + no-macaroons=1 + debuglevel=debug + noencryptwallet=1 + ports: + - "53281:8080" + expose: + - "8080" + - "10009" + volumes: + - "customer_lnd_datadir:/root/.lnd" + - "bitcoin_datadir:/deps/.bitcoin" + links: + - bitcoind + volumes: bitcoin_datadir: customer_lightningd_datadir: merchant_lightningd_datadir: lightning_charge_datadir: + customer_lnd_datadir: + merchant_lnd_datadir: diff --git a/BTCPayServer/BTCPayNetwork.cs b/BTCPayServer/BTCPayNetwork.cs index bc0be4841..685215e6b 100644 --- a/BTCPayServer/BTCPayNetwork.cs +++ b/BTCPayServer/BTCPayNetwork.cs @@ -44,6 +44,8 @@ namespace BTCPayServer public string CryptoCode { get; internal set; } public string BlockExplorerLink { get; internal set; } public string UriScheme { get; internal set; } + public Money MinFee { get; internal set; } + public string DisplayName { get; set; } [Obsolete("Should not be needed")] public bool IsBTC diff --git a/BTCPayServer/BTCPayNetworkProvider.Bitcoin.cs b/BTCPayServer/BTCPayNetworkProvider.Bitcoin.cs index 911edff2c..b6752d0e2 100644 --- a/BTCPayServer/BTCPayNetworkProvider.Bitcoin.cs +++ b/BTCPayServer/BTCPayNetworkProvider.Bitcoin.cs @@ -17,12 +17,13 @@ namespace BTCPayServer Add(new BTCPayNetwork() { CryptoCode = nbxplorerNetwork.CryptoCode, + DisplayName = "Bitcoin", BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://www.smartbit.com.au/tx/{0}" : "https://testnet.smartbit.com.au/tx/{0}", NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork, NBXplorerNetwork = nbxplorerNetwork, UriScheme = "bitcoin", - CryptoImagePath = "imlegacy/bitcoin-symbol.svg", - LightningImagePath = "imlegacy/btc-lightning.svg", + CryptoImagePath = "imlegacy/bitcoin.svg", + LightningImagePath = "imlegacy/bitcoin-lightning.svg", DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType), CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("0'") : new KeyPath("1'") }); diff --git a/BTCPayServer/BTCPayNetworkProvider.BitcoinGold.cs b/BTCPayServer/BTCPayNetworkProvider.BitcoinGold.cs index c75c3dd18..891483763 100644 --- a/BTCPayServer/BTCPayNetworkProvider.BitcoinGold.cs +++ b/BTCPayServer/BTCPayNetworkProvider.BitcoinGold.cs @@ -10,6 +10,7 @@ namespace BTCPayServer Add(new BTCPayNetwork() { CryptoCode = nbxplorerNetwork.CryptoCode, + DisplayName = "BGold", BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://explorer.bitcoingold.org/insight/tx/{0}/" : "https://test-explorer.bitcoingold.org/insight/tx/{0}", NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork, NBXplorerNetwork = nbxplorerNetwork, @@ -19,8 +20,8 @@ namespace BTCPayServer "BTG_X = BTG_BTC * BTC_X", "BTG_BTC = bitfinex(BTG_BTC)", }, - CryptoImagePath = "imlegacy/btg-symbol.svg", - LightningImagePath = "imlegacy/btg-symbol.svg", + CryptoImagePath = "imlegacy/btg.svg", + LightningImagePath = "imlegacy/btg-lightning.svg", DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType), CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("156'") : new KeyPath("1'") }); diff --git a/BTCPayServer/BTCPayNetworkProvider.Dogecoin.cs b/BTCPayServer/BTCPayNetworkProvider.Dogecoin.cs index e12ceaff7..d583c20e2 100644 --- a/BTCPayServer/BTCPayNetworkProvider.Dogecoin.cs +++ b/BTCPayServer/BTCPayNetworkProvider.Dogecoin.cs @@ -16,6 +16,7 @@ namespace BTCPayServer Add(new BTCPayNetwork() { CryptoCode = nbxplorerNetwork.CryptoCode, + DisplayName = "Dogecoin", BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://dogechain.info/tx/{0}" : "https://dogechain.info/tx/{0}", NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork, NBXplorerNetwork = nbxplorerNetwork, @@ -27,7 +28,8 @@ namespace BTCPayServer }, CryptoImagePath = "imlegacy/dogecoin.png", DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType), - CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("3'") : new KeyPath("1'") + CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("3'") : new KeyPath("1'"), + MinFee = Money.Coins(1m) }); } } diff --git a/BTCPayServer/BTCPayNetworkProvider.Feathercoin.cs b/BTCPayServer/BTCPayNetworkProvider.Feathercoin.cs index 2f48274b1..8f9642e2f 100644 --- a/BTCPayServer/BTCPayNetworkProvider.Feathercoin.cs +++ b/BTCPayServer/BTCPayNetworkProvider.Feathercoin.cs @@ -16,6 +16,7 @@ namespace BTCPayServer Add(new BTCPayNetwork() { CryptoCode = nbxplorerNetwork.CryptoCode, + DisplayName = "Feathercoin", BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://explorer.feathercoin.com/tx/{0}" : "https://explorer.feathercoin.com/tx/{0}", NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork, NBXplorerNetwork = nbxplorerNetwork, diff --git a/BTCPayServer/BTCPayNetworkProvider.Groestlcoin.cs b/BTCPayServer/BTCPayNetworkProvider.Groestlcoin.cs new file mode 100644 index 000000000..50ad0cf33 --- /dev/null +++ b/BTCPayServer/BTCPayNetworkProvider.Groestlcoin.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using NBitcoin; + +namespace BTCPayServer +{ + public partial class BTCPayNetworkProvider + { + public void InitGroestlcoin() + { + + var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("GRS"); + Add(new BTCPayNetwork() + { + CryptoCode = nbxplorerNetwork.CryptoCode, + DisplayName = "Groestlcoin", + BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://chainz.cryptoid.info/grs/tx.dws?{0}.htm" : "https://chainz.cryptoid.info/grs-test/tx.dws?{0}.htm", + NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork, + NBXplorerNetwork = nbxplorerNetwork, + UriScheme = "groestlcoin", + DefaultRateRules = new[] + { + "GRS_X = GRS_BTC * BTC_X", + "GRS_BTC = bittrex(GRS_BTC)" + }, + CryptoImagePath = "imlegacy/groestlcoin.png", + DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType), + CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("17'") : new KeyPath("1'") + }); + } + } +} diff --git a/BTCPayServer/BTCPayNetworkProvider.Litecoin.cs b/BTCPayServer/BTCPayNetworkProvider.Litecoin.cs index 42b3de248..c09692f49 100644 --- a/BTCPayServer/BTCPayNetworkProvider.Litecoin.cs +++ b/BTCPayServer/BTCPayNetworkProvider.Litecoin.cs @@ -16,12 +16,13 @@ namespace BTCPayServer Add(new BTCPayNetwork() { CryptoCode = nbxplorerNetwork.CryptoCode, + DisplayName = "Litecoin", BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://live.blockcypher.com/ltc/tx/{0}/" : "http://explorer.litecointools.com/tx/{0}", NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork, NBXplorerNetwork = nbxplorerNetwork, UriScheme = "litecoin", - CryptoImagePath = "imlegacy/litecoin-symbol.svg", - LightningImagePath = "imlegacy/ltc-lightning.svg", + CryptoImagePath = "imlegacy/litecoin.svg", + LightningImagePath = "imlegacy/litecoin-lightning.svg", DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType), CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("2'") : new KeyPath("1'") }); diff --git a/BTCPayServer/BTCPayNetworkProvider.Monacoin.cs b/BTCPayServer/BTCPayNetworkProvider.Monacoin.cs index bb086132b..07798cd6e 100644 --- a/BTCPayServer/BTCPayNetworkProvider.Monacoin.cs +++ b/BTCPayServer/BTCPayNetworkProvider.Monacoin.cs @@ -16,6 +16,7 @@ namespace BTCPayServer Add(new BTCPayNetwork() { CryptoCode = nbxplorerNetwork.CryptoCode, + DisplayName = "Monacoin", BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://mona.insight.monaco-ex.org/insight/tx/{0}" : "https://testnet-mona.insight.monaco-ex.org/insight/tx/{0}", NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork, NBXplorerNetwork = nbxplorerNetwork, diff --git a/BTCPayServer/BTCPayNetworkProvider.Polis.cs b/BTCPayServer/BTCPayNetworkProvider.Polis.cs index 69640dab1..5379477c6 100644 --- a/BTCPayServer/BTCPayNetworkProvider.Polis.cs +++ b/BTCPayServer/BTCPayNetworkProvider.Polis.cs @@ -16,6 +16,7 @@ namespace BTCPayServer Add(new BTCPayNetwork() { CryptoCode = nbxplorerNetwork.CryptoCode, + DisplayName = "Polis", BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://insight.polispay.org/tx/{0}" : "https://insight.polispay.org/tx/{0}", NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork, NBXplorerNetwork = nbxplorerNetwork, diff --git a/BTCPayServer/BTCPayNetworkProvider.Ufo.cs b/BTCPayServer/BTCPayNetworkProvider.Ufo.cs index 45a3e6d6f..801bd7194 100644 --- a/BTCPayServer/BTCPayNetworkProvider.Ufo.cs +++ b/BTCPayServer/BTCPayNetworkProvider.Ufo.cs @@ -16,6 +16,7 @@ namespace BTCPayServer Add(new BTCPayNetwork() { CryptoCode = nbxplorerNetwork.CryptoCode, + DisplayName = "Ufo", BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://chainz.cryptoid.info/ufo/tx.dws?{0}" : "https://chainz.cryptoid.info/ufo/tx.dws?{0}", NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork, NBXplorerNetwork = nbxplorerNetwork, diff --git a/BTCPayServer/BTCPayNetworkProvider.Viacoin.cs b/BTCPayServer/BTCPayNetworkProvider.Viacoin.cs new file mode 100644 index 000000000..58103a7cc --- /dev/null +++ b/BTCPayServer/BTCPayNetworkProvider.Viacoin.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BTCPayServer.Services.Rates; +using NBitcoin; +using NBXplorer; + +namespace BTCPayServer +{ + public partial class BTCPayNetworkProvider + { + public void InitViacoin() + { + var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("VIA"); + Add(new BTCPayNetwork() + { + CryptoCode = nbxplorerNetwork.CryptoCode, + DisplayName = "Viacoin", + BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://explorer.viacoin.org/tx/{0}" : "https://explorer.viacoin.org/tx/{0}", + NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork, + NBXplorerNetwork = nbxplorerNetwork, + UriScheme = "viacoin", + DefaultRateRules = new[] + { + "VIA_X = VIA_BTC * BTC_X", + "VIA_BTC = bittrex(VIA_BTC)" + }, + CryptoImagePath = "imlegacy/viacoin.png", + DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType), + CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("14'") : new KeyPath("1'") + }); + } + } +} diff --git a/BTCPayServer/BTCPayNetworkProvider.cs b/BTCPayServer/BTCPayNetworkProvider.cs index 5a1c997ce..8c7e8b1e2 100644 --- a/BTCPayServer/BTCPayNetworkProvider.cs +++ b/BTCPayServer/BTCPayNetworkProvider.cs @@ -52,7 +52,9 @@ namespace BTCPayServer InitMonacoin(); InitPolis(); InitFeathercoin(); - InitUfo(); + InitGroestlcoin(); + InitViacoin(); + //InitUfo(); } /// diff --git a/BTCPayServer/BTCPayServer.csproj b/BTCPayServer/BTCPayServer.csproj index def5d1e8f..5eca11a60 100644 --- a/BTCPayServer/BTCPayServer.csproj +++ b/BTCPayServer/BTCPayServer.csproj @@ -2,7 +2,7 @@ Exe netcoreapp2.1 - 1.0.2.34 + 1.0.2.78 NU1701,CA1816,CA1308,CA1810,CA2208 @@ -35,19 +35,20 @@ - + - - + + - + - + + @@ -111,6 +112,34 @@ + + + + + + $(IncludeRazorContentInPack) + + + $(IncludeRazorContentInPack) + + + $(IncludeRazorContentInPack) + + + $(IncludeRazorContentInPack) + + + $(IncludeRazorContentInPack) + + + $(IncludeRazorContentInPack) + + + $(IncludeRazorContentInPack) + + + $(IncludeRazorContentInPack) + diff --git a/BTCPayServer/Configuration/BTCPayServerOptions.cs b/BTCPayServer/Configuration/BTCPayServerOptions.cs index 2ed2e665d..31964bcc7 100644 --- a/BTCPayServer/Configuration/BTCPayServerOptions.cs +++ b/BTCPayServer/Configuration/BTCPayServerOptions.cs @@ -74,16 +74,40 @@ namespace BTCPayServer.Configuration setting.ExplorerUri = conf.GetOrDefault($"{net.CryptoCode}.explorer.url", net.NBXplorerNetwork.DefaultSettings.DefaultUrl); setting.CookieFile = conf.GetOrDefault($"{net.CryptoCode}.explorer.cookiefile", net.NBXplorerNetwork.DefaultSettings.DefaultCookieFile); NBXplorerConnectionSettings.Add(setting); - var lightning = conf.GetOrDefault($"{net.CryptoCode}.lightning", string.Empty); - if(lightning.Length != 0) { - if(!LightningConnectionString.TryParse(lightning, out var connectionString, out var error)) + var lightning = conf.GetOrDefault($"{net.CryptoCode}.lightning", string.Empty); + if (lightning.Length != 0) { - throw new ConfigException($"Invalid setting {net.CryptoCode}.lightning, you need to pass either " + - $"the absolute path to the unix socket of a running CLightning instance (eg. /root/.lightning/lightning-rpc), " + - $"or the url to a charge server with crendetials (eg. https://apitoken@API_TOKEN_SECRET:charge.example.com/)"); + if (!LightningConnectionString.TryParse(lightning, true, out var connectionString, out var error)) + { + throw new ConfigException($"Invalid setting {net.CryptoCode}.lightning, " + Environment.NewLine + + $"If you have a lightning server use: 'type=clightning;server=/root/.lightning/lightning-rpc', " + Environment.NewLine + + $"If you have a lightning charge server: 'type=charge;server=https://charge.example.com;api-token=yourapitoken'" + Environment.NewLine + + $"If you have a lnd server: 'type=lnd-rest;server=https://lnd:lnd@lnd.example.com;macaroon=abf239...;certthumbprint=2abdf302...'" + Environment.NewLine + + $" lnd server: 'type=lnd-rest;server=https://lnd:lnd@lnd.example.com;macaroonfilepath=/root/.lnd/admin.macaroon;certthumbprint=2abdf302...'" + Environment.NewLine + + error); + } + if (connectionString.IsLegacy) + { + Logs.Configuration.LogWarning($"Setting {net.CryptoCode}.lightning will work but use an deprecated format, please replace it by '{connectionString.ToString()}'"); + } + InternalLightningByCryptoCode.Add(net.CryptoCode, connectionString); + } + } + + { + var lightning = conf.GetOrDefault($"{net.CryptoCode}.external.lnd.grpc", string.Empty); + if (lightning.Length != 0) + { + if (!LightningConnectionString.TryParse(lightning, false, out var connectionString, out var error)) + { + throw new ConfigException($"Invalid setting {net.CryptoCode}.external.lnd.grpc, " + Environment.NewLine + + $"lnd server: 'type=lnd-grpc;server=https://lnd.example.com;macaroon=abf239...;certthumbprint=2abdf302...'" + Environment.NewLine + + $"lnd server: 'type=lnd-grpc;server=https://lnd.example.com;macaroonfilepath=/root/.lnd/admin.macaroon;certthumbprint=2abdf302...'" + Environment.NewLine + + error); + } + ExternalServicesByCryptoCode.Add(net.CryptoCode, new ExternalLNDGRPC(connectionString)); } - InternalLightningByCryptoCode.Add(net.CryptoCode, connectionString); } } @@ -97,11 +121,12 @@ namespace BTCPayServer.Configuration if (!RootPath.StartsWith("/", StringComparison.InvariantCultureIgnoreCase)) RootPath = "/" + RootPath; var old = conf.GetOrDefault("internallightningnode", null); - if(old != null) + if (old != null) throw new ConfigException($"internallightningnode should not be used anymore, use btclightning instead"); } public string RootPath { get; set; } public Dictionary InternalLightningByCryptoCode { get; set; } = new Dictionary(); + public ExternalServices ExternalServicesByCryptoCode { get; set; } = new ExternalServices(); public BTCPayNetworkProvider NetworkProvider { get; set; } public string PostgresConnectionString @@ -129,4 +154,29 @@ namespace BTCPayServer.Configuration return builder.ToString(); } } + + public class ExternalServices : MultiValueDictionary + { + public IEnumerable GetServices(string cryptoCode) where T : ExternalService + { + if (!this.TryGetValue(cryptoCode.ToUpperInvariant(), out var services)) + return Array.Empty(); + return services.OfType(); + } + } + + public class ExternalService + { + + } + + public class ExternalLNDGRPC : ExternalService + { + public ExternalLNDGRPC(LightningConnectionString connectionString) + { + ConnectionString = connectionString; + } + + public LightningConnectionString ConnectionString { get; set; } + } } diff --git a/BTCPayServer/Configuration/DefaultConfiguration.cs b/BTCPayServer/Configuration/DefaultConfiguration.cs index d4bb141a3..d216f86f5 100644 --- a/BTCPayServer/Configuration/DefaultConfiguration.cs +++ b/BTCPayServer/Configuration/DefaultConfiguration.cs @@ -27,19 +27,20 @@ namespace BTCPayServer.Configuration }; app.HelpOption("-? | -h | --help"); app.Option("-n | --network", $"Set the network among (mainnet,testnet,regtest) (default: mainnet)", CommandOptionType.SingleValue); - app.Option("--testnet | -testnet", $"Use testnet (Deprecated, use --network instead)", CommandOptionType.BoolValue); - app.Option("--regtest | -regtest", $"Use regtest (Deprecated, use --network instead)", CommandOptionType.BoolValue); - app.Option("--chains | -c", $"Chains to support comma separated (default: btc, available: {chains})", CommandOptionType.SingleValue); - app.Option("--postgres", $"Connection string to postgres database (default: sqlite is used)", CommandOptionType.SingleValue); - app.Option("--externalurl", $"The expected external url of this service, to use if BTCPay is behind a reverse proxy (default: empty, use the incoming HTTP request to figure out)", CommandOptionType.SingleValue); - app.Option("--bundlejscss", $"Bundle javascript and css files for better performance (default: true)", CommandOptionType.SingleValue); + app.Option("--testnet | -testnet", $"Use testnet (deprecated, use --network instead)", CommandOptionType.BoolValue); + app.Option("--regtest | -regtest", $"Use regtest (deprecated, use --network instead)", CommandOptionType.BoolValue); + app.Option("--chains | -c", $"Chains to support as a comma separated (default: btc; available: {chains})", CommandOptionType.SingleValue); + app.Option("--postgres", $"Connection string to a PostgreSQL database (default: SQLite)", CommandOptionType.SingleValue); + app.Option("--externalurl", $"The expected external URL of this service, to use if BTCPay is behind a reverse proxy (default: empty, use the incoming HTTP request to figure out)", CommandOptionType.SingleValue); + app.Option("--bundlejscss", $"Bundle JavaScript and CSS files for better performance (default: true)", CommandOptionType.SingleValue); app.Option("--rootpath", "The root path in the URL to access BTCPay (default: /)", CommandOptionType.SingleValue); foreach (var network in provider.GetAll()) { var crypto = network.CryptoCode.ToLowerInvariant(); - app.Option($"--{crypto}explorerurl", $"Url of the NBxplorer for {network.CryptoCode} (default: {network.NBXplorerNetwork.DefaultSettings.DefaultUrl})", CommandOptionType.SingleValue); + app.Option($"--{crypto}explorerurl", $"URL of the NBXplorer for {network.CryptoCode} (default: {network.NBXplorerNetwork.DefaultSettings.DefaultUrl})", CommandOptionType.SingleValue); app.Option($"--{crypto}explorercookiefile", $"Path to the cookie file (default: {network.NBXplorerNetwork.DefaultSettings.DefaultCookieFile})", CommandOptionType.SingleValue); - app.Option($"--{crypto}lightning", $"Easy configuration of lightning for the server adnistrator: Must be a unix socket of CLightning (lightning-rpc) or URL to a charge server (default: empty)", CommandOptionType.SingleValue); + app.Option($"--{crypto}lightning", $"Easy configuration of lightning for the server administrator: Must be a UNIX socket of c-lightning (lightning-rpc) or URL to a charge server (default: empty)", CommandOptionType.SingleValue); + app.Option($"--{crypto}externallndgrpc", $"The LND gRPC configuration BTCPay will expose to easily connect to the internal lnd wallet from Zap wallet (default: empty)", CommandOptionType.SingleValue); } return app; } diff --git a/BTCPayServer/Controllers/AppsController.PointOfSale.cs b/BTCPayServer/Controllers/AppsController.PointOfSale.cs index 22f227a0b..938e79bce 100644 --- a/BTCPayServer/Controllers/AppsController.PointOfSale.cs +++ b/BTCPayServer/Controllers/AppsController.PointOfSale.cs @@ -134,7 +134,7 @@ namespace BTCPayServer.Controllers }); await UpdateAppSettings(app); StatusMessage = "App updated"; - return RedirectToAction(nameof(UpdatePointOfSale)); + return RedirectToAction(nameof(ListApps)); } [HttpGet] @@ -162,7 +162,8 @@ namespace BTCPayServer.Controllers using (var ctx = _ContextFactory.CreateContext()) { return await ctx.Apps - .Where(us => us.Id == appId && us.AppType == appType.ToString()) + .Where(us => us.Id == appId && + us.AppType == appType.ToString()) .FirstOrDefaultAsync(); } } diff --git a/BTCPayServer/Controllers/AppsController.cs b/BTCPayServer/Controllers/AppsController.cs index 143f6d92a..e9e54bd8b 100644 --- a/BTCPayServer/Controllers/AppsController.cs +++ b/BTCPayServer/Controllers/AppsController.cs @@ -102,9 +102,9 @@ namespace BTCPayServer.Controllers StatusMessage = "Error: You are not owner of this store"; return RedirectToAction(nameof(ListApps)); } + var id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(32)); using (var ctx = _ContextFactory.CreateContext()) { - var id = Encoders.Base58.EncodeData(RandomUtils.GetBytes(32)); var appData = new AppData() { Id = id }; appData.StoreDataId = selectedStore; appData.Name = vm.Name; @@ -113,6 +113,9 @@ namespace BTCPayServer.Controllers await ctx.SaveChangesAsync(); } StatusMessage = "App successfully created"; + + if (appType == AppType.PointOfSale) + return RedirectToAction(nameof(UpdatePointOfSale), new { appId = id }); return RedirectToAction(nameof(ListApps)); } diff --git a/BTCPayServer/Controllers/HomeController.cs b/BTCPayServer/Controllers/HomeController.cs index 49e7036f1..80c1328c7 100644 --- a/BTCPayServer/Controllers/HomeController.cs +++ b/BTCPayServer/Controllers/HomeController.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using BTCPayServer.Models; +using NBitcoin.DataEncoders; namespace BTCPayServer.Controllers { diff --git a/BTCPayServer/Controllers/InvoiceController.PaymentProtocol.cs b/BTCPayServer/Controllers/InvoiceController.PaymentProtocol.cs index 3966f40d6..198ef9cb3 100644 --- a/BTCPayServer/Controllers/InvoiceController.PaymentProtocol.cs +++ b/BTCPayServer/Controllers/InvoiceController.PaymentProtocol.cs @@ -78,7 +78,7 @@ namespace BTCPayServer.Controllers var wallet = _WalletProvider.GetWallet(network); if (wallet == null) return NotFound(); - var payment = PaymentMessage.Load(Request.Body); + var payment = PaymentMessage.Load(Request.Body, network.NBitcoinNetwork); var unused = wallet.BroadcastTransactionsAsync(payment.Transactions); await _InvoiceRepository.AddRefundsAsync(invoiceId, payment.RefundTo.Select(p => new TxOut(p.Amount, p.Script)).ToArray(), network.NBitcoinNetwork); return new PaymentAckActionResult(payment.CreateACK(invoiceId + " is currently processing, thanks for your purchase...")); diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index 0b04d6a36..76ba4f472 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -175,6 +175,7 @@ namespace BTCPayServer.Controllers [Route("invoice")] [AcceptMediaTypeConstraint("application/bitcoin-paymentrequest", false)] [XFrameOptionsAttribute(null)] + [ReferrerPolicyAttribute("origin")] public async Task Checkout(string invoiceId, string id = null, string paymentMethodId = null) { //Keep compatibility with Bitpay @@ -186,6 +187,20 @@ namespace BTCPayServer.Controllers if (model == null) return NotFound(); + + _CSP.Add(new ConsentSecurityPolicy("script-src", "'unsafe-eval'")); // Needed by Vue + if (!string.IsNullOrEmpty(model.CustomCSSLink) && + Uri.TryCreate(model.CustomCSSLink, UriKind.Absolute, out var uri)) + { + _CSP.Clear(); + } + + if (!string.IsNullOrEmpty(model.CustomLogoLink) && + Uri.TryCreate(model.CustomLogoLink, UriKind.Absolute, out uri)) + { + _CSP.Clear(); + } + return View(nameof(Checkout), model); } @@ -233,6 +248,8 @@ namespace BTCPayServer.Controllers { CryptoCode = network.CryptoCode, PaymentMethodId = paymentMethodId.ToString(), + PaymentMethodName = GetDisplayName(paymentMethodId, network), + CryptoImage = GetImage(paymentMethodId, network), IsLightning = paymentMethodId.PaymentType == PaymentTypes.LightningLike, ServerUrl = HttpContext.Request.GetAbsoluteRoot(), OrderId = invoice.OrderId, @@ -264,7 +281,6 @@ namespace BTCPayServer.Controllers TxCount = accounting.TxRequired, BtcPaid = accounting.Paid.ToString(), Status = invoice.Status, - CryptoImage = "/" + GetImage(paymentMethodId, network), NetworkFee = paymentMethodDetails.GetTxFee(), IsMultiCurrency = invoice.GetPayments().Select(p => p.GetPaymentMethodId()).Concat(new[] { paymentMethod.GetId() }).Distinct().Count() > 1, AllowCoinConversion = storeBlob.AllowCoinConversion, @@ -273,10 +289,14 @@ namespace BTCPayServer.Controllers .Select(kv => new PaymentModel.AvailableCrypto() { PaymentMethodId = kv.GetId().ToString(), - CryptoImage = "/" + GetImage(kv.GetId(), kv.Network), + CryptoCode = kv.GetId().CryptoCode, + PaymentMethodName = GetDisplayName(kv.GetId(), kv.Network), + IsLightning = kv.GetId().PaymentType == PaymentTypes.LightningLike, + CryptoImage = GetImage(kv.GetId(), kv.Network), Link = Url.Action(nameof(Checkout), new { invoiceId = invoiceId, paymentMethodId = kv.GetId().ToString() }) }).Where(c => c.CryptoImage != "/") - .ToList() + .OrderByDescending(a => a.CryptoCode == "BTC").ThenBy(a => a.PaymentMethodName).ThenBy(a => a.IsLightning ? 1 : 0) + .ToList() }; var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds); @@ -284,9 +304,17 @@ namespace BTCPayServer.Controllers return model; } + private string GetDisplayName(PaymentMethodId paymentMethodId, BTCPayNetwork network) + { + return paymentMethodId.PaymentType == PaymentTypes.BTCLike ? + network.DisplayName : network.DisplayName + " (via Lightning)"; + } + private string GetImage(PaymentMethodId paymentMethodId, BTCPayNetwork network) { - return (paymentMethodId.PaymentType == PaymentTypes.BTCLike ? Url.Content(network.CryptoImagePath) : Url.Content(network.LightningImagePath)); + var res = paymentMethodId.PaymentType == PaymentTypes.BTCLike ? + Url.Content(network.CryptoImagePath) : Url.Content(network.LightningImagePath); + return "/" + res; } private string OrderAmountFromInvoice(string cryptoCode, ProductInformation productInformation) @@ -323,7 +351,7 @@ namespace BTCPayServer.Controllers provider = (NumberFormatInfo)provider.Clone(); provider.CurrencyDecimalDigits = divisibility; } - + if (currencyData.Crypto) return price.ToString("C", provider); else @@ -356,7 +384,7 @@ namespace BTCPayServer.Controllers { leases.Add(_EventAggregator.Subscribe(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId))); leases.Add(_EventAggregator.Subscribe(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId))); - leases.Add(_EventAggregator.Subscribe(async o => await NotifySocket(webSocket, o.InvoiceId, invoiceId))); + leases.Add(_EventAggregator.Subscribe(async o => await NotifySocket(webSocket, o.Invoice.Id, invoiceId))); while (true) { var message = await webSocket.ReceiveAsync(DummyBuffer, default(CancellationToken)); @@ -535,8 +563,11 @@ namespace BTCPayServer.Controllers [BitpayAPIConstraint(false)] public async Task InvalidatePaidInvoice(string invoiceId) { + var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId); + if (invoice == null) + return NotFound(); await _InvoiceRepository.UpdatePaidInvoiceToInvalid(invoiceId); - _EventAggregator.Publish(new InvoiceEvent(invoiceId, 1008, "invoice_markedInvalid")); + _EventAggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1008, "invoice_markedInvalid")); return RedirectToAction(nameof(ListInvoices)); } diff --git a/BTCPayServer/Controllers/InvoiceController.cs b/BTCPayServer/Controllers/InvoiceController.cs index d449aa173..c6310bc6e 100644 --- a/BTCPayServer/Controllers/InvoiceController.cs +++ b/BTCPayServer/Controllers/InvoiceController.cs @@ -41,12 +41,14 @@ using NBXplorer; using BTCPayServer.HostedServices; using BTCPayServer.Payments; using BTCPayServer.Rating; +using BTCPayServer.Security; namespace BTCPayServer.Controllers { public partial class InvoiceController : Controller { InvoiceRepository _InvoiceRepository; + ContentSecurityPolicies _CSP; BTCPayRateProviderFactory _RateProvider; StoreRepository _StoreRepository; UserManager _UserManager; @@ -64,6 +66,7 @@ namespace BTCPayServer.Controllers StoreRepository storeRepository, EventAggregator eventAggregator, BTCPayWalletProvider walletProvider, + ContentSecurityPolicies csp, BTCPayNetworkProvider networkProvider) { _ServiceProvider = serviceProvider; @@ -75,11 +78,14 @@ namespace BTCPayServer.Controllers _EventAggregator = eventAggregator; _NetworkProvider = networkProvider; _WalletProvider = walletProvider; + _CSP = csp; } internal async Task> CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl) { + InvoiceLogs logs = new InvoiceLogs(); + logs.Write("Creation of invoice starting"); var entity = new InvoiceEntity { InvoiceTime = DateTimeOffset.UtcNow @@ -115,7 +121,6 @@ namespace BTCPayServer.Controllers entity.Status = "new"; entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy); - HashSet currencyPairsToFetch = new HashSet(); var rules = storeBlob.GetRateRules(_NetworkProvider); @@ -133,6 +138,7 @@ namespace BTCPayServer.Controllers var rateRules = storeBlob.GetRateRules(_NetworkProvider); var fetchingByCurrencyPair = _RateProvider.FetchRates(currencyPairsToFetch, rateRules); + var fetchingAll = WhenAllFetched(logs, fetchingByCurrencyPair); var supportedPaymentMethods = store.GetSupportedPaymentMethods(_NetworkProvider) .Select(c => (Handler: (IPaymentMethodHandler)_ServiceProvider.GetService(typeof(IPaymentMethodHandler<>).MakeGenericType(c.GetType())), @@ -141,65 +147,26 @@ namespace BTCPayServer.Controllers .Where(c => c.Network != null) .Select(o => (SupportedPaymentMethod: o.SupportedPaymentMethod, - PaymentMethod: CreatePaymentMethodAsync(fetchingByCurrencyPair, o.Handler, o.SupportedPaymentMethod, o.Network, entity, store))) + PaymentMethod: CreatePaymentMethodAsync(fetchingByCurrencyPair, o.Handler, o.SupportedPaymentMethod, o.Network, entity, store, logs))) .ToList(); - - List paymentMethodErrors = new List(); List supported = new List(); var paymentMethods = new PaymentMethodDictionary(); - - foreach(var pair in fetchingByCurrencyPair) - { - var rateResult = await pair.Value; - bool hasError = false; - if(rateResult.Errors.Count != 0) - { - var allRateRuleErrors = string.Join(", ", rateResult.Errors.ToArray()); - paymentMethodErrors.Add($"{pair.Key}: Rate rule error ({allRateRuleErrors})"); - hasError = true; - } - if(rateResult.ExchangeExceptions.Count != 0) - { - foreach(var ex in rateResult.ExchangeExceptions) - { - paymentMethodErrors.Add($"{pair.Key}: Exception reaching exchange {ex.ExchangeName} ({ex.Exception.Message})"); - } - hasError = true; - } - if(hasError) - { - paymentMethodErrors.Add($"{pair.Key}: The rule is {rateResult.Rule}"); - paymentMethodErrors.Add($"{pair.Key}: Evaluated rule is {rateResult.EvaluatedRule}"); - } - } - foreach (var o in supportedPaymentMethods) { - try - { - var paymentMethod = await o.PaymentMethod; - if (paymentMethod == null) - throw new PaymentMethodUnavailableException("Payment method unavailable"); - supported.Add(o.SupportedPaymentMethod); - paymentMethods.Add(paymentMethod); - } - catch (PaymentMethodUnavailableException ex) - { - paymentMethodErrors.Add($"{o.SupportedPaymentMethod.PaymentId.CryptoCode} ({o.SupportedPaymentMethod.PaymentId.PaymentType}): Payment method unavailable ({ex.Message})"); - } - catch (Exception ex) - { - paymentMethodErrors.Add($"{o.SupportedPaymentMethod.PaymentId.CryptoCode} ({o.SupportedPaymentMethod.PaymentId.PaymentType}): Unexpected exception ({ex.ToString()})"); - } + var paymentMethod = await o.PaymentMethod; + if (paymentMethod == null) + continue; + supported.Add(o.SupportedPaymentMethod); + paymentMethods.Add(paymentMethod); } if (supported.Count == 0) { StringBuilder errors = new StringBuilder(); errors.AppendLine("No payment method available for this store"); - foreach (var error in paymentMethodErrors) + foreach (var error in logs.ToList()) { - errors.AppendLine(error); + errors.AppendLine(error.ToString()); } throw new BitpayHttpException(400, errors.ToString()); } @@ -207,71 +174,108 @@ namespace BTCPayServer.Controllers entity.SetSupportedPaymentMethods(supported); entity.SetPaymentMethods(paymentMethods); entity.PosData = invoice.PosData; - entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, paymentMethodErrors, _NetworkProvider); - - _EventAggregator.Publish(new Events.InvoiceEvent(entity, 1001, "invoice_created")); + entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, logs, _NetworkProvider); + await fetchingAll; + _EventAggregator.Publish(new Events.InvoiceEvent(entity.EntityToDTO(_NetworkProvider), 1001, "invoice_created")); var resp = entity.EntityToDTO(_NetworkProvider); return new DataWrapper(resp) { Facade = "pos/invoice" }; } - private async Task CreatePaymentMethodAsync(Dictionary> fetchingByCurrencyPair, IPaymentMethodHandler handler, ISupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network, InvoiceEntity entity, StoreData store) + private Task WhenAllFetched(InvoiceLogs logs, Dictionary> fetchingByCurrencyPair) { - var storeBlob = store.GetStoreBlob(); - var rate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, entity.ProductInformation.Currency)]; - if (rate.Value == null) - return null; - PaymentMethod paymentMethod = new PaymentMethod(); - paymentMethod.ParentEntity = entity; - paymentMethod.Network = network; - paymentMethod.SetId(supportedPaymentMethod.PaymentId); - paymentMethod.Rate = rate.Value.Value; - var paymentDetails = await handler.CreatePaymentMethodDetails(supportedPaymentMethod, paymentMethod, store, network); - if (storeBlob.NetworkFeeDisabled) - paymentDetails.SetNoTxFee(); - paymentMethod.SetPaymentMethodDetails(paymentDetails); - - Func compare = null; - CurrencyValue limitValue = null; - string errorMessage = null; - if (supportedPaymentMethod.PaymentId.PaymentType == PaymentTypes.LightningLike && - storeBlob.LightningMaxValue != null) + return Task.WhenAll(fetchingByCurrencyPair.Select(async pair => { - compare = (a, b) => a > b; - limitValue = storeBlob.LightningMaxValue; - errorMessage = "The amount of the invoice is too high to be paid with lightning"; - } - else if (supportedPaymentMethod.PaymentId.PaymentType == PaymentTypes.BTCLike && - storeBlob.OnChainMinValue != null) - { - compare = (a, b) => a < b; - limitValue = storeBlob.OnChainMinValue; - errorMessage = "The amount of the invoice is too low to be paid on chain"; - } - - if (compare != null) - { - var limitValueRate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, limitValue.Currency)]; - if (limitValueRate.Value.HasValue) + var rateResult = await pair.Value; + logs.Write($"{pair.Key}: The rating rule is {rateResult.Rule}"); + logs.Write($"{pair.Key}: The evaluated rating rule is {rateResult.EvaluatedRule}"); + if (rateResult.Errors.Count != 0) { - var limitValueCrypto = Money.Coins(limitValue.Value / limitValueRate.Value.Value); - if (compare(paymentMethod.Calculate().Due, limitValueCrypto)) + var allRateRuleErrors = string.Join(", ", rateResult.Errors.ToArray()); + logs.Write($"{pair.Key}: Rate rule error ({allRateRuleErrors})"); + } + if (rateResult.ExchangeExceptions.Count != 0) + { + foreach (var ex in rateResult.ExchangeExceptions) { - throw new PaymentMethodUnavailableException(errorMessage); + logs.Write($"{pair.Key}: Exception reaching exchange {ex.ExchangeName} ({ex.Exception.Message})"); } } - } - /////////////// + }).ToArray()); + } + + private async Task CreatePaymentMethodAsync(Dictionary> fetchingByCurrencyPair, IPaymentMethodHandler handler, ISupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network, InvoiceEntity entity, StoreData store, InvoiceLogs logs) + { + try + { + var storeBlob = store.GetStoreBlob(); + var rate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, entity.ProductInformation.Currency)]; + if (rate.Value == null) + { + return null; + } + PaymentMethod paymentMethod = new PaymentMethod(); + paymentMethod.ParentEntity = entity; + paymentMethod.Network = network; + paymentMethod.SetId(supportedPaymentMethod.PaymentId); + paymentMethod.Rate = rate.Value.Value; + var paymentDetails = await handler.CreatePaymentMethodDetails(supportedPaymentMethod, paymentMethod, store, network); + if (storeBlob.NetworkFeeDisabled) + paymentDetails.SetNoTxFee(); + paymentMethod.SetPaymentMethodDetails(paymentDetails); + + Func compare = null; + CurrencyValue limitValue = null; + string errorMessage = null; + if (supportedPaymentMethod.PaymentId.PaymentType == PaymentTypes.LightningLike && + storeBlob.LightningMaxValue != null) + { + compare = (a, b) => a > b; + limitValue = storeBlob.LightningMaxValue; + errorMessage = "The amount of the invoice is too high to be paid with lightning"; + } + else if (supportedPaymentMethod.PaymentId.PaymentType == PaymentTypes.BTCLike && + storeBlob.OnChainMinValue != null) + { + compare = (a, b) => a < b; + limitValue = storeBlob.OnChainMinValue; + errorMessage = "The amount of the invoice is too low to be paid on chain"; + } + + if (compare != null) + { + var limitValueRate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, limitValue.Currency)]; + if (limitValueRate.Value.HasValue) + { + var limitValueCrypto = Money.Coins(limitValue.Value / limitValueRate.Value.Value); + if (compare(paymentMethod.Calculate().Due, limitValueCrypto)) + { + logs.Write($"{supportedPaymentMethod.PaymentId.CryptoCode}: {errorMessage}"); + return null; + } + } + } + /////////////// #pragma warning disable CS0618 - if (paymentMethod.GetId().IsBTCOnChain) - { - entity.TxFee = paymentMethod.TxFee; - entity.Rate = paymentMethod.Rate; - entity.DepositAddress = paymentMethod.DepositAddress; - } + if (paymentMethod.GetId().IsBTCOnChain) + { + entity.TxFee = paymentMethod.TxFee; + entity.Rate = paymentMethod.Rate; + entity.DepositAddress = paymentMethod.DepositAddress; + } #pragma warning restore CS0618 - return paymentMethod; + return paymentMethod; + } + catch (PaymentMethodUnavailableException ex) + { + logs.Write($"{supportedPaymentMethod.PaymentId.CryptoCode}: Payment method unavailable ({ex.Message})"); + } + catch (Exception ex) + { + logs.Write($"{supportedPaymentMethod.PaymentId.CryptoCode}: Unexpected exception ({ex.ToString()})"); + } + return null; } private SpeedPolicy ParseSpeedPolicy(string transactionSpeed, SpeedPolicy defaultPolicy) diff --git a/BTCPayServer/Controllers/ServerController.cs b/BTCPayServer/Controllers/ServerController.cs index 2c10440d0..42374ae15 100644 --- a/BTCPayServer/Controllers/ServerController.cs +++ b/BTCPayServer/Controllers/ServerController.cs @@ -1,13 +1,19 @@ -using BTCPayServer.HostedServices; +using BTCPayServer.Configuration; +using Microsoft.Extensions.Logging; +using BTCPayServer.HostedServices; using BTCPayServer.Models; using BTCPayServer.Models.ServerViewModels; +using BTCPayServer.Payments.Lightning; using BTCPayServer.Services; using BTCPayServer.Services.Mails; using BTCPayServer.Services.Rates; +using BTCPayServer.Services.Stores; using BTCPayServer.Validations; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using NBitcoin; +using NBitcoin.DataEncoders; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -16,6 +22,8 @@ using System.Net; using System.Net.Http; using System.Net.Mail; using System.Threading.Tasks; +using Renci.SshNet; +using BTCPayServer.Logging; namespace BTCPayServer.Controllers { @@ -25,14 +33,23 @@ namespace BTCPayServer.Controllers private UserManager _UserManager; SettingsRepository _SettingsRepository; private BTCPayRateProviderFactory _RateProviderFactory; + private StoreRepository _StoreRepository; + LightningConfigurationProvider _LnConfigProvider; + BTCPayServerOptions _Options; public ServerController(UserManager userManager, + Configuration.BTCPayServerOptions options, BTCPayRateProviderFactory rateProviderFactory, - SettingsRepository settingsRepository) + SettingsRepository settingsRepository, + LightningConfigurationProvider lnConfigProvider, + Services.Stores.StoreRepository storeRepository) { + _Options = options; _UserManager = userManager; _SettingsRepository = settingsRepository; _RateProviderFactory = rateProviderFactory; + _StoreRepository = storeRepository; + _LnConfigProvider = lnConfigProvider; } [Route("server/rates")] @@ -74,7 +91,7 @@ namespace BTCPayServer.Controllers try { var service = GetCoinaverageService(vm, true); - if(service != null) + if (service != null) await service.TestAuthAsync(); } catch @@ -134,6 +151,150 @@ namespace BTCPayServer.Controllers return View(userVM); } + [Route("server/maintenance")] + public IActionResult Maintenance() + { + MaintenanceViewModel vm = new MaintenanceViewModel(); + vm.UserName = "btcpayserver"; + vm.DNSDomain = this.Request.Host.Host; + if (IPAddress.TryParse(vm.DNSDomain, out var unused)) + vm.DNSDomain = null; + return View(vm); + } + [Route("server/maintenance")] + [HttpPost] + public async Task Maintenance(MaintenanceViewModel vm, string command) + { + if (!ModelState.IsValid) + return View(vm); + if (command == "changedomain") + { + if (string.IsNullOrWhiteSpace(vm.DNSDomain)) + { + ModelState.AddModelError(nameof(vm.DNSDomain), $"Required field"); + return View(vm); + } + vm.DNSDomain = vm.DNSDomain.Trim().ToLowerInvariant(); + if (IPAddress.TryParse(vm.DNSDomain, out var unused)) + { + ModelState.AddModelError(nameof(vm.DNSDomain), $"This should be a domain name"); + return View(vm); + } + if (vm.DNSDomain.Equals(this.Request.Host.Host, StringComparison.InvariantCultureIgnoreCase)) + { + ModelState.AddModelError(nameof(vm.DNSDomain), $"The server is already set to use this domain"); + return View(vm); + } + var builder = new UriBuilder(); + using (var client = new HttpClient(new HttpClientHandler() + { + ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator + })) + { + try + { + builder.Scheme = this.Request.Scheme; + builder.Host = vm.DNSDomain; + if (this.Request.Host.Port != null) + builder.Port = this.Request.Host.Port.Value; + builder.Path = "runid"; + builder.Query = $"expected={RunId}"; + var response = await client.GetAsync(builder.Uri); + if (!response.IsSuccessStatusCode) + { + ModelState.AddModelError(nameof(vm.DNSDomain), $"Invalid host ({vm.DNSDomain} is not pointing to this BTCPay instance)"); + return View(vm); + } + } + catch (Exception ex) + { + var messages = new List(); + messages.Add(ex.Message); + if (ex.InnerException != null) + messages.Add(ex.InnerException.Message); + ModelState.AddModelError(nameof(vm.DNSDomain), $"Invalid domain ({string.Join(", ", messages.ToArray())})"); + return View(vm); + } + } + + var error = RunSSH(vm, $"changedomain.sh {vm.DNSDomain}"); + if (error != null) + return error; + + builder.Path = null; + builder.Query = null; + StatusMessage = $"Domain name changing... the server will restart, please use \"{builder.Uri.AbsoluteUri}\""; + } + else if (command == "update") + { + var error = RunSSH(vm, $"btcpay-update.sh"); + if (error != null) + return error; + StatusMessage = $"The server might restart soon if an update is available..."; + } + else + { + return NotFound(); + } + return RedirectToAction(nameof(Maintenance)); + } + + public static string RunId = Encoders.Hex.EncodeData(NBitcoin.RandomUtils.GetBytes(32)); + [HttpGet] + [Route("runid")] + [AllowAnonymous] + public IActionResult SeeRunId(string expected = null) + { + if (expected == RunId) + return Ok(); + return BadRequest(); + } + + private IActionResult RunSSH(MaintenanceViewModel vm, string ssh) + { + ssh = $"sudo bash -c '. /etc/profile.d/btcpay-env.sh && nohup {ssh} > /dev/null 2>&1 & disown'"; + var sshClient = vm.CreateSSHClient(this.Request.Host.Host); + try + { + sshClient.Connect(); + } + catch (Renci.SshNet.Common.SshAuthenticationException) + { + ModelState.AddModelError(nameof(vm.Password), "Invalid credentials"); + sshClient.Dispose(); + return View(vm); + } + catch (Exception ex) + { + var message = ex.Message; + if (ex is AggregateException aggrEx && aggrEx.InnerException?.Message != null) + { + message = aggrEx.InnerException.Message; + } + ModelState.AddModelError(nameof(vm.UserName), $"Connection problem ({message})"); + sshClient.Dispose(); + return View(vm); + } + + var sshCommand = sshClient.CreateCommand(ssh); + sshCommand.CommandTimeout = TimeSpan.FromMinutes(1.0); + sshCommand.BeginExecute(ar => + { + try + { + Logs.PayServer.LogInformation("Running SSH command: " + ssh); + var result = sshCommand.EndExecute(ar); + Logs.PayServer.LogInformation("SSH command executed: " + result); + } + catch (Exception ex) + { + Logs.PayServer.LogWarning("Error while executing SSH command: " + ex.Message); + } + sshClient.Dispose(); + }); + return null; + } + private static bool IsAdmin(IList roles) { return roles.Contains(Roles.ServerAdmin, StringComparer.Ordinal); @@ -188,6 +349,7 @@ namespace BTCPayServer.Controllers if (user == null) return NotFound(); await _UserManager.DeleteAsync(user); + await _StoreRepository.CleanUnreachableStores(); StatusMessage = "User deleted"; return RedirectToAction(nameof(ListUsers)); } @@ -220,6 +382,122 @@ namespace BTCPayServer.Controllers return View(settings); } + [Route("server/services")] + public IActionResult Services() + { + var result = new ServicesViewModel(); + foreach (var cryptoCode in _Options.ExternalServicesByCryptoCode.Keys) + { + { + int i = 0; + foreach (var grpcService in _Options.ExternalServicesByCryptoCode.GetServices(cryptoCode)) + { + result.LNDServices.Add(new ServicesViewModel.LNDServiceViewModel() + { + Crypto = cryptoCode, + Type = "gRPC", + Index = i++, + }); + } + } + } + return View(result); + } + + [Route("server/services/lnd-grpc/{cryptoCode}/{index}")] + public IActionResult LNDGRPCServices(string cryptoCode, int index, uint? nonce) + { + var external = GetExternalLNDConnectionString(cryptoCode, index); + if (external == null) + return NotFound(); + var model = new LNDGRPCServicesViewModel(); + + model.Host = $"{external.BaseUri.DnsSafeHost}:{external.BaseUri.Port}"; + model.SSL = external.BaseUri.Scheme == "https"; + if (external.CertificateThumbprint != null) + { + model.CertificateThumbprint = Encoders.Hex.EncodeData(external.CertificateThumbprint); + } + if (external.Macaroon != null) + { + model.Macaroon = Encoders.Hex.EncodeData(external.Macaroon); + } + + if (nonce != null) + { + var configKey = GetConfigKey("lnd-grpc", cryptoCode, index, nonce.Value); + var lnConfig = _LnConfigProvider.GetConfig(configKey); + if (lnConfig != null) + { + model.QRCodeLink = $"{this.Request.GetAbsoluteRoot().WithTrailingSlash()}lnd-config/{configKey}/lnd.config"; + model.QRCode = $"config={model.QRCodeLink}"; + } + } + + return View(model); + } + + private static uint GetConfigKey(string type, string cryptoCode, int index, uint nonce) + { + return (uint)HashCode.Combine(type, cryptoCode, index, nonce); + } + + [Route("lnd-config/{configKey}/lnd.config")] + [AllowAnonymous] + public IActionResult GetLNDConfig(uint configKey) + { + var conf = _LnConfigProvider.GetConfig(configKey); + if (conf == null) + return NotFound(); + return Json(conf); + } + + [Route("server/services/lnd-grpc/{cryptoCode}/{index}")] + [HttpPost] + public IActionResult LNDGRPCServicesPOST(string cryptoCode, int index) + { + var external = GetExternalLNDConnectionString(cryptoCode, index); + if (external == null) + return NotFound(); + LightningConfigurations confs = new LightningConfigurations(); + LightningConfiguration conf = new LightningConfiguration(); + conf.Type = "grpc"; + conf.CryptoCode = cryptoCode; + conf.Host = external.BaseUri.DnsSafeHost; + conf.Port = external.BaseUri.Port; + conf.SSL = external.BaseUri.Scheme == "https"; + conf.Macaroon = external.Macaroon == null ? null : Encoders.Hex.EncodeData(external.Macaroon); + conf.CertificateThumbprint = external.CertificateThumbprint == null ? null : Encoders.Hex.EncodeData(external.CertificateThumbprint); + confs.Configurations.Add(conf); + + var nonce = RandomUtils.GetUInt32(); + var configKey = GetConfigKey("lnd-grpc", cryptoCode, index, nonce); + _LnConfigProvider.KeepConfig(configKey, confs); + return RedirectToAction(nameof(LNDGRPCServices), new { cryptoCode = cryptoCode, nonce = nonce }); + } + + private LightningConnectionString GetExternalLNDConnectionString(string cryptoCode, int index) + { + var connectionString = _Options.ExternalServicesByCryptoCode.GetServices(cryptoCode).Skip(index).Select(c => c.ConnectionString).FirstOrDefault(); + if (connectionString == null) + return null; + connectionString = connectionString.Clone(); + if (connectionString.MacaroonFilePath != null) + { + try + { + connectionString.Macaroon = System.IO.File.ReadAllBytes(connectionString.MacaroonFilePath); + connectionString.MacaroonFilePath = null; + } + catch + { + Logging.Logs.Configuration.LogWarning($"{cryptoCode}: The macaroon file path of the external LND grpc config was not found ({connectionString.MacaroonFilePath})"); + return null; + } + } + return connectionString; + } + [Route("server/theme")] public async Task Theme() { @@ -243,7 +521,7 @@ namespace BTCPayServer.Controllers { try { - if(!model.Settings.IsComplete()) + if (!model.Settings.IsComplete()) { model.StatusMessage = "Error: Required fields missing"; return View(model); diff --git a/BTCPayServer/Controllers/StoresController.BTCLike.cs b/BTCPayServer/Controllers/StoresController.BTCLike.cs index 944c97f4f..62d16df2e 100644 --- a/BTCPayServer/Controllers/StoresController.BTCLike.cs +++ b/BTCPayServer/Controllers/StoresController.BTCLike.cs @@ -34,7 +34,7 @@ namespace BTCPayServer.Controllers } DerivationSchemeViewModel vm = new DerivationSchemeViewModel(); - vm.ServerUrl = GetStoreUrl(storeId); + vm.ServerUrl = WalletsController.GetLedgerWebsocketUrl(this.HttpContext, cryptoCode, null); vm.CryptoCode = cryptoCode; vm.RootKeyPath = network.GetRootKeyPath(); SetExistingValues(store, vm); @@ -59,7 +59,7 @@ namespace BTCPayServer.Controllers [Route("{storeId}/derivations/{cryptoCode}")] public async Task AddDerivationScheme(string storeId, DerivationSchemeViewModel vm, string cryptoCode) { - vm.ServerUrl = GetStoreUrl(storeId); + vm.ServerUrl = WalletsController.GetLedgerWebsocketUrl(this.HttpContext, cryptoCode, null); vm.CryptoCode = cryptoCode; var store = HttpContext.GetStoreData(); if (store == null) @@ -161,262 +161,5 @@ namespace BTCPayServer.Controllers vm.Confirmation = true; return View(vm); } - - - public class GetInfoResult - { - public int RecommendedSatoshiPerByte { get; set; } - public double Balance { get; set; } - } - - public class SendToAddressResult - { - public string TransactionId { get; set; } - } - - [HttpGet] - [Route("{storeId}/ws/ledger")] - public async Task LedgerConnection( - string storeId, - string command, - // getinfo - string cryptoCode = null, - // getxpub - int account = 0, - // sendtoaddress - string destination = null, string amount = null, string feeRate = null, string substractFees = null - ) - { - if (!HttpContext.WebSockets.IsWebSocketRequest) - return NotFound(); - var store = HttpContext.GetStoreData(); - if (store == null) - return NotFound(); - - var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); - - var hw = new HardwareWalletService(webSocket); - object result = null; - try - { - BTCPayNetwork network = null; - if (cryptoCode != null) - { - network = _NetworkProvider.GetNetwork(cryptoCode); - if (network == null) - throw new FormatException("Invalid value for crypto code"); - } - - BitcoinAddress destinationAddress = null; - if (destination != null) - { - try - { - destinationAddress = BitcoinAddress.Create(destination, network.NBitcoinNetwork); - } - catch { } - if (destinationAddress == null) - throw new FormatException("Invalid value for destination"); - } - - FeeRate feeRateValue = null; - if (feeRate != null) - { - try - { - feeRateValue = new FeeRate(Money.Satoshis(int.Parse(feeRate, CultureInfo.InvariantCulture)), 1); - } - catch { } - if (feeRateValue == null || feeRateValue.FeePerK <= Money.Zero) - throw new FormatException("Invalid value for fee rate"); - } - - Money amountBTC = null; - if (amount != null) - { - try - { - amountBTC = Money.Parse(amount); - } - catch { } - if (amountBTC == null || amountBTC <= Money.Zero) - throw new FormatException("Invalid value for amount"); - } - - bool subsctractFeesValue = false; - if (substractFees != null) - { - try - { - subsctractFeesValue = bool.Parse(substractFees); - } - catch { throw new FormatException("Invalid value for subtract fees"); } - } - if (command == "test") - { - result = await hw.Test(); - } - if (command == "getxpub") - { - var getxpubResult = await hw.GetExtPubKey(network, account); - result = getxpubResult; - } - if (command == "getinfo") - { - var strategy = GetDirectDerivationStrategy(store, network); - var strategyBase = GetDerivationStrategy(store, network); - if (strategy == null || await hw.GetKeyPath(network, strategy) == null) - { - throw new Exception($"This store is not configured to use this ledger"); - } - - var feeProvider = _FeeRateProvider.CreateFeeProvider(network); - var recommendedFees = feeProvider.GetFeeRateAsync(); - var balance = _WalletProvider.GetWallet(network).GetBalance(strategyBase); - result = new GetInfoResult() { Balance = (double)(await balance).ToDecimal(MoneyUnit.BTC), RecommendedSatoshiPerByte = (int)(await recommendedFees).GetFee(1).Satoshi }; - } - - if (command == "sendtoaddress") - { - if (!_Dashboard.IsFullySynched(network.CryptoCode, out var summary)) - throw new Exception($"{network.CryptoCode}: not started or fully synched"); - var strategy = GetDirectDerivationStrategy(store, network); - var strategyBase = GetDerivationStrategy(store, network); - var wallet = _WalletProvider.GetWallet(network); - var change = wallet.GetChangeAddressAsync(strategyBase); - - var unspentCoins = await wallet.GetUnspentCoins(strategyBase); - var changeAddress = await change; - var send = new[] { ( - destination: destinationAddress as IDestination, - amount: amountBTC, - substractFees: subsctractFeesValue) }; - - foreach (var element in send) - { - if (element.destination == null) - throw new ArgumentNullException(nameof(element.destination)); - if (element.amount == null) - throw new ArgumentNullException(nameof(element.amount)); - if (element.amount <= Money.Zero) - throw new ArgumentOutOfRangeException(nameof(element.amount), "The amount should be above zero"); - } - - var foundKeyPath = await hw.GetKeyPath(network, strategy); - if (foundKeyPath == null) - { - throw new HardwareWalletException($"This store is not configured to use this ledger"); - } - - TransactionBuilder builder = new TransactionBuilder(); - builder.StandardTransactionPolicy.MinRelayTxFee = summary.Status.BitcoinStatus.MinRelayTxFee; - builder.SetConsensusFactory(network.NBitcoinNetwork); - builder.AddCoins(unspentCoins.Select(c => c.Coin).ToArray()); - - foreach (var element in send) - { - builder.Send(element.destination, element.amount); - if (element.substractFees) - builder.SubtractFees(); - } - builder.SetChange(changeAddress.Item1); - builder.SendEstimatedFees(feeRateValue); - builder.Shuffle(); - var unsigned = builder.BuildTransaction(false); - - var keypaths = new Dictionary(); - foreach (var c in unspentCoins) - { - keypaths.TryAdd(c.Coin.ScriptPubKey, c.KeyPath); - } - - var hasChange = unsigned.Outputs.Count == 2; - var usedCoins = builder.FindSpentCoins(unsigned); - - Dictionary parentTransactions = new Dictionary(); - - if(!strategy.Segwit) - { - var parentHashes = usedCoins.Select(c => c.Outpoint.Hash).ToHashSet(); - var explorer = _ExplorerProvider.GetExplorerClient(network); - var getTransactionAsyncs = parentHashes.Select(h => (Op: explorer.GetTransactionAsync(h), Hash: h)).ToList(); - foreach(var getTransactionAsync in getTransactionAsyncs) - { - var tx = (await getTransactionAsync.Op); - if(tx == null) - throw new Exception($"Parent transaction {getTransactionAsync.Hash} not found"); - parentTransactions.Add(tx.Transaction.GetHash(), tx.Transaction); - } - } - - var transaction = await hw.SignTransactionAsync(usedCoins.Select(c => new SignatureRequest - { - InputTransaction = parentTransactions.TryGet(c.Outpoint.Hash), - InputCoin = c, - KeyPath = foundKeyPath.Derive(keypaths[c.TxOut.ScriptPubKey]), - PubKey = strategy.Root.Derive(keypaths[c.TxOut.ScriptPubKey]).PubKey - }).ToArray(), unsigned, hasChange ? foundKeyPath.Derive(changeAddress.Item2) : null); - - try - { - var broadcastResult = await wallet.BroadcastTransactionsAsync(new List() { transaction }); - if (!broadcastResult[0].Success) - { - throw new Exception($"RPC Error while broadcasting: {broadcastResult[0].RPCCode} {broadcastResult[0].RPCCodeMessage} {broadcastResult[0].RPCMessage}"); - } - } - catch (Exception ex) - { - throw new Exception("Error while broadcasting: " + ex.Message); - } - wallet.InvalidateCache(strategyBase); - result = new SendToAddressResult() { TransactionId = transaction.GetHash().ToString() }; - } - } - catch (OperationCanceledException) - { result = new LedgerTestResult() { Success = false, Error = "Timeout" }; } - catch (Exception ex) - { result = new LedgerTestResult() { Success = false, Error = ex.Message }; } - - try - { - if (result != null) - { - UTF8Encoding UTF8NOBOM = new UTF8Encoding(false); - var bytes = UTF8NOBOM.GetBytes(JsonConvert.SerializeObject(result, _MvcJsonOptions.SerializerSettings)); - await webSocket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, new CancellationTokenSource(2000).Token); - } - } - catch { } - finally - { - await webSocket.CloseSocket(); - } - - return new EmptyResult(); - } - - private DirectDerivationStrategy GetDirectDerivationStrategy(StoreData store, BTCPayNetwork network) - { - var strategy = GetDerivationStrategy(store, network); - var directStrategy = strategy as DirectDerivationStrategy; - if (directStrategy == null) - directStrategy = (strategy as P2SHDerivationStrategy).Inner as DirectDerivationStrategy; - return directStrategy; - } - - private DerivationStrategyBase GetDerivationStrategy(StoreData store, BTCPayNetwork network) - { - var strategy = store - .GetSupportedPaymentMethods(_NetworkProvider) - .OfType() - .FirstOrDefault(s => s.Network.NBitcoinNetwork == network.NBitcoinNetwork); - if (strategy == null) - { - throw new Exception($"Derivation strategy for {network.CryptoCode} is not set"); - } - - return strategy.DerivationStrategyBase; - } } } diff --git a/BTCPayServer/Controllers/StoresController.LightningLike.cs b/BTCPayServer/Controllers/StoresController.LightningLike.cs index 1b51b51b4..79e46ad91 100644 --- a/BTCPayServer/Controllers/StoresController.LightningLike.cs +++ b/BTCPayServer/Controllers/StoresController.LightningLike.cs @@ -26,16 +26,15 @@ namespace BTCPayServer.Controllers return NotFound(); LightningNodeViewModel vm = new LightningNodeViewModel(); vm.CryptoCode = cryptoCode; - vm.InternalLightningNode = GetInternalLighningNode(cryptoCode)?.ToUri(true)?.AbsoluteUri; + vm.InternalLightningNode = GetInternalLighningNode(cryptoCode)?.ToString(); SetExistingValues(store, vm); return View(vm); } private void SetExistingValues(StoreData store, LightningNodeViewModel vm) { - vm.Url = GetExistingLightningSupportedPaymentMethod(vm.CryptoCode, store)?.GetLightningUrl()?.ToString(); + vm.ConnectionString = GetExistingLightningSupportedPaymentMethod(vm.CryptoCode, store)?.GetLightningUrl()?.ToString(); } - private LightningSupportedPaymentMethod GetExistingLightningSupportedPaymentMethod(string cryptoCode, StoreData store) { var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike); @@ -65,7 +64,7 @@ namespace BTCPayServer.Controllers var network = vm.CryptoCode == null ? null : _ExplorerProvider.GetNetwork(vm.CryptoCode); var internalLightning = GetInternalLighningNode(network.CryptoCode); - vm.InternalLightningNode = internalLightning?.ToUri(true)?.AbsoluteUri; + vm.InternalLightningNode = internalLightning?.ToString(); if (network == null) { ModelState.AddModelError(nameof(vm.CryptoCode), "Invalid network"); @@ -74,33 +73,57 @@ namespace BTCPayServer.Controllers PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.LightningLike); Payments.Lightning.LightningSupportedPaymentMethod paymentMethod = null; - if (!string.IsNullOrEmpty(vm.Url)) + if (!string.IsNullOrEmpty(vm.ConnectionString)) { - if (!LightningConnectionString.TryParse(vm.Url, out var connectionString, out var error)) + if (!LightningConnectionString.TryParse(vm.ConnectionString, false, out var connectionString, out var error)) { - ModelState.AddModelError(nameof(vm.Url), $"Invalid URL ({error})"); + ModelState.AddModelError(nameof(vm.ConnectionString), $"Invalid URL ({error})"); return View(vm); } - var internalDomain = internalLightning?.ToUri(false)?.DnsSafeHost; - bool isLocal = (internalDomain == "127.0.0.1" || internalDomain == "localhost"); + if(connectionString.ConnectionType == LightningConnectionType.LndGRPC) + { + ModelState.AddModelError(nameof(vm.ConnectionString), $"BTCPay does not support gRPC connections"); + return View(vm); + } + + var internalDomain = internalLightning?.BaseUri?.DnsSafeHost; bool isInternalNode = connectionString.ConnectionType == LightningConnectionType.CLightning || connectionString.BaseUri.DnsSafeHost == internalDomain || - isLocal; + (internalDomain == "127.0.0.1" || internalDomain == "localhost"); - if (connectionString.BaseUri.Scheme == "http" && !isLocal) + if (connectionString.BaseUri.Scheme == "http") { - if (!isInternalNode || (isInternalNode && !CanUseInternalLightning())) + if (!isInternalNode) { - ModelState.AddModelError(nameof(vm.Url), "The url must be HTTPS"); + ModelState.AddModelError(nameof(vm.ConnectionString), "The url must be HTTPS"); + return View(vm); + } + } + + if(connectionString.MacaroonFilePath != null) + { + if(!CanUseInternalLightning()) + { + ModelState.AddModelError(nameof(vm.ConnectionString), "You are not authorized to use macaroonfilepath"); + return View(vm); + } + if(!System.IO.File.Exists(connectionString.MacaroonFilePath)) + { + ModelState.AddModelError(nameof(vm.ConnectionString), "The macaroonfilepath file does exist"); + return View(vm); + } + if(!System.IO.Path.IsPathRooted(connectionString.MacaroonFilePath)) + { + ModelState.AddModelError(nameof(vm.ConnectionString), "The macaroonfilepath should be fully rooted"); return View(vm); } } if (isInternalNode && !CanUseInternalLightning()) { - ModelState.AddModelError(nameof(vm.Url), "Unauthorized url"); + ModelState.AddModelError(nameof(vm.ConnectionString), "Unauthorized url"); return View(vm); } @@ -110,6 +133,7 @@ namespace BTCPayServer.Controllers }; paymentMethod.SetLightningUrl(connectionString); } + if (command == "save") { store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod); @@ -121,7 +145,7 @@ namespace BTCPayServer.Controllers { if (paymentMethod == null) { - ModelState.AddModelError(nameof(vm.Url), "Missing url parameter"); + ModelState.AddModelError(nameof(vm.ConnectionString), "Missing url parameter"); return View(vm); } var handler = (LightningLikePaymentHandler)_ServiceProvider.GetRequiredService>(); @@ -135,7 +159,7 @@ namespace BTCPayServer.Controllers await handler.TestConnection(info, cts.Token); } } - vm.StatusMessage = $"Connection to the lightning node succeed ({info})"; + vm.StatusMessage = $"Connection to the lightning node succeeded ({info})"; } catch (Exception ex) { diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index 12b7cf005..daef61110 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -38,11 +38,9 @@ namespace BTCPayServer.Controllers BTCPayRateProviderFactory _RateFactory; public string CreatedStoreId { get; set; } public StoresController( - NBXplorerDashboard dashboard, IServiceProvider serviceProvider, BTCPayServerOptions btcpayServerOptions, BTCPayServerEnvironment btcpayEnv, - IOptions mvcJsonOptions, StoreRepository repo, TokenRepository tokenRepo, UserManager userManager, @@ -56,7 +54,6 @@ namespace BTCPayServer.Controllers IHostingEnvironment env) { _RateFactory = rateFactory; - _Dashboard = dashboard; _Repo = repo; _TokenRepository = tokenRepo; _UserManager = userManager; @@ -66,19 +63,16 @@ namespace BTCPayServer.Controllers _Env = env; _NetworkProvider = networkProvider; _ExplorerProvider = explorerProvider; - _MvcJsonOptions = mvcJsonOptions.Value; _FeeRateProvider = feeRateProvider; _ServiceProvider = serviceProvider; _BtcpayServerOptions = btcpayServerOptions; _BTCPayEnv = btcpayEnv; } - NBXplorerDashboard _Dashboard; BTCPayServerOptions _BtcpayServerOptions; BTCPayServerEnvironment _BTCPayEnv; IServiceProvider _ServiceProvider; BTCPayNetworkProvider _NetworkProvider; private ExplorerClientProvider _ExplorerProvider; - private MvcJsonOptions _MvcJsonOptions; private IFeeProviderFactory _FeeRateProvider; BTCPayWalletProvider _WalletProvider; AccessTokenController _TokenController; @@ -94,21 +88,6 @@ namespace BTCPayServer.Controllers get; set; } - [HttpGet] - [Route("{storeId}/wallet/{cryptoCode}")] - public IActionResult Wallet(string cryptoCode) - { - WalletModel model = new WalletModel(); - model.ServerUrl = GetStoreUrl(StoreData.Id); - model.CryptoCurrency = cryptoCode; - return View(model); - } - - private string GetStoreUrl(string storeId) - { - return HttpContext.Request.GetAbsoluteRoot() + "/stores/" + storeId + "/"; - } - [HttpGet] [Route("{storeId}/users")] public async Task StoreUsers() @@ -283,7 +262,7 @@ namespace BTCPayServer.Controllers { CurrencyPair = fetch.Key.ToString(), Error = testResult.Errors.Count != 0, - Rule = testResult.Errors.Count == 0 ? testResult.Rule + " = " + testResult.Value.Value.ToString(CultureInfo.InvariantCulture) + Rule = testResult.Errors.Count == 0 ? testResult.Rule + " = " + testResult.Value.Value.ToString(CultureInfo.InvariantCulture) : testResult.EvaluatedRule }); } @@ -313,7 +292,7 @@ namespace BTCPayServer.Controllers Action = "Continue", Title = "Rate rule scripting", Description = scripting ? - "This action will mofify your current rate sources. Are you sure to turn on rate rules scripting? (Advanced users)" + "This action will modify your current rate sources. Are you sure to turn on rate rules scripting? (Advanced users)" : "This action will delete your rate script. Are you sure to turn off rate rules scripting?", ButtonClass = "btn-primary" }); @@ -424,6 +403,7 @@ namespace BTCPayServer.Controllers vm.StoreWebsite = store.StoreWebsite; vm.NetworkFee = !storeBlob.NetworkFeeDisabled; vm.SpeedPolicy = store.SpeedPolicy; + vm.CanDelete = _Repo.CanDeleteStores(); AddPaymentMethods(store, vm); vm.MonitoringExpiration = storeBlob.MonitoringExpiration; vm.InvoiceExpiration = storeBlob.InvoiceExpiration; @@ -446,7 +426,8 @@ namespace BTCPayServer.Controllers vm.DerivationSchemes.Add(new StoreViewModel.DerivationScheme() { Crypto = network.CryptoCode, - Value = strategy?.DerivationStrategyBase?.ToString() ?? string.Empty + Value = strategy?.DerivationStrategyBase?.ToString() ?? string.Empty, + WalletId = new WalletId(store.Id, network.CryptoCode), }); } @@ -468,10 +449,8 @@ namespace BTCPayServer.Controllers [HttpPost] [Route("{storeId}")] - public async Task UpdateStore(StoreViewModel model) + public async Task UpdateStore(StoreViewModel model, string command = null) { - AddPaymentMethods(StoreData, model); - bool needUpdate = false; if (StoreData.SpeedPolicy != model.SpeedPolicy) { @@ -511,6 +490,29 @@ namespace BTCPayServer.Controllers { storeId = StoreData.Id }); + + } + + [HttpGet] + [Route("{storeId}/delete")] + public IActionResult DeleteStore(string storeId) + { + return View("Confirm", new ConfirmModel() + { + Action = "Delete this store", + Title = "Delete this store", + Description = "This action is irreversible and will remove all information related to this store. (Invoices, Apps etc...)", + ButtonClass = "btn-danger" + }); + } + + [HttpPost] + [Route("{storeId}/delete")] + public async Task DeleteStorePost(string storeId) + { + await _Repo.DeleteStore(StoreData.Id); + StatusMessage = "Success: Store successfully deleted"; + return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores"); } private CoinAverageExchange[] GetSupportedExchanges() diff --git a/BTCPayServer/Controllers/UserStoresController.cs b/BTCPayServer/Controllers/UserStoresController.cs index 417277356..7e5a91789 100644 --- a/BTCPayServer/Controllers/UserStoresController.cs +++ b/BTCPayServer/Controllers/UserStoresController.cs @@ -23,33 +23,16 @@ namespace BTCPayServer.Controllers private StoreRepository _Repo; private BTCPayNetworkProvider _NetworkProvider; private UserManager _UserManager; - private BTCPayWalletProvider _WalletProvider; public UserStoresController( UserManager userManager, BTCPayNetworkProvider networkProvider, - BTCPayWalletProvider walletProvider, StoreRepository storeRepository) { _Repo = storeRepository; _NetworkProvider = networkProvider; _UserManager = userManager; - _WalletProvider = walletProvider; - } - [HttpGet] - [Route("{storeId}/delete")] - public IActionResult DeleteStore(string storeId) - { - var store = HttpContext.GetStoreData(); - if (store == null) - return NotFound(); - return View("Confirm", new ConfirmModel() - { - Title = "Delete store " + store.StoreName, - Description = "This store will still be accessible to users sharing it", - Action = "Delete" - }); - } + } [HttpGet] [Route("create")] @@ -63,8 +46,23 @@ namespace BTCPayServer.Controllers get; set; } + [HttpGet] + [Route("{storeId}/me/delete")] + public IActionResult DeleteStore(string storeId) + { + var store = HttpContext.GetStoreData(); + if (store == null) + return NotFound(); + return View("Confirm", new ConfirmModel() + { + Title = "Delete store " + store.StoreName, + Description = "This store will still be accessible to users sharing it", + Action = "Delete" + }); + } + [HttpPost] - [Route("{storeId}/delete")] + [Route("{storeId}/me/delete")] public async Task DeleteStorePost(string storeId) { var userId = GetUserId(); @@ -84,17 +82,6 @@ namespace BTCPayServer.Controllers { StoresViewModel result = new StoresViewModel(); var stores = await _Repo.GetStoresByUserId(GetUserId()); - - var balances = stores - .Select(s => s.GetSupportedPaymentMethods(_NetworkProvider) - .OfType() - .Select(d => ((Wallet: _WalletProvider.GetWallet(d.Network), - DerivationStrategy: d.DerivationStrategyBase))) - .Where(_ => _.Wallet != null) - .Select(async _ => (await GetBalanceString(_)) + " " + _.Wallet.Network.CryptoCode)) - .ToArray(); - - await Task.WhenAll(balances.SelectMany(_ => _)); for (int i = 0; i < stores.Length; i++) { var store = stores[i]; @@ -103,8 +90,7 @@ namespace BTCPayServer.Controllers Id = store.Id, Name = store.StoreName, WebSite = store.StoreWebsite, - IsOwner = store.HasClaim(Policies.CanModifyStoreSettings.Key), - Balances = store.HasClaim(Policies.CanModifyStoreSettings.Key) ? balances[i].Select(t => t.Result).ToArray() : Array.Empty() + IsOwner = store.HasClaim(Policies.CanModifyStoreSettings.Key) }); } return View(result); @@ -121,25 +107,12 @@ namespace BTCPayServer.Controllers var store = await _Repo.CreateStore(GetUserId(), vm.Name); CreatedStoreId = store.Id; StatusMessage = "Store successfully created"; - return RedirectToAction(nameof(ListStores)); - } - - private static async Task GetBalanceString((BTCPayWallet Wallet, DerivationStrategyBase DerivationStrategy) _) - { - using (CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) + return RedirectToAction(nameof(StoresController.UpdateStore), "Stores", new { - try - { - return (await _.Wallet.GetBalance(_.DerivationStrategy, cts.Token)).ToString(); - } - catch - { - return "--"; - } - } + storeId = store.Id + }); } - private string GetUserId() { return _UserManager.GetUserId(User); diff --git a/BTCPayServer/Controllers/WalletsController.cs b/BTCPayServer/Controllers/WalletsController.cs new file mode 100644 index 000000000..9ef0efaf9 --- /dev/null +++ b/BTCPayServer/Controllers/WalletsController.cs @@ -0,0 +1,487 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using BTCPayServer.Data; +using BTCPayServer.HostedServices; +using BTCPayServer.ModelBinders; +using BTCPayServer.Models; +using BTCPayServer.Models.WalletViewModels; +using BTCPayServer.Security; +using BTCPayServer.Services; +using BTCPayServer.Services.Rates; +using BTCPayServer.Services.Stores; +using BTCPayServer.Services.Wallets; +using LedgerWallet; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using NBitcoin; +using NBXplorer.DerivationStrategy; +using Newtonsoft.Json; +using static BTCPayServer.Controllers.StoresController; + +namespace BTCPayServer.Controllers +{ + [Route("wallets")] + [Authorize(AuthenticationSchemes = Policies.CookieAuthentication)] + [AutoValidateAntiforgeryToken] + public class WalletsController : Controller + { + private StoreRepository _Repo; + private BTCPayNetworkProvider _NetworkProvider; + private readonly UserManager _userManager; + private readonly IOptions _mvcJsonOptions; + private readonly NBXplorerDashboard _dashboard; + private readonly ExplorerClientProvider _explorerProvider; + private readonly IFeeProviderFactory _feeRateProvider; + private readonly BTCPayWalletProvider _walletProvider; + BTCPayRateProviderFactory _RateProvider; + CurrencyNameTable _currencyTable; + public WalletsController(StoreRepository repo, + CurrencyNameTable currencyTable, + BTCPayNetworkProvider networkProvider, + UserManager userManager, + IOptions mvcJsonOptions, + NBXplorerDashboard dashboard, + BTCPayRateProviderFactory rateProvider, + ExplorerClientProvider explorerProvider, + IFeeProviderFactory feeRateProvider, + BTCPayWalletProvider walletProvider) + { + _currencyTable = currencyTable; + _Repo = repo; + _RateProvider = rateProvider; + _NetworkProvider = networkProvider; + _userManager = userManager; + _mvcJsonOptions = mvcJsonOptions; + _dashboard = dashboard; + _explorerProvider = explorerProvider; + _feeRateProvider = feeRateProvider; + _walletProvider = walletProvider; + } + + public async Task ListWallets() + { + var wallets = new ListWalletsViewModel(); + var stores = await _Repo.GetStoresByUserId(GetUserId()); + + var onChainWallets = stores + .SelectMany(s => s.GetSupportedPaymentMethods(_NetworkProvider) + .OfType() + .Select(d => ((Wallet: _walletProvider.GetWallet(d.Network), + DerivationStrategy: d.DerivationStrategyBase, + Network: d.Network))) + .Where(_ => _.Wallet != null) + .Select(_ => (Wallet: _.Wallet, + Store: s, + Balance: GetBalanceString(_.Wallet, _.DerivationStrategy), + DerivationStrategy: _.DerivationStrategy, + Network: _.Network))) + .ToList(); + + foreach (var wallet in onChainWallets) + { + ListWalletsViewModel.WalletViewModel walletVm = new ListWalletsViewModel.WalletViewModel(); + wallets.Wallets.Add(walletVm); + walletVm.Balance = await wallet.Balance + " " + wallet.Wallet.Network.CryptoCode; + if (!wallet.Store.HasClaim(Policies.CanModifyStoreSettings.Key)) + { + walletVm.Balance = ""; + } + walletVm.CryptoCode = wallet.Network.CryptoCode; + walletVm.StoreId = wallet.Store.Id; + walletVm.Id = new WalletId(wallet.Store.Id, wallet.Network.CryptoCode); + walletVm.StoreName = wallet.Store.StoreName; + walletVm.IsOwner = wallet.Store.HasClaim(Policies.CanModifyStoreSettings.Key); + } + + return View(wallets); + } + + [HttpGet] + [Route("{walletId}")] + public async Task WalletTransactions( + [ModelBinder(typeof(WalletIdModelBinder))] + WalletId walletId) + { + var store = await _Repo.FindStore(walletId.StoreId, GetUserId()); + DerivationStrategy paymentMethod = GetPaymentMethod(walletId, store); + if (paymentMethod == null) + return NotFound(); + + var wallet = _walletProvider.GetWallet(paymentMethod.Network); + var transactions = await wallet.FetchTransactions(paymentMethod.DerivationStrategyBase); + + var model = new ListTransactionsViewModel(); + foreach(var tx in transactions.UnconfirmedTransactions.Transactions.Concat(transactions.ConfirmedTransactions.Transactions)) + { + var vm = new ListTransactionsViewModel.TransactionViewModel(); + model.Transactions.Add(vm); + vm.Id = tx.TransactionId.ToString(); + vm.Link = string.Format(CultureInfo.InvariantCulture, paymentMethod.Network.BlockExplorerLink, vm.Id); + vm.Timestamp = tx.Timestamp; + vm.Positive = tx.BalanceChange >= Money.Zero; + vm.Balance = tx.BalanceChange.ToString(); + } + model.Transactions = model.Transactions.OrderByDescending(t => t.Timestamp).ToList(); + return View(model); + } + + + [HttpGet] + [Route("{walletId}/send")] + public async Task WalletSend( + [ModelBinder(typeof(WalletIdModelBinder))] + WalletId walletId) + { + if (walletId?.StoreId == null) + return NotFound(); + var store = await _Repo.FindStore(walletId.StoreId, GetUserId()); + DerivationStrategy paymentMethod = GetPaymentMethod(walletId, store); + if (paymentMethod == null) + return NotFound(); + + var storeData = store.GetStoreBlob(); + var rateRules = store.GetStoreBlob().GetRateRules(_NetworkProvider); + rateRules.GlobalMultiplier = 1.0m; + var currencyPair = new Rating.CurrencyPair(paymentMethod.PaymentId.CryptoCode, GetCurrencyCode(storeData.DefaultLang) ?? "USD"); + WalletModel model = new WalletModel(); + model.ServerUrl = GetLedgerWebsocketUrl(this.HttpContext, walletId.CryptoCode, paymentMethod.DerivationStrategyBase); + model.CryptoCurrency = walletId.CryptoCode; + + using (CancellationTokenSource cts = new CancellationTokenSource()) + { + try + { + cts.CancelAfter(TimeSpan.FromSeconds(5)); + var result = await _RateProvider.FetchRate(currencyPair, rateRules).WithCancellation(cts.Token); + if (result.Value != null) + { + model.Rate = result.Value; + model.Divisibility = _currencyTable.GetNumberFormatInfo(currencyPair.Right, true).CurrencyDecimalDigits; + model.Fiat = currencyPair.Right; + } + else + { + model.RateError = $"{result.EvaluatedRule} ({string.Join(", ", result.Errors.OfType().ToArray())})"; + } + } + catch(Exception ex) { model.RateError = ex.Message; } + } + return View(model); + } + + private string GetCurrencyCode(string defaultLang) + { + if (defaultLang == null) + return null; + try + { + var ri = new RegionInfo(defaultLang); + return ri.ISOCurrencySymbol; + } + catch(ArgumentException) { } + return null; + } + + private DerivationStrategy GetPaymentMethod(WalletId walletId, StoreData store) + { + if (store == null || !store.HasClaim(Policies.CanModifyStoreSettings.Key)) + return null; + + var paymentMethod = store + .GetSupportedPaymentMethods(_NetworkProvider) + .OfType() + .FirstOrDefault(p => p.PaymentId.PaymentType == Payments.PaymentTypes.BTCLike && p.PaymentId.CryptoCode == walletId.CryptoCode); + return paymentMethod; + } + + private static async Task GetBalanceString(BTCPayWallet wallet, DerivationStrategyBase derivationStrategy) + { + using (CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) + { + try + { + return (await wallet.GetBalance(derivationStrategy, cts.Token)).ToString(); + } + catch + { + return "--"; + } + } + } + + private string GetUserId() + { + return _userManager.GetUserId(User); + } + + public static string GetLedgerWebsocketUrl(HttpContext httpContext, string cryptoCode, DerivationStrategyBase derivationStrategy) + { + return $"{httpContext.Request.GetAbsoluteRoot().WithTrailingSlash()}ws/ledger/{cryptoCode}/{derivationStrategy?.ToString() ?? string.Empty}"; + } + + [HttpGet] + [Route("/ws/ledger/{cryptoCode}/{derivationScheme?}")] + public async Task LedgerConnection( + string command, + // getinfo + string cryptoCode = null, + // getxpub + [ModelBinder(typeof(ModelBinders.DerivationSchemeModelBinder))] + DerivationStrategyBase derivationScheme = null, + int account = 0, + // sendtoaddress + string destination = null, string amount = null, string feeRate = null, string substractFees = null + ) + { + if (!HttpContext.WebSockets.IsWebSocketRequest) + return NotFound(); + var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); + + using (var normalOperationTimeout = new CancellationTokenSource()) + using (var signTimeout = new CancellationTokenSource()) + { + normalOperationTimeout.CancelAfter(TimeSpan.FromMinutes(30)); + var hw = new HardwareWalletService(webSocket); + object result = null; + try + { + BTCPayNetwork network = null; + if (cryptoCode != null) + { + network = _NetworkProvider.GetNetwork(cryptoCode); + if (network == null) + throw new FormatException("Invalid value for crypto code"); + } + + BitcoinAddress destinationAddress = null; + if (destination != null) + { + try + { + destinationAddress = BitcoinAddress.Create(destination, network.NBitcoinNetwork); + } + catch { } + if (destinationAddress == null) + throw new FormatException("Invalid value for destination"); + } + + FeeRate feeRateValue = null; + if (feeRate != null) + { + try + { + feeRateValue = new FeeRate(Money.Satoshis(int.Parse(feeRate, CultureInfo.InvariantCulture)), 1); + } + catch { } + if (feeRateValue == null || feeRateValue.FeePerK <= Money.Zero) + throw new FormatException("Invalid value for fee rate"); + } + + Money amountBTC = null; + if (amount != null) + { + try + { + amountBTC = Money.Parse(amount); + } + catch { } + if (amountBTC == null || amountBTC <= Money.Zero) + throw new FormatException("Invalid value for amount"); + } + + bool subsctractFeesValue = false; + if (substractFees != null) + { + try + { + subsctractFeesValue = bool.Parse(substractFees); + } + catch { throw new FormatException("Invalid value for subtract fees"); } + } + if (command == "test") + { + result = await hw.Test(normalOperationTimeout.Token); + } + if (command == "getxpub") + { + var getxpubResult = await hw.GetExtPubKey(network, account, normalOperationTimeout.Token); + result = getxpubResult; + } + if (command == "getinfo") + { + var strategy = GetDirectDerivationStrategy(derivationScheme); + if (strategy == null || await hw.GetKeyPath(network, strategy, normalOperationTimeout.Token) == null) + { + throw new Exception($"This store is not configured to use this ledger"); + } + + var feeProvider = _feeRateProvider.CreateFeeProvider(network); + var recommendedFees = feeProvider.GetFeeRateAsync(); + var balance = _walletProvider.GetWallet(network).GetBalance(derivationScheme); + result = new GetInfoResult() { Balance = (double)(await balance).ToDecimal(MoneyUnit.BTC), RecommendedSatoshiPerByte = (int)(await recommendedFees).GetFee(1).Satoshi }; + } + + if (command == "sendtoaddress") + { + if (!_dashboard.IsFullySynched(network.CryptoCode, out var summary)) + throw new Exception($"{network.CryptoCode}: not started or fully synched"); + var strategy = GetDirectDerivationStrategy(derivationScheme); + var wallet = _walletProvider.GetWallet(network); + var change = wallet.GetChangeAddressAsync(derivationScheme); + + var unspentCoins = await wallet.GetUnspentCoins(derivationScheme); + var changeAddress = await change; + var send = new[] { ( + destination: destinationAddress as IDestination, + amount: amountBTC, + substractFees: subsctractFeesValue) }; + + foreach (var element in send) + { + if (element.destination == null) + throw new ArgumentNullException(nameof(element.destination)); + if (element.amount == null) + throw new ArgumentNullException(nameof(element.amount)); + if (element.amount <= Money.Zero) + throw new ArgumentOutOfRangeException(nameof(element.amount), "The amount should be above zero"); + } + + var foundKeyPath = await hw.GetKeyPath(network, strategy, normalOperationTimeout.Token); + if (foundKeyPath == null) + { + throw new HardwareWalletException($"This store is not configured to use this ledger"); + } + + TransactionBuilder builder = new TransactionBuilder(); + builder.StandardTransactionPolicy.MinRelayTxFee = summary.Status.BitcoinStatus.MinRelayTxFee; + builder.SetConsensusFactory(network.NBitcoinNetwork); + builder.AddCoins(unspentCoins.Select(c => c.Coin).ToArray()); + + foreach (var element in send) + { + builder.Send(element.destination, element.amount); + if (element.substractFees) + builder.SubtractFees(); + } + builder.SetChange(changeAddress.Item1); + + if (network.MinFee == null) + { + builder.SendEstimatedFees(feeRateValue); + } + else + { + var estimatedFee = builder.EstimateFees(feeRateValue); + if (network.MinFee > estimatedFee) + builder.SendFees(network.MinFee); + else + builder.SendEstimatedFees(feeRateValue); + } + builder.Shuffle(); + var unsigned = builder.BuildTransaction(false); + + var keypaths = new Dictionary(); + foreach (var c in unspentCoins) + { + keypaths.TryAdd(c.Coin.ScriptPubKey, c.KeyPath); + } + + var hasChange = unsigned.Outputs.Count == 2; + var usedCoins = builder.FindSpentCoins(unsigned); + + Dictionary parentTransactions = new Dictionary(); + + if (!strategy.Segwit) + { + var parentHashes = usedCoins.Select(c => c.Outpoint.Hash).ToHashSet(); + var explorer = _explorerProvider.GetExplorerClient(network); + var getTransactionAsyncs = parentHashes.Select(h => (Op: explorer.GetTransactionAsync(h), Hash: h)).ToList(); + foreach (var getTransactionAsync in getTransactionAsyncs) + { + var tx = (await getTransactionAsync.Op); + if (tx == null) + throw new Exception($"Parent transaction {getTransactionAsync.Hash} not found"); + parentTransactions.Add(tx.Transaction.GetHash(), tx.Transaction); + } + } + + + signTimeout.CancelAfter(TimeSpan.FromMinutes(5)); + var transaction = await hw.SignTransactionAsync(usedCoins.Select(c => new SignatureRequest + { + InputTransaction = parentTransactions.TryGet(c.Outpoint.Hash), + InputCoin = c, + KeyPath = foundKeyPath.Derive(keypaths[c.TxOut.ScriptPubKey]), + PubKey = strategy.Root.Derive(keypaths[c.TxOut.ScriptPubKey]).PubKey + }).ToArray(), unsigned, hasChange ? foundKeyPath.Derive(changeAddress.Item2) : null, signTimeout.Token); + try + { + var broadcastResult = await wallet.BroadcastTransactionsAsync(new List() { transaction }); + if (!broadcastResult[0].Success) + { + throw new Exception($"RPC Error while broadcasting: {broadcastResult[0].RPCCode} {broadcastResult[0].RPCCodeMessage} {broadcastResult[0].RPCMessage}"); + } + } + catch (Exception ex) + { + throw new Exception("Error while broadcasting: " + ex.Message); + } + wallet.InvalidateCache(derivationScheme); + result = new SendToAddressResult() { TransactionId = transaction.GetHash().ToString() }; + } + } + catch (OperationCanceledException) + { result = new LedgerTestResult() { Success = false, Error = "Timeout" }; } + catch (Exception ex) + { result = new LedgerTestResult() { Success = false, Error = ex.Message }; } + finally { hw.Dispose(); } + try + { + if (result != null) + { + UTF8Encoding UTF8NOBOM = new UTF8Encoding(false); + var bytes = UTF8NOBOM.GetBytes(JsonConvert.SerializeObject(result, _mvcJsonOptions.Value.SerializerSettings)); + await webSocket.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, new CancellationTokenSource(2000).Token); + } + } + catch { } + finally + { + await webSocket.CloseSocket(); + } + } + return new EmptyResult(); + } + + private DirectDerivationStrategy GetDirectDerivationStrategy(DerivationStrategyBase strategy) + { + if (strategy == null) + throw new Exception("The derivation scheme is not provided"); + var directStrategy = strategy as DirectDerivationStrategy; + if (directStrategy == null) + directStrategy = (strategy as P2SHDerivationStrategy).Inner as DirectDerivationStrategy; + return directStrategy; + } + } + + + public class GetInfoResult + { + public int RecommendedSatoshiPerByte { get; set; } + public double Balance { get; set; } + } + + public class SendToAddressResult + { + public string TransactionId { get; set; } + } +} diff --git a/BTCPayServer/Data/APIKeyData.cs b/BTCPayServer/Data/APIKeyData.cs index e826c32f7..b1a27af6d 100644 --- a/BTCPayServer/Data/APIKeyData.cs +++ b/BTCPayServer/Data/APIKeyData.cs @@ -19,5 +19,7 @@ namespace BTCPayServer.Data { get; set; } + + public StoreData StoreData { get; set; } } } diff --git a/BTCPayServer/Data/ApplicationDbContext.cs b/BTCPayServer/Data/ApplicationDbContext.cs index fb4507a99..22451d293 100644 --- a/BTCPayServer/Data/ApplicationDbContext.cs +++ b/BTCPayServer/Data/ApplicationDbContext.cs @@ -102,14 +102,27 @@ namespace BTCPayServer.Data { base.OnModelCreating(builder); builder.Entity() - .HasIndex(o => o.StoreDataId); + .HasOne(o => o.StoreData) + .WithMany(a => a.Invoices).OnDelete(DeleteBehavior.Cascade); + builder.Entity().HasIndex(o => o.StoreDataId); + builder.Entity() - .HasIndex(o => o.InvoiceDataId); + .HasOne(o => o.InvoiceData) + .WithMany(i => i.Payments).OnDelete(DeleteBehavior.Cascade); + builder.Entity() + .HasIndex(o => o.InvoiceDataId); + + builder.Entity() + .HasOne(o => o.InvoiceData) + .WithMany(i => i.RefundAddresses).OnDelete(DeleteBehavior.Cascade); builder.Entity() .HasIndex(o => o.InvoiceDataId); + builder.Entity() + .HasOne(o => o.StoreData) + .WithMany(i => i.UserStores).OnDelete(DeleteBehavior.Cascade); builder.Entity() .HasKey(t => new { @@ -117,9 +130,16 @@ namespace BTCPayServer.Data t.StoreDataId }); + builder.Entity() + .HasOne(o => o.StoreData) + .WithMany(i => i.APIKeys) + .HasForeignKey(i => i.StoreId).OnDelete(DeleteBehavior.Cascade); builder.Entity() .HasIndex(o => o.StoreId); + builder.Entity() + .HasOne(o => o.StoreData) + .WithMany(i => i.Apps).OnDelete(DeleteBehavior.Cascade); builder.Entity() .HasOne(a => a.StoreData); @@ -133,6 +153,10 @@ namespace BTCPayServer.Data .WithMany(t => t.UserStores) .HasForeignKey(pt => pt.StoreDataId); + + builder.Entity() + .HasOne(o => o.InvoiceData) + .WithMany(i => i.AddressInvoices).OnDelete(DeleteBehavior.Cascade); builder.Entity() #pragma warning disable CS0618 .HasKey(o => o.Address); @@ -141,12 +165,24 @@ namespace BTCPayServer.Data builder.Entity() .HasKey(o => o.Id); + builder.Entity() + .HasOne(o => o.InvoiceData) + .WithMany(o => o.PendingInvoices) + .HasForeignKey(o => o.Id).OnDelete(DeleteBehavior.Cascade); + + + builder.Entity() + .HasOne(o => o.StoreData) + .WithMany(i => i.PairedSINs).OnDelete(DeleteBehavior.Cascade); builder.Entity(b => { b.HasIndex(o => o.SIN); b.HasIndex(o => o.StoreDataId); }); + builder.Entity() + .HasOne(o => o.InvoiceData) + .WithMany(i => i.HistoricalAddressInvoices).OnDelete(DeleteBehavior.Cascade); builder.Entity() .HasKey(o => new { @@ -156,6 +192,10 @@ namespace BTCPayServer.Data #pragma warning restore CS0618 }); + + builder.Entity() + .HasOne(o => o.InvoiceData) + .WithMany(i => i.Events).OnDelete(DeleteBehavior.Cascade); builder.Entity() .HasKey(o => new { diff --git a/BTCPayServer/Data/ApplicationDbContextFactory.cs b/BTCPayServer/Data/ApplicationDbContextFactory.cs index 1571a34ad..3d1bb2ce0 100644 --- a/BTCPayServer/Data/ApplicationDbContextFactory.cs +++ b/BTCPayServer/Data/ApplicationDbContextFactory.cs @@ -29,6 +29,15 @@ namespace BTCPayServer.Data _Type = type; } + + public DatabaseType Type + { + get + { + return _Type; + } + } + public ApplicationDbContext CreateContext() { var builder = new DbContextOptionsBuilder(); diff --git a/BTCPayServer/Data/HistoricalAddressInvoiceData.cs b/BTCPayServer/Data/HistoricalAddressInvoiceData.cs index fc909f6cf..ad29d672b 100644 --- a/BTCPayServer/Data/HistoricalAddressInvoiceData.cs +++ b/BTCPayServer/Data/HistoricalAddressInvoiceData.cs @@ -12,6 +12,11 @@ namespace BTCPayServer.Data get; set; } + public InvoiceData InvoiceData + { + get; set; + } + /// /// Some crypto currencies share same address prefix /// For not having exceptions thrown by two address on different network, we suffix by "#CRYPTOCODE" diff --git a/BTCPayServer/Data/InvoiceData.cs b/BTCPayServer/Data/InvoiceData.cs index c23326270..10d81c26a 100644 --- a/BTCPayServer/Data/InvoiceData.cs +++ b/BTCPayServer/Data/InvoiceData.cs @@ -80,5 +80,6 @@ namespace BTCPayServer.Data { get; set; } + public List PendingInvoices { get; set; } } } diff --git a/BTCPayServer/Data/InvoiceEventData.cs b/BTCPayServer/Data/InvoiceEventData.cs index 66b50ffcd..e968f44f6 100644 --- a/BTCPayServer/Data/InvoiceEventData.cs +++ b/BTCPayServer/Data/InvoiceEventData.cs @@ -11,6 +11,10 @@ namespace BTCPayServer.Data { get; set; } + public InvoiceData InvoiceData + { + get; set; + } public string UniqueId { get; internal set; } public DateTimeOffset Timestamp { diff --git a/BTCPayServer/Data/PairedSINData.cs b/BTCPayServer/Data/PairedSINData.cs index b3680c835..7f3dcb634 100644 --- a/BTCPayServer/Data/PairedSINData.cs +++ b/BTCPayServer/Data/PairedSINData.cs @@ -21,6 +21,9 @@ namespace BTCPayServer.Data { get; set; } + + public StoreData StoreData { get; set; } + public string Label { get; diff --git a/BTCPayServer/Data/PendingInvoiceData.cs b/BTCPayServer/Data/PendingInvoiceData.cs index 3fd9a7bcc..2bfdde074 100644 --- a/BTCPayServer/Data/PendingInvoiceData.cs +++ b/BTCPayServer/Data/PendingInvoiceData.cs @@ -11,5 +11,6 @@ namespace BTCPayServer.Data { get; set; } + public InvoiceData InvoiceData { get; set; } } } diff --git a/BTCPayServer/Data/StoreData.cs b/BTCPayServer/Data/StoreData.cs index 9fa4224a2..2aff5f446 100644 --- a/BTCPayServer/Data/StoreData.cs +++ b/BTCPayServer/Data/StoreData.cs @@ -34,12 +34,13 @@ namespace BTCPayServer.Data { get; set; } - public List Apps { get; set; } + public List Invoices { get; set; } + [Obsolete("Use GetDerivationStrategies instead")] public string DerivationStrategy { @@ -192,6 +193,8 @@ namespace BTCPayServer.Data } [Obsolete("Use GetDefaultCrypto instead")] public string DefaultCrypto { get; set; } + public List PairedSINs { get; set; } + public IEnumerable APIKeys { get; set; } #pragma warning disable CS0618 public string GetDefaultCrypto() diff --git a/BTCPayServer/DerivationSchemeParser.cs b/BTCPayServer/DerivationSchemeParser.cs index 30f188423..eabcdd6eb 100644 --- a/BTCPayServer/DerivationSchemeParser.cs +++ b/BTCPayServer/DerivationSchemeParser.cs @@ -72,7 +72,7 @@ namespace BTCPayServer } try { - var data = Encoders.Base58Check.DecodeData(parts[i]); + var data = Network.GetBase58CheckEncoder().DecodeData(parts[i]); if (data.Length < 4) continue; var prefix = Utils.ToUInt32(data, false); @@ -80,7 +80,7 @@ namespace BTCPayServer for (int ii = 0; ii < 4; ii++) data[ii] = standardPrefix[ii]; - var derivationScheme = new BitcoinExtPubKey(Encoders.Base58Check.EncodeData(data), Network).ToString(); + var derivationScheme = new BitcoinExtPubKey(Network.GetBase58CheckEncoder().EncodeData(data), Network).ToString(); electrumMapping.TryGetValue(prefix, out string[] labels); if (labels != null) { diff --git a/BTCPayServer/Events/InvoiceEvent.cs b/BTCPayServer/Events/InvoiceEvent.cs index b21e15cc9..c3d0f60a3 100644 --- a/BTCPayServer/Events/InvoiceEvent.cs +++ b/BTCPayServer/Events/InvoiceEvent.cs @@ -8,24 +8,20 @@ namespace BTCPayServer.Events { public class InvoiceEvent { - public InvoiceEvent(InvoiceEntity invoice, int code, string name) : this(invoice.Id, code, name) + public InvoiceEvent(Models.InvoiceResponse invoice, int code, string name) { - - } - public InvoiceEvent(string invoiceId, int code, string name) - { - InvoiceId = invoiceId; + Invoice = invoice; EventCode = code; Name = name; } - public string InvoiceId { get; set; } + public Models.InvoiceResponse Invoice { get; set; } public int EventCode { get; set; } public string Name { get; set; } public override string ToString() { - return $"Invoice {InvoiceId} new event: {Name} ({EventCode})"; + return $"Invoice {Invoice.Id} new event: {Name} ({EventCode})"; } } } diff --git a/BTCPayServer/Extensions.cs b/BTCPayServer/Extensions.cs index 151042737..f7a8f5e9b 100644 --- a/BTCPayServer/Extensions.cs +++ b/BTCPayServer/Extensions.cs @@ -31,6 +31,7 @@ using System.Security.Claims; using System.Globalization; using BTCPayServer.Services; using BTCPayServer.Data; +using Microsoft.EntityFrameworkCore.Infrastructure; namespace BTCPayServer { @@ -82,6 +83,15 @@ namespace BTCPayServer return activeProvider != "Microsoft.EntityFrameworkCore.Sqlite"; } + public static bool SupportDropForeignKey(this Microsoft.EntityFrameworkCore.Migrations.Migration migration, string activeProvider) + { + return activeProvider != "Microsoft.EntityFrameworkCore.Sqlite"; + } + public static bool SupportDropForeignKey(this DatabaseFacade facade) + { + return facade.ProviderName != "Microsoft.EntityFrameworkCore.Sqlite"; + } + public static async Task> GetTransactions(this BTCPayWallet client, uint256[] hashes, CancellationToken cts = default(CancellationToken)) { hashes = hashes.Distinct().ToArray(); @@ -98,6 +108,26 @@ namespace BTCPayServer return str + "/"; } + public static void SetHeaderOnStarting(this HttpResponse resp, string name, string value) + { + if (resp.HasStarted) + return; + resp.OnStarting(() => + { + SetHeader(resp, name, value); + return Task.CompletedTask; + }); + } + + public static void SetHeader(this HttpResponse resp, string name, string value) + { + var existing = resp.Headers[name].FirstOrDefault(); + if (existing != null && value == null) + resp.Headers.Remove(name); + else + resp.Headers[name] = value; + } + public static string GetAbsoluteRoot(this HttpRequest request) { return string.Concat( @@ -109,7 +139,7 @@ namespace BTCPayServer public static string GetAbsoluteUri(this HttpRequest request, string redirectUrl) { - bool isRelative = + bool isRelative = (redirectUrl.Length > 0 && redirectUrl[0] == '/') || !new Uri(redirectUrl, UriKind.RelativeOrAbsolute).IsAbsoluteUri; return isRelative ? request.GetAbsoluteRoot() + redirectUrl : redirectUrl; @@ -141,7 +171,7 @@ namespace BTCPayServer public static void AddRange(this HashSet hashSet, IEnumerable items) { - foreach(var item in items) + foreach (var item in items) { hashSet.Add(item); } @@ -157,6 +187,30 @@ namespace BTCPayServer NBitcoin.Extensions.TryAdd(ctx.Items, "BitpayAuth", value); } + public static async Task WithCancellation(this Task task, CancellationToken cancellationToken) + { + using (var delayCTS = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) + { + var waiting = Task.Delay(-1, delayCTS.Token); + var doing = task; + await Task.WhenAny(waiting, doing); + delayCTS.Cancel(); + cancellationToken.ThrowIfCancellationRequested(); + return await doing; + } + } + public static async Task WithCancellation(this Task task, CancellationToken cancellationToken) + { + using (var delayCTS = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) + { + var waiting = Task.Delay(-1, delayCTS.Token); + var doing = task; + await Task.WhenAny(waiting, doing); + delayCTS.Cancel(); + cancellationToken.ThrowIfCancellationRequested(); + } + } + public static (string Signature, String Id, String Authorization) GetBitpayAuth(this HttpContext ctx) { ctx.Items.TryGetValue("BitpayAuth", out object obj); diff --git a/BTCPayServer/Filters/ContentSecurityPolicyAttribute.cs b/BTCPayServer/Filters/ContentSecurityPolicyAttribute.cs new file mode 100644 index 000000000..45839d17e --- /dev/null +++ b/BTCPayServer/Filters/ContentSecurityPolicyAttribute.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BTCPayServer.Security; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace BTCPayServer.Filters +{ + public interface IContentSecurityPolicy : IFilterMetadata { } + public class ContentSecurityPolicyAttribute : Attribute, IActionFilter, IContentSecurityPolicy + { + public void OnActionExecuted(ActionExecutedContext context) + { + + } + + public bool AutoSelf { get; set; } = true; + public bool UnsafeInline { get; set; } = true; + public bool FixWebsocket { get; set; } = true; + public string FontSrc { get; set; } = null; + public string ImgSrc { get; set; } = null; + public string DefaultSrc { get; set; } + public string StyleSrc { get; set; } + public string ScriptSrc { get; set; } + + public void OnActionExecuting(ActionExecutingContext context) + { + if (context.IsEffectivePolicy(this)) + { + var policies = context.HttpContext.RequestServices.GetService(typeof(ContentSecurityPolicies)) as ContentSecurityPolicies; + if (policies == null) + return; + if (DefaultSrc != null) + { + policies.Add(new ConsentSecurityPolicy("default-src", DefaultSrc)); + } + if (UnsafeInline) + { + policies.Add(new ConsentSecurityPolicy("script-src", "'unsafe-inline'")); + } + if (!string.IsNullOrEmpty(FontSrc)) + { + policies.Add(new ConsentSecurityPolicy("font-src", FontSrc)); + } + + if (!string.IsNullOrEmpty(ImgSrc)) + { + policies.Add(new ConsentSecurityPolicy("img-src", ImgSrc)); + } + + if (!string.IsNullOrEmpty(StyleSrc)) + { + policies.Add(new ConsentSecurityPolicy("style-src", StyleSrc)); + } + + if (!string.IsNullOrEmpty(ScriptSrc)) + { + policies.Add(new ConsentSecurityPolicy("script-src", ScriptSrc)); + } + + if (FixWebsocket && AutoSelf) // Self does not match wss:// and ws:// :( + { + var request = context.HttpContext.Request; + + var url = string.Concat( + request.Scheme.Equals("http", StringComparison.OrdinalIgnoreCase) ? "ws" : "wss", + "://", + request.Host.ToUriComponent(), + request.PathBase.ToUriComponent()); + policies.Add(new ConsentSecurityPolicy("connect-src", url)); + } + + context.HttpContext.Response.OnStarting(() => + { + if (!policies.HasRules) + return Task.CompletedTask; + if (AutoSelf) + { + bool hasSelf = false; + foreach (var group in policies.Rules.GroupBy(p => p.Name)) + { + hasSelf = group.Any(g => g.Value.Contains("'self'", StringComparison.OrdinalIgnoreCase)); + if (!hasSelf && !group.Any(g => g.Value.Contains("'none'", StringComparison.OrdinalIgnoreCase) || + g.Value.Contains("*", StringComparison.OrdinalIgnoreCase))) + { + policies.Add(new ConsentSecurityPolicy(group.Key, "'self'")); + hasSelf = true; + } + if (hasSelf) + { + foreach (var authorized in policies.Authorized) + { + policies.Add(new ConsentSecurityPolicy(group.Key, authorized)); + } + } + } + } + context.HttpContext.Response.SetHeader("Content-Security-Policy", policies.ToString()); + return Task.CompletedTask; + }); + } + } + } +} diff --git a/BTCPayServer/Filters/ReferrerPolicyAttribute.cs b/BTCPayServer/Filters/ReferrerPolicyAttribute.cs new file mode 100644 index 000000000..829a68dc8 --- /dev/null +++ b/BTCPayServer/Filters/ReferrerPolicyAttribute.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace BTCPayServer.Filters +{ + public interface IReferrerPolicy : IFilterMetadata { } + public class ReferrerPolicyAttribute : Attribute, IActionFilter + { + public ReferrerPolicyAttribute(string value) + { + Value = value; + } + public string Value { get; set; } + public void OnActionExecuted(ActionExecutedContext context) + { + + } + + public void OnActionExecuting(ActionExecutingContext context) + { + if (context.IsEffectivePolicy(this)) + { + context.HttpContext.Response.SetHeaderOnStarting("Referrer-Policy", Value); + } + } + } +} diff --git a/BTCPayServer/Filters/XContentTypeOptionsAttribute.cs b/BTCPayServer/Filters/XContentTypeOptionsAttribute.cs new file mode 100644 index 000000000..4f29bcd53 --- /dev/null +++ b/BTCPayServer/Filters/XContentTypeOptionsAttribute.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace BTCPayServer.Filters +{ + public class XContentTypeOptionsAttribute : Attribute, IActionFilter + { + public XContentTypeOptionsAttribute(string value) + { + Value = value; + } + public void OnActionExecuted(ActionExecutedContext context) + { + } + + public string Value { get; set; } + public void OnActionExecuting(ActionExecutingContext context) + { + context.HttpContext.Response.SetHeaderOnStarting("X-Content-Type-Options", Value); + } + } +} diff --git a/BTCPayServer/Filters/XFrameOptionsAttribute.cs b/BTCPayServer/Filters/XFrameOptionsAttribute.cs index 99b2c2be6..509943c7f 100644 --- a/BTCPayServer/Filters/XFrameOptionsAttribute.cs +++ b/BTCPayServer/Filters/XFrameOptionsAttribute.cs @@ -23,11 +23,7 @@ namespace BTCPayServer.Filters public void OnActionExecuting(ActionExecutingContext context) { - var existing = context.HttpContext.Response.Headers["x-frame-options"].FirstOrDefault(); - if (existing != null && Value == null) - context.HttpContext.Response.Headers.Remove("x-frame-options"); - else - context.HttpContext.Response.Headers["x-frame-options"] = Value; + context.HttpContext.Response.SetHeaderOnStarting("X-Frame-Options", Value); } } } diff --git a/BTCPayServer/Filters/XXSSProtectionAttribute.cs b/BTCPayServer/Filters/XXSSProtectionAttribute.cs new file mode 100644 index 000000000..a929ba664 --- /dev/null +++ b/BTCPayServer/Filters/XXSSProtectionAttribute.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; + +namespace BTCPayServer.Filters +{ + public class XXSSProtectionAttribute : Attribute, IActionFilter + { + public void OnActionExecuted(ActionExecutedContext context) + { + } + + public void OnActionExecuting(ActionExecutingContext context) + { + context.HttpContext.Response.SetHeaderOnStarting("X-XSS-Protection", "1; mode=block"); + } + + } +} diff --git a/BTCPayServer/HostedServices/CssThemeManager.cs b/BTCPayServer/HostedServices/CssThemeManager.cs index b0e32673b..1e240c940 100644 --- a/BTCPayServer/HostedServices/CssThemeManager.cs +++ b/BTCPayServer/HostedServices/CssThemeManager.cs @@ -11,6 +11,8 @@ using NBXplorer.Models; using System.Collections.Concurrent; using BTCPayServer.Events; using BTCPayServer.Services; +using Microsoft.AspNetCore.Mvc.Filters; +using BTCPayServer.Security; namespace BTCPayServer.HostedServices { @@ -50,6 +52,33 @@ namespace BTCPayServer.HostedServices } } + public class ContentSecurityPolicyCssThemeManager : Attribute, IActionFilter, IOrderedFilter + { + public int Order => 1001; + + public void OnActionExecuted(ActionExecutedContext context) + { + + } + + public void OnActionExecuting(ActionExecutingContext context) + { + var manager = context.HttpContext.RequestServices.GetService(typeof(CssThemeManager)) as CssThemeManager; + var policies = context.HttpContext.RequestServices.GetService(typeof(ContentSecurityPolicies)) as ContentSecurityPolicies; + if (manager != null && policies != null) + { + if(manager.CreativeStartUri != null && Uri.TryCreate(manager.CreativeStartUri, UriKind.Absolute, out var uri)) + { + policies.Clear(); + } + if (manager.BootstrapUri != null && Uri.TryCreate(manager.BootstrapUri, UriKind.Absolute, out uri)) + { + policies.Clear(); + } + } + } + } + public class CssThemeManagerHostedService : BaseAsyncService { private SettingsRepository _SettingsRepository; diff --git a/BTCPayServer/HostedServices/InvoiceNotificationManager.cs b/BTCPayServer/HostedServices/InvoiceNotificationManager.cs index 8b713d973..b0444567c 100644 --- a/BTCPayServer/HostedServices/InvoiceNotificationManager.cs +++ b/BTCPayServer/HostedServices/InvoiceNotificationManager.cs @@ -309,7 +309,9 @@ namespace BTCPayServer.HostedServices { leases.Add(_EventAggregator.Subscribe(async e => { - var invoice = await _InvoiceRepository.GetInvoice(null, e.InvoiceId); + var invoice = await _InvoiceRepository.GetInvoice(null, e.Invoice.Id); + if (invoice == null) + return; List tasks = new List(); // Awaiting this later help make sure invoices should arrive in order diff --git a/BTCPayServer/HostedServices/InvoiceWatcher.cs b/BTCPayServer/HostedServices/InvoiceWatcher.cs index a6a2be0d0..ddddd8698 100644 --- a/BTCPayServer/HostedServices/InvoiceWatcher.cs +++ b/BTCPayServer/HostedServices/InvoiceWatcher.cs @@ -66,10 +66,10 @@ namespace BTCPayServer.HostedServices context.MarkDirty(); await _InvoiceRepository.UnaffectAddress(invoice.Id); - context.Events.Add(new InvoiceEvent(invoice, 1004, "invoice_expired")); + context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1004, "invoice_expired")); invoice.Status = "expired"; if(invoice.ExceptionStatus == "paidPartial") - context.Events.Add(new InvoiceEvent(invoice, 2000, "invoice_expiredPaidPartial")); + context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 2000, "invoice_expiredPaidPartial")); } var payments = invoice.GetPayments().Where(p => p.Accounted).ToArray(); @@ -84,7 +84,7 @@ namespace BTCPayServer.HostedServices { if (invoice.Status == "new") { - context.Events.Add(new InvoiceEvent(invoice, 1003, "invoice_paidInFull")); + context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1003, "invoice_paidInFull")); invoice.Status = "paid"; invoice.ExceptionStatus = accounting.Paid > accounting.TotalDue ? "paidOver" : null; await _InvoiceRepository.UnaffectAddress(invoice.Id); @@ -93,7 +93,7 @@ namespace BTCPayServer.HostedServices else if (invoice.Status == "expired" && invoice.ExceptionStatus != "paidLate") { invoice.ExceptionStatus = "paidLate"; - context.Events.Add(new InvoiceEvent(invoice, 1009, "invoice_paidAfterExpiration")); + context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1009, "invoice_paidAfterExpiration")); context.MarkDirty(); } } @@ -139,14 +139,14 @@ namespace BTCPayServer.HostedServices (confirmedAccounting.Paid < accounting.MinimumTotalDue)) { await _InvoiceRepository.UnaffectAddress(invoice.Id); - context.Events.Add(new InvoiceEvent(invoice, 1013, "invoice_failedToConfirm")); + context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1013, "invoice_failedToConfirm")); invoice.Status = "invalid"; context.MarkDirty(); } else if (confirmedAccounting.Paid >= accounting.MinimumTotalDue) { await _InvoiceRepository.UnaffectAddress(invoice.Id); - context.Events.Add(new InvoiceEvent(invoice, 1005, "invoice_confirmed")); + context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1005, "invoice_confirmed")); invoice.Status = "confirmed"; context.MarkDirty(); } @@ -157,7 +157,7 @@ namespace BTCPayServer.HostedServices var completedAccounting = paymentMethod.Calculate(p => p.GetCryptoPaymentData().PaymentCompleted(p, network)); if (completedAccounting.Paid >= accounting.MinimumTotalDue) { - context.Events.Add(new InvoiceEvent(invoice, 1006, "invoice_completed")); + context.Events.Add(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1006, "invoice_completed")); invoice.Status = "complete"; context.MarkDirty(); } @@ -249,13 +249,13 @@ namespace BTCPayServer.HostedServices { if (b.Name == "invoice_created") { - Watch(b.InvoiceId); - await Wait(b.InvoiceId); + Watch(b.Invoice.Id); + await Wait(b.Invoice.Id); } if (b.Name == "invoice_receivedPayment") { - Watch(b.InvoiceId); + Watch(b.Invoice.Id); } })); return Task.CompletedTask; diff --git a/BTCPayServer/HostedServices/MigratorHostedService.cs b/BTCPayServer/HostedServices/MigratorHostedService.cs new file mode 100644 index 000000000..d9fff577a --- /dev/null +++ b/BTCPayServer/HostedServices/MigratorHostedService.cs @@ -0,0 +1,89 @@ +using System; +using Microsoft.Extensions.Logging; +using Microsoft.EntityFrameworkCore; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BTCPayServer.Data; +using BTCPayServer.Services; +using BTCPayServer.Services.Stores; +using BTCPayServer.Logging; + +namespace BTCPayServer.HostedServices +{ + public class MigratorHostedService : BaseAsyncService + { + private ApplicationDbContextFactory _DBContextFactory; + private StoreRepository _StoreRepository; + private BTCPayNetworkProvider _NetworkProvider; + private SettingsRepository _Settings; + public MigratorHostedService( + BTCPayNetworkProvider networkProvider, + StoreRepository storeRepository, + ApplicationDbContextFactory dbContextFactory, + SettingsRepository settingsRepository) + { + _DBContextFactory = dbContextFactory; + _StoreRepository = storeRepository; + _NetworkProvider = networkProvider; + _Settings = settingsRepository; + } + internal override Task[] InitializeTasks() + { + return new[] + { + Update() + }; + } + + private async Task Update() + { + try + { + var settings = (await _Settings.GetSettingAsync()) ?? new MigrationSettings(); + if (!settings.DeprecatedLightningConnectionStringCheck) + { + await DeprecatedLightningConnectionStringCheck(); + settings.DeprecatedLightningConnectionStringCheck = true; + await _Settings.UpdateSetting(settings); + } + if (!settings.UnreachableStoreCheck) + { + await UnreachableStoreCheck(); + settings.UnreachableStoreCheck = true; + await _Settings.UpdateSetting(settings); + } + } + catch (Exception ex) + { + Logs.PayServer.LogError(ex, "Error on the MigratorHostedService"); + throw; + } + } + + private Task UnreachableStoreCheck() + { + return _StoreRepository.CleanUnreachableStores(); + } + + private async Task DeprecatedLightningConnectionStringCheck() + { + using (var ctx = _DBContextFactory.CreateContext()) + { + foreach (var store in await ctx.Stores.ToArrayAsync()) + { + foreach (var method in store.GetSupportedPaymentMethods(_NetworkProvider).OfType()) + { + var lightning = method.GetLightningUrl(); + if (lightning.IsLegacy) + { + method.SetLightningUrl(lightning); + store.SetSupportedPaymentMethod(method.PaymentId, method); + } + } + } + await ctx.SaveChangesAsync(); + } + } + } +} diff --git a/BTCPayServer/Hosting/BTCPayServerServices.cs b/BTCPayServer/Hosting/BTCPayServerServices.cs index 41509792b..4e18b55c1 100644 --- a/BTCPayServer/Hosting/BTCPayServerServices.cs +++ b/BTCPayServer/Hosting/BTCPayServerServices.cs @@ -39,6 +39,8 @@ using BTCPayServer.HostedServices; using Meziantou.AspNetCore.BundleTagHelpers; using System.Security.Claims; using BTCPayServer.Security; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using NBXplorer.DerivationStrategy; namespace BTCPayServer.Hosting { @@ -92,6 +94,7 @@ namespace BTCPayServer.Hosting return opts.NetworkProvider; }); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); @@ -104,7 +107,13 @@ namespace BTCPayServer.Hosting }); services.AddSingleton(); + services.Configure((o) => { + o.Filters.Add(new ContentSecurityPolicyCssThemeManager()); + o.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(WalletId))); + o.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(DerivationStrategyBase))); + }); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton, Payments.Bitcoin.BitcoinLikePaymentHandler>(); services.AddSingleton(); diff --git a/BTCPayServer/Hosting/Startup.cs b/BTCPayServer/Hosting/Startup.cs index b5107aa6f..320fcb6b0 100644 --- a/BTCPayServer/Hosting/Startup.cs +++ b/BTCPayServer/Hosting/Startup.cs @@ -39,6 +39,7 @@ using Microsoft.AspNetCore.Mvc.Cors.Internal; using Microsoft.AspNetCore.Server.Kestrel.Core; using System.Net; using Meziantou.AspNetCore.BundleTagHelpers; +using BTCPayServer.Security; namespace BTCPayServer.Hosting { @@ -79,8 +80,19 @@ namespace BTCPayServer.Hosting services.AddMvc(o => { o.Filters.Add(new XFrameOptionsAttribute("DENY")); + o.Filters.Add(new XContentTypeOptionsAttribute("nosniff")); + o.Filters.Add(new XXSSProtectionAttribute()); + o.Filters.Add(new ReferrerPolicyAttribute("same-origin")); + //o.Filters.Add(new ContentSecurityPolicyAttribute() + //{ + // FontSrc = "'self' https://fonts.gstatic.com/", + // ImgSrc = "'self' data:", + // DefaultSrc = "'none'", + // StyleSrc = "'self' 'unsafe-inline'", + // ScriptSrc = "'self' 'unsafe-inline'" + //}); }); - + services.TryAddScoped(); services.Configure(options => { options.Password.RequireDigit = false; diff --git a/BTCPayServer/Logging/InvoiceLog.cs b/BTCPayServer/Logging/InvoiceLog.cs new file mode 100644 index 000000000..f1e0e8af4 --- /dev/null +++ b/BTCPayServer/Logging/InvoiceLog.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace BTCPayServer.Logging +{ + public class InvoiceLog + { + public DateTimeOffset Timestamp { get; set; } + public string Log { get; set; } + + public override string ToString() + { + return $"{Timestamp.UtcDateTime}: {Log}"; + } + } + public class InvoiceLogs + { + List _InvoiceLogs = new List(); + public void Write(string data) + { + lock (_InvoiceLogs) + { + _InvoiceLogs.Add(new InvoiceLog() { Timestamp = DateTimeOffset.UtcNow, Log = data }); + } + } + + public List ToList() + { + lock (_InvoiceLogs) + { + return _InvoiceLogs.ToList(); + } + } + } +} diff --git a/BTCPayServer/Logging/Logs.cs b/BTCPayServer/Logging/Logs.cs index fa0df98fc..52977c5d6 100644 --- a/BTCPayServer/Logging/Logs.cs +++ b/BTCPayServer/Logging/Logs.cs @@ -15,9 +15,14 @@ namespace BTCPayServer.Logging } public static void Configure(ILoggerFactory factory) { - Configuration = factory.CreateLogger("Configuration"); - PayServer = factory.CreateLogger("PayServer"); - Events = factory.CreateLogger("Events"); + if (factory == null) + Configure(new FuncLoggerFactory(n => NullLogger.Instance)); + else + { + Configuration = factory.CreateLogger("Configuration"); + PayServer = factory.CreateLogger("PayServer"); + Events = factory.CreateLogger("Events"); + } } public static ILogger Configuration { diff --git a/BTCPayServer/Migrations/20180719095626_CanDeleteStores.Designer.cs b/BTCPayServer/Migrations/20180719095626_CanDeleteStores.Designer.cs new file mode 100644 index 000000000..d1bbcf7de --- /dev/null +++ b/BTCPayServer/Migrations/20180719095626_CanDeleteStores.Designer.cs @@ -0,0 +1,578 @@ +// +using System; +using BTCPayServer.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace BTCPayServer.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20180719095626_CanDeleteStores")] + partial class CanDeleteStores + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.1.0-rtm-30799"); + + modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b => + { + b.Property("Address") + .ValueGeneratedOnAdd(); + + b.Property("CreatedTime"); + + b.Property("InvoiceDataId"); + + b.HasKey("Address"); + + b.HasIndex("InvoiceDataId"); + + b.ToTable("AddressInvoices"); + }); + + modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasMaxLength(50); + + b.Property("StoreId") + .HasMaxLength(50); + + b.HasKey("Id"); + + b.HasIndex("StoreId"); + + b.ToTable("ApiKeys"); + }); + + modelBuilder.Entity("BTCPayServer.Data.AppData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AppType"); + + b.Property("Created"); + + b.Property("Name"); + + b.Property("Settings"); + + b.Property("StoreDataId"); + + b.HasKey("Id"); + + b.HasIndex("StoreDataId"); + + b.ToTable("Apps"); + }); + + modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b => + { + b.Property("InvoiceDataId"); + + b.Property("Address"); + + b.Property("Assigned"); + + b.Property("CryptoCode"); + + b.Property("UnAssigned"); + + b.HasKey("InvoiceDataId", "Address"); + + b.ToTable("HistoricalAddressInvoices"); + }); + + modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Blob"); + + b.Property("Created"); + + b.Property("CustomerEmail"); + + b.Property("ExceptionStatus"); + + b.Property("ItemCode"); + + b.Property("OrderId"); + + b.Property("Status"); + + b.Property("StoreDataId"); + + b.HasKey("Id"); + + b.HasIndex("StoreDataId"); + + b.ToTable("Invoices"); + }); + + modelBuilder.Entity("BTCPayServer.Data.InvoiceEventData", b => + { + b.Property("InvoiceDataId"); + + b.Property("UniqueId"); + + b.Property("Message"); + + b.Property("Timestamp"); + + b.HasKey("InvoiceDataId", "UniqueId"); + + b.ToTable("InvoiceEvents"); + }); + + modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Facade"); + + b.Property("Label"); + + b.Property("PairingTime"); + + b.Property("SIN"); + + b.Property("StoreDataId"); + + b.HasKey("Id"); + + b.HasIndex("SIN"); + + b.HasIndex("StoreDataId"); + + b.ToTable("PairedSINData"); + }); + + modelBuilder.Entity("BTCPayServer.Data.PairingCodeData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateCreated"); + + b.Property("Expiration"); + + b.Property("Facade"); + + b.Property("Label"); + + b.Property("SIN"); + + b.Property("StoreDataId"); + + b.Property("TokenValue"); + + b.HasKey("Id"); + + b.ToTable("PairingCodes"); + }); + + modelBuilder.Entity("BTCPayServer.Data.PaymentData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Accounted"); + + b.Property("Blob"); + + b.Property("InvoiceDataId"); + + b.HasKey("Id"); + + b.HasIndex("InvoiceDataId"); + + b.ToTable("Payments"); + }); + + modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b => + { + b.Property("Id"); + + b.HasKey("Id"); + + b.ToTable("PendingInvoices"); + }); + + modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Blob"); + + b.Property("InvoiceDataId"); + + b.HasKey("Id"); + + b.HasIndex("InvoiceDataId"); + + b.ToTable("RefundAddresses"); + }); + + modelBuilder.Entity("BTCPayServer.Data.SettingData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("BTCPayServer.Data.StoreData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DefaultCrypto"); + + b.Property("DerivationStrategies"); + + b.Property("DerivationStrategy"); + + b.Property("SpeedPolicy"); + + b.Property("StoreBlob"); + + b.Property("StoreCertificate"); + + b.Property("StoreName"); + + b.Property("StoreWebsite"); + + b.HasKey("Id"); + + b.ToTable("Stores"); + }); + + modelBuilder.Entity("BTCPayServer.Data.UserStore", b => + { + b.Property("ApplicationUserId"); + + b.Property("StoreDataId"); + + b.Property("Role"); + + b.HasKey("ApplicationUserId", "StoreDataId"); + + b.HasIndex("StoreDataId"); + + b.ToTable("UserStore"); + }); + + modelBuilder.Entity("BTCPayServer.Models.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("RequiresEmailConfirmation"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b => + { + b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData") + .WithMany("AddressInvoices") + .HasForeignKey("InvoiceDataId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b => + { + b.HasOne("BTCPayServer.Data.StoreData", "StoreData") + .WithMany("APIKeys") + .HasForeignKey("StoreId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("BTCPayServer.Data.AppData", b => + { + b.HasOne("BTCPayServer.Data.StoreData", "StoreData") + .WithMany("Apps") + .HasForeignKey("StoreDataId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b => + { + b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData") + .WithMany("HistoricalAddressInvoices") + .HasForeignKey("InvoiceDataId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b => + { + b.HasOne("BTCPayServer.Data.StoreData", "StoreData") + .WithMany("Invoices") + .HasForeignKey("StoreDataId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("BTCPayServer.Data.InvoiceEventData", b => + { + b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData") + .WithMany("Events") + .HasForeignKey("InvoiceDataId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b => + { + b.HasOne("BTCPayServer.Data.StoreData", "StoreData") + .WithMany("PairedSINs") + .HasForeignKey("StoreDataId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("BTCPayServer.Data.PaymentData", b => + { + b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData") + .WithMany("Payments") + .HasForeignKey("InvoiceDataId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b => + { + b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData") + .WithMany("PendingInvoices") + .HasForeignKey("Id") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b => + { + b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData") + .WithMany("RefundAddresses") + .HasForeignKey("InvoiceDataId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("BTCPayServer.Data.UserStore", b => + { + b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser") + .WithMany("UserStores") + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("BTCPayServer.Data.StoreData", "StoreData") + .WithMany("UserStores") + .HasForeignKey("StoreDataId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("BTCPayServer.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("BTCPayServer.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("BTCPayServer.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("BTCPayServer.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/BTCPayServer/Migrations/20180719095626_CanDeleteStores.cs b/BTCPayServer/Migrations/20180719095626_CanDeleteStores.cs new file mode 100644 index 000000000..f4dd95eff --- /dev/null +++ b/BTCPayServer/Migrations/20180719095626_CanDeleteStores.cs @@ -0,0 +1,172 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +namespace BTCPayServer.Migrations +{ + public partial class CanDeleteStores : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + if (this.SupportDropForeignKey(migrationBuilder.ActiveProvider)) + { + migrationBuilder.DropForeignKey( + name: "FK_AddressInvoices_Invoices_InvoiceDataId", + table: "AddressInvoices"); + + migrationBuilder.DropForeignKey( + name: "FK_Apps_Stores_StoreDataId", + table: "Apps"); + + migrationBuilder.DropForeignKey( + name: "FK_Invoices_Stores_StoreDataId", + table: "Invoices"); + + migrationBuilder.DropForeignKey( + name: "FK_Payments_Invoices_InvoiceDataId", + table: "Payments"); + + migrationBuilder.DropForeignKey( + name: "FK_RefundAddresses_Invoices_InvoiceDataId", + table: "RefundAddresses"); + + migrationBuilder.AddForeignKey( + name: "FK_AddressInvoices_Invoices_InvoiceDataId", + table: "AddressInvoices", + column: "InvoiceDataId", + principalTable: "Invoices", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_ApiKeys_Stores_StoreId", + table: "ApiKeys", + column: "StoreId", + principalTable: "Stores", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Apps_Stores_StoreDataId", + table: "Apps", + column: "StoreDataId", + principalTable: "Stores", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Invoices_Stores_StoreDataId", + table: "Invoices", + column: "StoreDataId", + principalTable: "Stores", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_PairedSINData_Stores_StoreDataId", + table: "PairedSINData", + column: "StoreDataId", + principalTable: "Stores", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_Payments_Invoices_InvoiceDataId", + table: "Payments", + column: "InvoiceDataId", + principalTable: "Invoices", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_PendingInvoices_Invoices_Id", + table: "PendingInvoices", + column: "Id", + principalTable: "Invoices", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + + migrationBuilder.AddForeignKey( + name: "FK_RefundAddresses_Invoices_InvoiceDataId", + table: "RefundAddresses", + column: "InvoiceDataId", + principalTable: "Invoices", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + } + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_AddressInvoices_Invoices_InvoiceDataId", + table: "AddressInvoices"); + + migrationBuilder.DropForeignKey( + name: "FK_ApiKeys_Stores_StoreId", + table: "ApiKeys"); + + migrationBuilder.DropForeignKey( + name: "FK_Apps_Stores_StoreDataId", + table: "Apps"); + + migrationBuilder.DropForeignKey( + name: "FK_Invoices_Stores_StoreDataId", + table: "Invoices"); + + migrationBuilder.DropForeignKey( + name: "FK_PairedSINData_Stores_StoreDataId", + table: "PairedSINData"); + + migrationBuilder.DropForeignKey( + name: "FK_Payments_Invoices_InvoiceDataId", + table: "Payments"); + + migrationBuilder.DropForeignKey( + name: "FK_PendingInvoices_Invoices_Id", + table: "PendingInvoices"); + + migrationBuilder.DropForeignKey( + name: "FK_RefundAddresses_Invoices_InvoiceDataId", + table: "RefundAddresses"); + + migrationBuilder.AddForeignKey( + name: "FK_AddressInvoices_Invoices_InvoiceDataId", + table: "AddressInvoices", + column: "InvoiceDataId", + principalTable: "Invoices", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_Apps_Stores_StoreDataId", + table: "Apps", + column: "StoreDataId", + principalTable: "Stores", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_Invoices_Stores_StoreDataId", + table: "Invoices", + column: "StoreDataId", + principalTable: "Stores", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_Payments_Invoices_InvoiceDataId", + table: "Payments", + column: "InvoiceDataId", + principalTable: "Invoices", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + + migrationBuilder.AddForeignKey( + name: "FK_RefundAddresses_Invoices_InvoiceDataId", + table: "RefundAddresses", + column: "InvoiceDataId", + principalTable: "Invoices", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + } + } +} diff --git a/BTCPayServer/Migrations/ApplicationDbContextModelSnapshot.cs b/BTCPayServer/Migrations/ApplicationDbContextModelSnapshot.cs index c31f3ff53..a0f5c96e5 100644 --- a/BTCPayServer/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/BTCPayServer/Migrations/ApplicationDbContextModelSnapshot.cs @@ -1,13 +1,9 @@ // +using System; using BTCPayServer.Data; -using BTCPayServer.Services.Invoices; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Migrations; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.EntityFrameworkCore.Storage.Internal; -using System; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; namespace BTCPayServer.Migrations { @@ -18,7 +14,7 @@ namespace BTCPayServer.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.0.2-rtm-10011"); + .HasAnnotation("ProductVersion", "2.1.0-rtm-30799"); modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b => { @@ -202,8 +198,7 @@ namespace BTCPayServer.Migrations modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b => { - b.Property("Id") - .ValueGeneratedOnAdd(); + b.Property("Id"); b.HasKey("Id"); @@ -442,19 +437,29 @@ namespace BTCPayServer.Migrations { b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData") .WithMany("AddressInvoices") - .HasForeignKey("InvoiceDataId"); + .HasForeignKey("InvoiceDataId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b => + { + b.HasOne("BTCPayServer.Data.StoreData", "StoreData") + .WithMany("APIKeys") + .HasForeignKey("StoreId") + .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity("BTCPayServer.Data.AppData", b => { b.HasOne("BTCPayServer.Data.StoreData", "StoreData") .WithMany("Apps") - .HasForeignKey("StoreDataId"); + .HasForeignKey("StoreDataId") + .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b => { - b.HasOne("BTCPayServer.Data.InvoiceData") + b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData") .WithMany("HistoricalAddressInvoices") .HasForeignKey("InvoiceDataId") .OnDelete(DeleteBehavior.Cascade); @@ -463,30 +468,49 @@ namespace BTCPayServer.Migrations modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b => { b.HasOne("BTCPayServer.Data.StoreData", "StoreData") - .WithMany() - .HasForeignKey("StoreDataId"); + .WithMany("Invoices") + .HasForeignKey("StoreDataId") + .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity("BTCPayServer.Data.InvoiceEventData", b => { - b.HasOne("BTCPayServer.Data.InvoiceData") + b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData") .WithMany("Events") .HasForeignKey("InvoiceDataId") .OnDelete(DeleteBehavior.Cascade); }); + modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b => + { + b.HasOne("BTCPayServer.Data.StoreData", "StoreData") + .WithMany("PairedSINs") + .HasForeignKey("StoreDataId") + .OnDelete(DeleteBehavior.Cascade); + }); + modelBuilder.Entity("BTCPayServer.Data.PaymentData", b => { b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData") .WithMany("Payments") - .HasForeignKey("InvoiceDataId"); + .HasForeignKey("InvoiceDataId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b => + { + b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData") + .WithMany("PendingInvoices") + .HasForeignKey("Id") + .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b => { b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData") .WithMany("RefundAddresses") - .HasForeignKey("InvoiceDataId"); + .HasForeignKey("InvoiceDataId") + .OnDelete(DeleteBehavior.Cascade); }); modelBuilder.Entity("BTCPayServer.Data.UserStore", b => diff --git a/BTCPayServer/ModelBinders/DerivationSchemeModelBinder.cs b/BTCPayServer/ModelBinders/DerivationSchemeModelBinder.cs new file mode 100644 index 000000000..2b47c1024 --- /dev/null +++ b/BTCPayServer/ModelBinders/DerivationSchemeModelBinder.cs @@ -0,0 +1,58 @@ +using Microsoft.AspNetCore.Mvc.ModelBinding; +using NBitcoin; +using System.Reflection; +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Internal; +using NBXplorer.DerivationStrategy; + +namespace BTCPayServer.ModelBinders +{ + public class DerivationSchemeModelBinder : IModelBinder + { + public DerivationSchemeModelBinder() + { + + } + + #region IModelBinder Members + + public Task BindModelAsync(ModelBindingContext bindingContext) + { + if (!typeof(DerivationStrategyBase).GetTypeInfo().IsAssignableFrom(bindingContext.ModelType)) + { + return Task.CompletedTask; + } + + ValueProviderResult val = bindingContext.ValueProvider.GetValue( + bindingContext.ModelName); + if (val == null) + { + return Task.CompletedTask; + } + + string key = val.FirstValue as string; + if (key == null) + { + return Task.CompletedTask; + } + + var networkProvider = (BTCPayNetworkProvider)bindingContext.HttpContext.RequestServices.GetService(typeof(BTCPayNetworkProvider)); + var cryptoCode = bindingContext.ValueProvider.GetValue("cryptoCode").FirstValue; + var network = networkProvider.GetNetwork(cryptoCode ?? "BTC"); + try + { + var data = new DerivationStrategyFactory(network.NBitcoinNetwork).Parse(key); + if (!bindingContext.ModelType.IsInstanceOfType(data)) + { + throw new FormatException("Invalid destination type"); + } + bindingContext.Result = ModelBindingResult.Success(data); + } + catch { throw new FormatException("Invalid derivation scheme"); } + return Task.CompletedTask; + } + + #endregion + } +} diff --git a/BTCPayServer/ModelBinders/WalletIdModelBinder.cs b/BTCPayServer/ModelBinders/WalletIdModelBinder.cs new file mode 100644 index 000000000..1bd3ddd32 --- /dev/null +++ b/BTCPayServer/ModelBinders/WalletIdModelBinder.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace BTCPayServer.ModelBinders +{ + public class WalletIdModelBinder : IModelBinder + { + public Task BindModelAsync(ModelBindingContext bindingContext) + { + if (!typeof(WalletId).GetTypeInfo().IsAssignableFrom(bindingContext.ModelType)) + { + return Task.CompletedTask; + } + + ValueProviderResult val = bindingContext.ValueProvider.GetValue( + bindingContext.ModelName); + if (val == null) + { + return Task.CompletedTask; + } + + string key = val.FirstValue as string; + if (key == null) + { + return Task.CompletedTask; + } + + if(WalletId.TryParse(key, out var walletId)) + { + bindingContext.Result = ModelBindingResult.Success(walletId); + } + return Task.CompletedTask; + } + } +} diff --git a/BTCPayServer/Models/InvoicingModels/PaymentModel.cs b/BTCPayServer/Models/InvoicingModels/PaymentModel.cs index da28a8975..78a13d50b 100644 --- a/BTCPayServer/Models/InvoicingModels/PaymentModel.cs +++ b/BTCPayServer/Models/InvoicingModels/PaymentModel.cs @@ -12,6 +12,9 @@ namespace BTCPayServer.Models.InvoicingModels public string PaymentMethodId { get; set; } public string CryptoImage { get; set; } public string Link { get; set; } + public string PaymentMethodName { get; set; } + public bool IsLightning { get; set; } + public string CryptoCode { get; set; } } public string HtmlTitle { get; set; } public string CustomCSSLink { get; set; } @@ -30,8 +33,7 @@ namespace BTCPayServer.Models.InvoicingModels public string Status { get; set; } public string MerchantRefLink { get; set; } public int MaxTimeSeconds { get; set; } - - // These properties are not used in client side code + public string StoreName { get; set; } public string ItemDesc { get; set; } public string TimeLeft { get; set; } @@ -45,12 +47,13 @@ namespace BTCPayServer.Models.InvoicingModels public string StoreEmail { get; set; } public string OrderId { get; set; } - public string CryptoImage { get; set; } public decimal NetworkFee { get; set; } public bool IsMultiCurrency { get; set; } - public int MaxTimeMinutes { get; internal set; } - public string PaymentType { get; internal set; } - public string PaymentMethodId { get; internal set; } + public int MaxTimeMinutes { get; set; } + public string PaymentType { get; set; } + public string PaymentMethodId { get; set; } + public string PaymentMethodName { get; set; } + public string CryptoImage { get; set; } public bool AllowCoinConversion { get; set; } public string PeerInfo { get; set; } diff --git a/BTCPayServer/Models/ServerViewModels/LNDGRPCServicesViewModel.cs b/BTCPayServer/Models/ServerViewModels/LNDGRPCServicesViewModel.cs new file mode 100644 index 000000000..01f78bd3b --- /dev/null +++ b/BTCPayServer/Models/ServerViewModels/LNDGRPCServicesViewModel.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace BTCPayServer.Models.ServerViewModels +{ + public class LNDGRPCServicesViewModel + { + public string Host { get; set; } + public bool SSL { get; set; } + public string Macaroon { get; set; } + public string CertificateThumbprint { get; set; } + public string QRCode { get; set; } + public string QRCodeLink { get; set; } + } +} diff --git a/BTCPayServer/Models/ServerViewModels/MaintenanceViewModel.cs b/BTCPayServer/Models/ServerViewModels/MaintenanceViewModel.cs new file mode 100644 index 000000000..e4393ac51 --- /dev/null +++ b/BTCPayServer/Models/ServerViewModels/MaintenanceViewModel.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Threading.Tasks; +using Renci.SshNet; + +namespace BTCPayServer.Models.ServerViewModels +{ + public class MaintenanceViewModel + { + [Required] + public string UserName { get; set; } + [Required] + [DataType(DataType.Password)] + public string Password { get; set; } + [Display(Name = "Change domain")] + public string DNSDomain { get; set; } + public SshClient CreateSSHClient(string host) + { + return new SshClient(host, UserName, Password); + } + } +} diff --git a/BTCPayServer/Models/ServerViewModels/ServicesViewModel.cs b/BTCPayServer/Models/ServerViewModels/ServicesViewModel.cs new file mode 100644 index 000000000..85a88e995 --- /dev/null +++ b/BTCPayServer/Models/ServerViewModels/ServicesViewModel.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace BTCPayServer.Models.ServerViewModels +{ + public class ServicesViewModel + { + public class LNDServiceViewModel + { + public string Crypto { get; set; } + public string Type { get; set; } + public int Index { get; set; } + } + public List LNDServices { get; set; } = new List(); + } +} diff --git a/BTCPayServer/Models/StoreViewModels/LightningNodeViewModel.cs b/BTCPayServer/Models/StoreViewModels/LightningNodeViewModel.cs index 1de9f6dc0..d9ba80caf 100644 --- a/BTCPayServer/Models/StoreViewModels/LightningNodeViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/LightningNodeViewModel.cs @@ -9,8 +9,8 @@ namespace BTCPayServer.Models.StoreViewModels { public class LightningNodeViewModel { - [Display(Name = "Lightning charge url")] - public string Url + [Display(Name = "Connection string")] + public string ConnectionString { get; set; diff --git a/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs b/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs index 1b86a07c2..7e676b6f8 100644 --- a/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/StoreViewModel.cs @@ -18,6 +18,7 @@ namespace BTCPayServer.Models.StoreViewModels { public string Crypto { get; set; } public string Value { get; set; } + public WalletId WalletId { get; set; } } public StoreViewModel() @@ -25,6 +26,7 @@ namespace BTCPayServer.Models.StoreViewModels } + public bool CanDelete { get; set; } public string Id { get; set; } [Display(Name = "Store Name")] [Required] diff --git a/BTCPayServer/Models/StoreViewModels/StoresViewModel.cs b/BTCPayServer/Models/StoreViewModels/StoresViewModel.cs index 5f05d61f9..709a701a9 100644 --- a/BTCPayServer/Models/StoreViewModels/StoresViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/StoresViewModel.cs @@ -34,10 +34,6 @@ namespace BTCPayServer.Models.StoreViewModels get; set; } - public string[] Balances - { - get; set; - } } } } diff --git a/BTCPayServer/Models/WalletViewModels/ListTransactionsViewModel.cs b/BTCPayServer/Models/WalletViewModels/ListTransactionsViewModel.cs new file mode 100644 index 000000000..2ef58bd80 --- /dev/null +++ b/BTCPayServer/Models/WalletViewModels/ListTransactionsViewModel.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace BTCPayServer.Models.WalletViewModels +{ + public class ListTransactionsViewModel + { + public class TransactionViewModel + { + public DateTimeOffset Timestamp { get; set; } + public string Id { get; set; } + public string Link { get; set; } + public bool Positive { get; set; } + public string Balance { get; set; } + } + public List Transactions { get; set; } = new List(); + } +} diff --git a/BTCPayServer/Models/WalletViewModels/ListWalletsViewModel.cs b/BTCPayServer/Models/WalletViewModels/ListWalletsViewModel.cs new file mode 100644 index 000000000..9219a4f4b --- /dev/null +++ b/BTCPayServer/Models/WalletViewModels/ListWalletsViewModel.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace BTCPayServer.Models.WalletViewModels +{ + public class ListWalletsViewModel + { + public class WalletViewModel + { + public string StoreName { get; set; } + public string StoreId { get; set; } + public string CryptoCode { get; set; } + public string Balance { get; set; } + public bool IsOwner { get; set; } + public WalletId Id { get; set; } + } + + public List Wallets { get; set; } = new List(); + } +} diff --git a/BTCPayServer/Models/StoreViewModels/WalletModel.cs b/BTCPayServer/Models/WalletViewModels/WalletModel.cs similarity index 62% rename from BTCPayServer/Models/StoreViewModels/WalletModel.cs rename to BTCPayServer/Models/WalletViewModels/WalletModel.cs index 8038c8429..e76d3fdd5 100644 --- a/BTCPayServer/Models/StoreViewModels/WalletModel.cs +++ b/BTCPayServer/Models/WalletViewModels/WalletModel.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Rendering; -namespace BTCPayServer.Models.StoreViewModels +namespace BTCPayServer.Models.WalletViewModels { public class WalletModel { @@ -15,5 +15,9 @@ namespace BTCPayServer.Models.StoreViewModels get; set; } + public decimal? Rate { get; set; } + public int Divisibility { get; set; } + public string Fiat { get; set; } + public string RateError { get; set; } } } diff --git a/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs b/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs index 4b0e6129d..f4471665c 100644 --- a/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs +++ b/BTCPayServer/Payments/Bitcoin/NBXplorerListener.cs @@ -161,7 +161,7 @@ namespace BTCPayServer.Payments.Bitcoin { var payment = await _InvoiceRepository.AddPayment(invoice.Id, DateTimeOffset.UtcNow, paymentData, network.CryptoCode); if(payment != null) - await ReceivedPayment(wallet, invoice.Id, payment, evt.DerivationStrategy); + await ReceivedPayment(wallet, invoice, payment, evt.DerivationStrategy); } else { @@ -207,6 +207,8 @@ namespace BTCPayServer.Payments.Bitcoin async Task UpdatePaymentStates(BTCPayWallet wallet, string invoiceId) { var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId, false); + if (invoice == null) + return null; List updatedPaymentEntities = new List(); var transactions = await wallet.GetTransactions(GetAllBitcoinPaymentData(invoice) .Select(p => p.Outpoint.Hash) @@ -315,6 +317,8 @@ namespace BTCPayServer.Payments.Bitcoin foreach (var invoiceId in invoices) { var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId, true); + if (invoice == null) + continue; var alreadyAccounted = GetAllBitcoinPaymentData(invoice).Select(p => p.Outpoint).ToHashSet(); var strategy = GetDerivationStrategy(invoice, network); if (strategy == null) @@ -332,8 +336,12 @@ namespace BTCPayServer.Payments.Bitcoin var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin.Timestamp, paymentData, network.CryptoCode).ConfigureAwait(false); alreadyAccounted.Add(coin.Coin.Outpoint); if (payment != null) - invoice = await ReceivedPayment(wallet, invoice.Id, payment, strategy); - totalPayment++; + { + invoice = await ReceivedPayment(wallet, invoice, payment, strategy); + if(invoice == null) + continue; + totalPayment++; + } } } return totalPayment; @@ -346,10 +354,12 @@ namespace BTCPayServer.Payments.Bitcoin .FirstOrDefault(); } - private async Task ReceivedPayment(BTCPayWallet wallet, string invoiceId, PaymentEntity payment, DerivationStrategyBase strategy) + private async Task ReceivedPayment(BTCPayWallet wallet, InvoiceEntity invoice, PaymentEntity payment, DerivationStrategyBase strategy) { var paymentData = (BitcoinLikePaymentData)payment.GetCryptoPaymentData(); - var invoice = (await UpdatePaymentStates(wallet, invoiceId)); + invoice = (await UpdatePaymentStates(wallet, invoice.Id)); + if (invoice == null) + return null; var paymentMethod = invoice.GetPaymentMethod(wallet.Network, PaymentTypes.BTCLike, _ExplorerClients.NetworkProviders); if (paymentMethod != null && paymentMethod.GetPaymentMethodDetails() is BitcoinLikeOnChainPaymentMethod btc && @@ -358,13 +368,13 @@ namespace BTCPayServer.Payments.Bitcoin { var address = await wallet.ReserveAddressAsync(strategy); btc.DepositAddress = address.ToString(); - await _InvoiceRepository.NewAddress(invoiceId, btc, wallet.Network); - _Aggregator.Publish(new InvoiceNewAddressEvent(invoiceId, address.ToString(), wallet.Network)); + await _InvoiceRepository.NewAddress(invoice.Id, btc, wallet.Network); + _Aggregator.Publish(new InvoiceNewAddressEvent(invoice.Id, address.ToString(), wallet.Network)); paymentMethod.SetPaymentMethodDetails(btc); invoice.SetPaymentMethod(paymentMethod); } wallet.InvalidateCache(strategy); - _Aggregator.Publish(new InvoiceEvent(invoiceId, 1002, "invoice_receivedPayment")); + _Aggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1002, "invoice_receivedPayment")); return invoice; } public Task StopAsync(CancellationToken cancellationToken) diff --git a/BTCPayServer/Payments/Lightning/LightningClientFactory.cs b/BTCPayServer/Payments/Lightning/LightningClientFactory.cs index 7cb26db7e..1b0e8d5eb 100644 --- a/BTCPayServer/Payments/Lightning/LightningClientFactory.cs +++ b/BTCPayServer/Payments/Lightning/LightningClientFactory.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Threading.Tasks; using BTCPayServer.Payments.Lightning.Charge; using BTCPayServer.Payments.Lightning.CLightning; +using NBitcoin; +using BTCPayServer.Payments.Lightning.Lnd; namespace BTCPayServer.Payments.Lightning { @@ -12,16 +14,38 @@ namespace BTCPayServer.Payments.Lightning public ILightningInvoiceClient CreateClient(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network) { var uri = supportedPaymentMethod.GetLightningUrl(); - if (uri.ConnectionType == LightningConnectionType.Charge) + return CreateClient(uri, network.NBitcoinNetwork); + } + + public static ILightningInvoiceClient CreateClient(LightningConnectionString connString, Network network) + { + if (connString.ConnectionType == LightningConnectionType.Charge) { - return new ChargeClient(uri.ToUri(true), network.NBitcoinNetwork); + return new ChargeClient(connString.ToUri(true), network); } - else if (uri.ConnectionType == LightningConnectionType.CLightning) + else if (connString.ConnectionType == LightningConnectionType.CLightning) { - return new CLightningRPCClient(uri.ToUri(false), network.NBitcoinNetwork); + return new CLightningRPCClient(connString.ToUri(false), network); + } + else if (connString.ConnectionType == LightningConnectionType.LndREST) + { + return new LndInvoiceClient(new LndSwaggerClient(new LndRestSettings(connString.BaseUri) + { + Macaroon = connString.Macaroon, + MacaroonFilePath = connString.MacaroonFilePath, + CertificateThumbprint = connString.CertificateThumbprint, + AllowInsecure = connString.AllowInsecure, + })); } else - throw new NotSupportedException($"Unsupported connection string for lightning server ({uri.ConnectionType})"); + throw new NotSupportedException($"Unsupported connection string for lightning server ({connString.ConnectionType})"); + } + + public static ILightningInvoiceClient CreateClient(string connectionString, Network network) + { + if (!Payments.Lightning.LightningConnectionString.TryParse(connectionString, false, out var conn, out string error)) + throw new FormatException($"Invalid format ({error})"); + return Payments.Lightning.LightningClientFactory.CreateClient(conn, network); } } } diff --git a/BTCPayServer/Payments/Lightning/LightningConnectionString.cs b/BTCPayServer/Payments/Lightning/LightningConnectionString.cs index fe7fc8a6a..cedb753dc 100644 --- a/BTCPayServer/Payments/Lightning/LightningConnectionString.cs +++ b/BTCPayServer/Payments/Lightning/LightningConnectionString.cs @@ -1,25 +1,298 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Text; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Primitives; +using NBitcoin.DataEncoders; namespace BTCPayServer.Payments.Lightning { public enum LightningConnectionType { Charge, - CLightning + CLightning, + LndREST, + LndGRPC } public class LightningConnectionString { - public static bool TryParse(string str, out LightningConnectionString connectionString) + static Dictionary typeMapping; + static Dictionary typeMappingReverse; + static LightningConnectionString() { - return TryParse(str, out connectionString, out var error); + typeMapping = new Dictionary(); + typeMapping.Add("clightning", LightningConnectionType.CLightning); + typeMapping.Add("charge", LightningConnectionType.Charge); + typeMapping.Add("lnd-rest", LightningConnectionType.LndREST); + typeMapping.Add("lnd-grpc", LightningConnectionType.LndGRPC); + typeMappingReverse = new Dictionary(); + foreach (var kv in typeMapping) + { + typeMappingReverse.Add(kv.Value, kv.Key); + } } - public static bool TryParse(string str, out LightningConnectionString connectionString, out string error) + public static bool TryParse(string str, bool supportLegacy, out LightningConnectionString connectionString) + { + return TryParse(str, supportLegacy, out connectionString, out var error); + } + public static bool TryParse(string str, bool supportLegacy, out LightningConnectionString connectionString, out string error) { if (str == null) throw new ArgumentNullException(nameof(str)); + + if (supportLegacy) + { + var parsed = TryParseLegacy(str, out connectionString, out error); + if (!parsed) + { + parsed = TryParseNewFormat(str, out connectionString, out error); + } + return parsed; + } + else + { + return TryParseNewFormat(str, out connectionString, out error); + } + } + + private static bool TryParseNewFormat(string str, out LightningConnectionString connectionString, out string error) + { + connectionString = null; + error = null; + var parts = str.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + Dictionary keyValues = new Dictionary(); + foreach (var part in parts.Select(p => p.Trim())) + { + var idx = part.IndexOf('=', StringComparison.OrdinalIgnoreCase); + if (idx == -1) + { + error = "The format of the connectionString should a list of key=value delimited by semicolon"; + return false; + } + var key = part.Substring(0, idx).Trim().ToLowerInvariant(); + var value = part.Substring(idx + 1).Trim(); + if (keyValues.ContainsKey(key)) + { + error = $"Duplicate key {key}"; + return false; + } + keyValues.Add(key, value); + } + + var possibleTypes = String.Join(", ", typeMapping.Select(k => k.Key).ToArray()); + + LightningConnectionString result = new LightningConnectionString(); + var type = Take(keyValues, "type"); + if (type == null) + { + error = $"The key 'type' is mandatory, possible values are {possibleTypes}"; + return false; + } + + if (!typeMapping.TryGetValue(type.ToLowerInvariant(), out var connectionType)) + { + error = $"The key 'type' is invalid, possible values are {possibleTypes}"; + return false; + } + + result.ConnectionType = connectionType; + + switch (connectionType) + { + case LightningConnectionType.Charge: + { + var server = Take(keyValues, "server"); + if (server == null) + { + error = $"The key 'server' is mandatory for charge connection strings"; + return false; + } + + if (!Uri.TryCreate(server, UriKind.Absolute, out var uri) + || (uri.Scheme != "http" && uri.Scheme != "https")) + { + error = $"The key 'server' should be an URI starting by http:// or https://"; + return false; + } + + parts = uri.UserInfo.Split(':'); + if (!string.IsNullOrEmpty(uri.UserInfo) && parts.Length == 2) + { + result.Username = parts[0]; + result.Password = parts[1]; + } + else + { + var apiToken = Take(keyValues, "api-token"); + if (apiToken == null) + { + error = "The key 'api-token' is not found"; + return false; + } + result.Username = "api-token"; + result.Password = apiToken; + } + result.BaseUri = new UriBuilder(uri) { UserName = "", Password = "" }.Uri; + } + break; + case LightningConnectionType.CLightning: + { + var server = Take(keyValues, "server"); + if (server == null) + { + error = $"The key 'server' is mandatory for charge connection strings"; + return false; + } + + if (server.StartsWith("//", StringComparison.OrdinalIgnoreCase)) + server = "unix:" + str; + else if (server.StartsWith("/", StringComparison.OrdinalIgnoreCase)) + server = "unix:/" + str; + + if (!Uri.TryCreate(server, UriKind.Absolute, out var uri) + || (uri.Scheme != "tcp" && uri.Scheme != "unix")) + { + error = $"The key 'server' should be an URI starting by tcp:// or unix:// or a path to the 'lightning-rpc' unix socket"; + return false; + } + result.BaseUri = uri; + } + break; + case LightningConnectionType.LndREST: + case LightningConnectionType.LndGRPC: + { + var server = Take(keyValues, "server"); + if (server == null) + { + error = $"The key 'server' is mandatory for lnd connection strings"; + return false; + } + if (!Uri.TryCreate(server, UriKind.Absolute, out var uri) + || (uri.Scheme != "http" && uri.Scheme != "https")) + { + error = $"The key 'server' should be an URI starting by http:// or https://"; + return false; + } + parts = uri.UserInfo.Split(':'); + if (!string.IsNullOrEmpty(uri.UserInfo) && parts.Length == 2) + { + result.Username = parts[0]; + result.Password = parts[1]; + } + result.BaseUri = new UriBuilder(uri) { UserName = "", Password = "" }.Uri; + + var macaroon = Take(keyValues, "macaroon"); + if (macaroon != null) + { + try + { + result.Macaroon = Encoder.DecodeData(macaroon); + } + catch + { + error = $"The key 'macaroon' format should be in hex"; + return false; + } + } + + var macaroonFilePath = Take(keyValues, "macaroonfilepath"); + if (macaroonFilePath != null) + { + if (macaroon != null) + { + error = $"The key 'macaroon' is already specified"; + return false; + } + if (!macaroonFilePath.EndsWith(".macaroon", StringComparison.OrdinalIgnoreCase)) + { + error = $"The key 'macaroonfilepath' should point to a .macaroon file"; + return false; + } + result.MacaroonFilePath = macaroonFilePath; + } + + string securitySet = null; + var certthumbprint = Take(keyValues, "certthumbprint"); + if (certthumbprint != null) + { + try + { + var bytes = Encoders.Hex.DecodeData(certthumbprint.Replace(":", string.Empty, StringComparison.OrdinalIgnoreCase)); + if (bytes.Length != 32) + { + error = $"The key 'certthumbprint' has invalid length: it should be the SHA256 of the PEM format of the certificate (32 bytes)"; + return false; + } + result.CertificateThumbprint = bytes; + } + catch + { + error = $"The key 'certthumbprint' has invalid format: it should be the SHA256 of the PEM format of the certificate"; + return false; + } + securitySet = "certthumbprint"; + } + + var allowinsecureStr = Take(keyValues, "allowinsecure"); + + if (allowinsecureStr != null) + { + var allowedValues = new[] { "true", "false" }; + if (!allowedValues.Any(v => v.Equals(allowinsecureStr, StringComparison.OrdinalIgnoreCase))) + { + error = $"The key 'allowinsecure' should be true or false"; + return false; + } + + bool allowInsecure = allowinsecureStr.Equals("true", StringComparison.OrdinalIgnoreCase); + if (securitySet != null && allowInsecure) + { + error = $"The key 'allowinsecure' conflict with '{securitySet}'"; + return false; + } + result.AllowInsecure = allowInsecure; + } + + if (!result.AllowInsecure && result.BaseUri.Scheme == "http") + { + error = $"The key 'allowinsecure' is false, but server's Uri is not using https"; + return false; + } + } + break; + default: + throw new NotSupportedException(connectionType.ToString()); + } + + if (keyValues.Count != 0) + { + error = $"Unknown keys ({String.Join(", ", keyValues.Select(k => k.Key).ToArray())})"; + return false; + } + + connectionString = result; + return true; + } + + public LightningConnectionString Clone() + { + LightningConnectionString.TryParse(this.ToString(), false, out var result); + return result; + } + + private static string Take(Dictionary keyValues, string key) + { + if (keyValues.TryGetValue(key, out var v)) + keyValues.Remove(key); + return v; + } + + private static bool TryParseLegacy(string str, out LightningConnectionString connectionString, out string error) + { if (str.StartsWith('/')) str = "unix:" + str; var result = new LightningConnectionString(); @@ -27,7 +300,7 @@ namespace BTCPayServer.Payments.Lightning error = null; Uri uri; - if (!System.Uri.TryCreate(str, UriKind.Absolute, out uri)) + if (!Uri.TryCreate(str, UriKind.Absolute, out uri)) { error = "Invalid URL"; return false; @@ -40,7 +313,6 @@ namespace BTCPayServer.Payments.Lightning error = $"The url support the following protocols {protocols}"; return false; } - if (uri.Scheme == "unix") { str = uri.AbsoluteUri.Substring("unix:".Length); @@ -49,8 +321,12 @@ namespace BTCPayServer.Payments.Lightning str = str.Substring(1); } uri = new Uri("unix://" + str, UriKind.Absolute); + result.ConnectionType = LightningConnectionType.CLightning; } + if (uri.Scheme == "tcp") + result.ConnectionType = LightningConnectionType.CLightning; + if (uri.Scheme == "http" || uri.Scheme == "https") { var parts = uri.UserInfo.Split(':'); @@ -61,6 +337,7 @@ namespace BTCPayServer.Payments.Lightning } result.Username = parts[0]; result.Password = parts[1]; + result.ConnectionType = LightningConnectionType.Charge; } else if (!string.IsNullOrEmpty(uri.UserInfo)) { @@ -68,6 +345,7 @@ namespace BTCPayServer.Payments.Lightning return false; } result.BaseUri = new UriBuilder(uri) { UserName = "", Password = "" }.Uri; + result.IsLegacy = true; connectionString = result; return true; } @@ -80,15 +358,17 @@ namespace BTCPayServer.Payments.Lightning public string Username { get; set; } public string Password { get; set; } public Uri BaseUri { get; set; } + public bool IsLegacy { get; private set; } public LightningConnectionType ConnectionType { - get - { - return BaseUri.Scheme == "http" || BaseUri.Scheme == "https" ? LightningConnectionType.Charge - : LightningConnectionType.CLightning; - } + get; + set; } + public byte[] Macaroon { get; set; } + public string MacaroonFilePath { get; set; } + public byte[] CertificateThumbprint { get; set; } + public bool AllowInsecure { get; set; } public Uri ToUri(bool withCredentials) { @@ -101,10 +381,58 @@ namespace BTCPayServer.Payments.Lightning return BaseUri; } } - + static NBitcoin.DataEncoders.DataEncoder Encoder = NBitcoin.DataEncoders.Encoders.Hex; public override string ToString() { - return ToUri(true).AbsoluteUri; + var type = typeMappingReverse[ConnectionType]; + StringBuilder builder = new StringBuilder(); + builder.Append($"type={type}"); + switch (ConnectionType) + { + case LightningConnectionType.Charge: + if (Username == null || Username == "api-token") + { + builder.Append($";server={BaseUri};api-token={Password}"); + } + else + { + builder.Append($";server={ToUri(true)}"); + } + break; + case LightningConnectionType.CLightning: + builder.Append($";server={BaseUri}"); + break; + case LightningConnectionType.LndREST: + case LightningConnectionType.LndGRPC: + if (Username == null) + { + builder.Append($";server={BaseUri}"); + } + else + { + builder.Append($";server={ToUri(true)}"); + } + if (Macaroon != null) + { + builder.Append($";macaroon={Encoder.EncodeData(Macaroon)}"); + } + if (MacaroonFilePath != null) + { + builder.Append($";macaroonfilepath={MacaroonFilePath}"); + } + if (CertificateThumbprint != null) + { + builder.Append($";certthumbprint={Encoders.Hex.EncodeData(CertificateThumbprint)}"); + } + if (AllowInsecure) + { + builder.Append($";allowinsecure=true"); + } + break; + default: + throw new NotSupportedException(type); + } + return builder.ToString(); } } } diff --git a/BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs b/BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs index 76fc5cb18..4b6c263fd 100644 --- a/BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs +++ b/BTCPayServer/Payments/Lightning/LightningLikePaymentHandler.cs @@ -15,6 +15,8 @@ namespace BTCPayServer.Payments.Lightning { public class LightningLikePaymentHandler : PaymentMethodHandlerBase { + public static int LIGHTNING_TIMEOUT = 5000; + NBXplorerDashboard _Dashboard; LightningClientFactory _LightningClientFactory; public LightningLikePaymentHandler( @@ -41,7 +43,7 @@ namespace BTCPayServer.Payments.Lightning description = description.Replace("{StoreName}", store.StoreName ?? "", StringComparison.OrdinalIgnoreCase) .Replace("{ItemDescription}", invoice.ProductInformation.ItemDesc ?? "", StringComparison.OrdinalIgnoreCase) .Replace("{OrderId}", invoice.OrderId ?? "", StringComparison.OrdinalIgnoreCase); - using (var cts = new CancellationTokenSource(5000)) + using (var cts = new CancellationTokenSource(LIGHTNING_TIMEOUT)) { try { @@ -70,7 +72,7 @@ namespace BTCPayServer.Payments.Lightning if (!_Dashboard.IsFullySynched(network.CryptoCode, out var summary)) throw new PaymentMethodUnavailableException($"Full node not available"); - using (var cts = new CancellationTokenSource(5000)) + using (var cts = new CancellationTokenSource(LIGHTNING_TIMEOUT)) { var client = _LightningClientFactory.CreateClient(supportedPaymentMethod, network); LightningNodeInformation info = null; @@ -121,7 +123,7 @@ namespace BTCPayServer.Payments.Lightning using (var tcp = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp)) { - await WithTimeout(tcp.ConnectAsync(new IPEndPoint(address, nodeInfo.Port)), cancellation); + await tcp.ConnectAsync(new IPEndPoint(address, nodeInfo.Port)).WithCancellation(cancellation); } } catch (Exception ex) @@ -129,15 +131,5 @@ namespace BTCPayServer.Payments.Lightning throw new PaymentMethodUnavailableException($"Error while connecting to the lightning node via {nodeInfo.Host}:{nodeInfo.Port} ({ex.Message})"); } } - - static Task WithTimeout(Task task, CancellationToken token) - { - TaskCompletionSource tcs = new TaskCompletionSource(); - var registration = token.Register(() => { try { tcs.TrySetResult(true); } catch { } }); -#pragma warning disable CA2008 // Do not create tasks without passing a TaskScheduler - var timeoutTask = tcs.Task; -#pragma warning restore CA2008 // Do not create tasks without passing a TaskScheduler - return Task.WhenAny(task, timeoutTask).Unwrap().ContinueWith(t => registration.Dispose(), TaskScheduler.Default); - } } } diff --git a/BTCPayServer/Payments/Lightning/LightningListener.cs b/BTCPayServer/Payments/Lightning/LightningListener.cs index 3db1e90f6..c47b2a329 100644 --- a/BTCPayServer/Payments/Lightning/LightningListener.cs +++ b/BTCPayServer/Payments/Lightning/LightningListener.cs @@ -46,7 +46,7 @@ namespace BTCPayServer.Payments.Lightning { if (inv.Name == "invoice_created") { - await EnsureListening(inv.InvoiceId, false); + await EnsureListening(inv.Invoice.Id, false); } })); @@ -143,11 +143,12 @@ namespace BTCPayServer.Payments.Lightning CancellationTokenSource _Cts = new CancellationTokenSource(); private async Task Listen(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network) { + ILightningListenInvoiceSession session = null; try { Logs.PayServer.LogInformation($"{supportedPaymentMethod.CryptoCode} (Lightning): Start listening {supportedPaymentMethod.GetLightningUrl().BaseUri}"); - var charge = _LightningClientFactory.CreateClient(supportedPaymentMethod, network); - var session = await charge.Listen(_Cts.Token); + var lightningClient = _LightningClientFactory.CreateClient(supportedPaymentMethod, network); + session = await lightningClient.Listen(_Cts.Token); while (true) { var notification = await session.WaitInvoice(_Cts.Token); @@ -179,6 +180,10 @@ namespace BTCPayServer.Payments.Lightning Logs.PayServer.LogError(ex, $"{supportedPaymentMethod.CryptoCode} (Lightning): Error while contacting {supportedPaymentMethod.GetLightningUrl().BaseUri}"); DoneListening(supportedPaymentMethod.GetLightningUrl()); } + finally + { + session?.Dispose(); + } Logs.PayServer.LogInformation($"{supportedPaymentMethod.CryptoCode} (Lightning): Stop listening {supportedPaymentMethod.GetLightningUrl().BaseUri}"); } @@ -189,8 +194,12 @@ namespace BTCPayServer.Payments.Lightning BOLT11 = notification.BOLT11, Amount = notification.Amount }, network.CryptoCode, accounted: true); - if(payment != null) - _Aggregator.Publish(new InvoiceEvent(listenedInvoice.InvoiceId, 1002, "invoice_receivedPayment")); + if (payment != null) + { + var invoice = await _InvoiceRepository.GetInvoice(null, listenedInvoice.InvoiceId); + if(invoice != null) + _Aggregator.Publish(new InvoiceEvent(invoice.EntityToDTO(_NetworkProvider), 1002, "invoice_receivedPayment")); + } } List _ListeningLightning = new List(); diff --git a/BTCPayServer/Payments/Lightning/LightningSupportedPaymentMethod.cs b/BTCPayServer/Payments/Lightning/LightningSupportedPaymentMethod.cs index e1b100e9e..cb6e41358 100644 --- a/BTCPayServer/Payments/Lightning/LightningSupportedPaymentMethod.cs +++ b/BTCPayServer/Payments/Lightning/LightningSupportedPaymentMethod.cs @@ -8,18 +8,41 @@ namespace BTCPayServer.Payments.Lightning public class LightningSupportedPaymentMethod : ISupportedPaymentMethod { public string CryptoCode { get; set; } + + [Obsolete("Use Get/SetLightningUrl")] + public string Username { get; set; } + [Obsolete("Use Get/SetLightningUrl")] + public string Password { get; set; } + + // This property MUST be after CryptoCode or else JSON serialization fails + public PaymentMethodId PaymentId => new PaymentMethodId(CryptoCode, PaymentTypes.LightningLike); + [Obsolete("Use Get/SetLightningUrl")] public string LightningChargeUrl { get; set; } + [Obsolete("Use Get/SetLightningUrl")] + public string LightningConnectionString { get; set; } + public LightningConnectionString GetLightningUrl() { #pragma warning disable CS0618 // Type or member is obsolete - var fullUri = new UriBuilder(LightningChargeUrl) { UserName = Username, Password = Password }.Uri.AbsoluteUri; - if(!LightningConnectionString.TryParse(fullUri, out var connectionString, out var error)) + if (!string.IsNullOrEmpty(LightningConnectionString)) { - throw new FormatException(error); + if (!BTCPayServer.Payments.Lightning.LightningConnectionString.TryParse(LightningConnectionString, false, out var connectionString, out var error)) + { + throw new FormatException(error); + } + return connectionString; + } + else + { + var fullUri = new UriBuilder(LightningChargeUrl) { UserName = Username, Password = Password }.Uri.AbsoluteUri; + if (!BTCPayServer.Payments.Lightning.LightningConnectionString.TryParse(fullUri, true, out var connectionString, out var error)) + { + throw new FormatException(error); + } + return connectionString; } - return connectionString; #pragma warning restore CS0618 // Type or member is obsolete } @@ -29,16 +52,11 @@ namespace BTCPayServer.Payments.Lightning throw new ArgumentNullException(nameof(connectionString)); #pragma warning disable CS0618 // Type or member is obsolete - Username = connectionString.Username; - Password = connectionString.Password; - LightningChargeUrl = connectionString.BaseUri.AbsoluteUri; + LightningConnectionString = connectionString.ToString(); + Username = null; + Password = null; + LightningChargeUrl = null; #pragma warning restore CS0618 // Type or member is obsolete } - - [Obsolete("Use Get/SetLightningUrl")] - public string Username { get; set; } - [Obsolete("Use Get/SetLightningUrl")] - public string Password { get; set; } - public PaymentMethodId PaymentId => new PaymentMethodId(CryptoCode, PaymentTypes.LightningLike); } } diff --git a/BTCPayServer/Payments/Lightning/Lnd/LndAuthentication.cs b/BTCPayServer/Payments/Lightning/Lnd/LndAuthentication.cs new file mode 100644 index 000000000..5f4c4cc93 --- /dev/null +++ b/BTCPayServer/Payments/Lightning/Lnd/LndAuthentication.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Threading.Tasks; +using NBitcoin.DataEncoders; + +namespace BTCPayServer.Payments.Lightning.Lnd +{ + public abstract class LndAuthentication + { + public class FixedMacaroonAuthentication : LndAuthentication + { + public FixedMacaroonAuthentication(byte[] macaroon) + { + if (macaroon == null) + throw new ArgumentNullException(nameof(macaroon)); + Macaroon = macaroon; + } + public byte[] Macaroon { get; set; } + public override void AddAuthentication(HttpRequestMessage httpRequest) + { + httpRequest.Headers.Add("Grpc-Metadata-macaroon", Encoders.Hex.EncodeData(Macaroon)); + } + } + public class NullAuthentication : LndAuthentication + { + public static NullAuthentication Instance { get; } = new NullAuthentication(); + + private NullAuthentication() + { + + } + public override void AddAuthentication(HttpRequestMessage httpRequest) + { + } + } + + public class MacaroonFileAuthentication : LndAuthentication + { + public MacaroonFileAuthentication(string filePath) + { + if (filePath == null) + throw new ArgumentNullException(nameof(filePath)); + // Because this dump the whole file, let's make sure it is indeed the macaroon + if (!filePath.EndsWith(".macaroon", StringComparison.OrdinalIgnoreCase)) + throw new ArgumentException(message: "filePath is not a macaroon file", paramName: nameof(filePath)); + FilePath = filePath; + } + public string FilePath { get; set; } + public override void AddAuthentication(HttpRequestMessage httpRequest) + { + try + { + var bytes = File.ReadAllBytes(FilePath); + httpRequest.Headers.Add("Grpc-Metadata-macaroon", Encoders.Hex.EncodeData(bytes)); + } + catch + { + } + } + } + + public abstract void AddAuthentication(HttpRequestMessage httpRequest); + } +} diff --git a/BTCPayServer/Payments/Lightning/Lnd/LndInvoiceClient.cs b/BTCPayServer/Payments/Lightning/Lnd/LndInvoiceClient.cs new file mode 100644 index 000000000..61d3a9c60 --- /dev/null +++ b/BTCPayServer/Payments/Lightning/Lnd/LndInvoiceClient.cs @@ -0,0 +1,273 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Net.Security; +using System.Runtime.ExceptionServices; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading; +using System.Threading.Channels; +using System.Threading.Tasks; +using NBitcoin; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace BTCPayServer.Payments.Lightning.Lnd +{ + public class LndInvoiceClient : ILightningInvoiceClient + { + class LndInvoiceClientSession : ILightningListenInvoiceSession + { + private LndSwaggerClient _Parent; + Channel _Invoices = Channel.CreateBounded(50); + CancellationTokenSource _Cts = new CancellationTokenSource(); + + + HttpClient _Client; + HttpResponseMessage _Response; + Stream _Body; + StreamReader _Reader; + Task _ListenLoop; + + public LndInvoiceClientSession(LndSwaggerClient parent) + { + _Parent = parent; + } + + public Task StartListening() + { + try + { + _Client = _Parent.CreateHttpClient(); + _Client.Timeout = TimeSpan.FromMilliseconds(Timeout.Infinite); + var request = new HttpRequestMessage(HttpMethod.Get, _Parent.BaseUrl.WithTrailingSlash() + "v1/invoices/subscribe"); + _Parent._Authentication.AddAuthentication(request); + _ListenLoop = ListenLoop(request); + } + catch + { + Dispose(); + } + return Task.CompletedTask; + } + + private async Task ListenLoop(HttpRequestMessage request) + { + try + { + _Response = await _Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, _Cts.Token); + _Body = await _Response.Content.ReadAsStreamAsync(); + _Reader = new StreamReader(_Body); + while (!_Cts.IsCancellationRequested) + { + string line = await _Reader.ReadLineAsync().WithCancellation(_Cts.Token); + if (line != null) + { + if (line.StartsWith("{\"result\":", StringComparison.OrdinalIgnoreCase)) + { + var invoiceString = JObject.Parse(line)["result"].ToString(); + LnrpcInvoice parsedInvoice = _Parent.Deserialize(invoiceString); + await _Invoices.Writer.WriteAsync(ConvertLndInvoice(parsedInvoice), _Cts.Token); + } + else if (line.StartsWith("{\"error\":", StringComparison.OrdinalIgnoreCase)) + { + var errorString = JObject.Parse(line)["error"].ToString(); + var error = _Parent.Deserialize(errorString); + throw new LndException(error); + } + else + { + throw new LndException("Unknown result from LND: " + line); + } + } + } + } + catch when (_Cts.IsCancellationRequested) + { + + } + catch (Exception ex) + { + _Invoices.Writer.TryComplete(ex); + } + finally + { + Dispose(false); + } + } + public async Task WaitInvoice(CancellationToken cancellation) + { + try + { + return await _Invoices.Reader.ReadAsync(cancellation); + } + catch (ChannelClosedException ex) when (ex.InnerException == null) + { + throw new TaskCanceledException(); + } + catch (ChannelClosedException ex) + { + ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); + throw; + } + } + + public void Dispose() + { + Dispose(true); + } + void Dispose(bool waitLoop) + { + if (_Cts.IsCancellationRequested) + return; + _Cts.Cancel(); + _Reader?.Dispose(); + _Reader = null; + _Body?.Dispose(); + _Body = null; + _Response?.Dispose(); + _Response = null; + _Client?.Dispose(); + _Client = null; + if (waitLoop) + _ListenLoop?.Wait(); + _Invoices.Writer.TryComplete(); + } + } + + + public LndSwaggerClient _rpcClient; + + public LndInvoiceClient(LndSwaggerClient swaggerClient) + { + if (swaggerClient == null) + throw new ArgumentNullException(nameof(swaggerClient)); + _rpcClient = swaggerClient; + } + + public async Task CreateInvoice(LightMoney amount, string description, TimeSpan expiry, + CancellationToken cancellation = default(CancellationToken)) + { + var strAmount = ConvertInv.ToString(amount.ToUnit(LightMoneyUnit.Satoshi)); + var strExpiry = ConvertInv.ToString(Math.Round(expiry.TotalSeconds, 0)); + // lnd requires numbers sent as strings. don't ask + var resp = await _rpcClient.AddInvoiceAsync(new LnrpcInvoice + { + Value = strAmount, + Memo = description, + Expiry = strExpiry + }); + + var invoice = new LightningInvoice + { + Id = BitString(resp.R_hash), + Amount = amount, + BOLT11 = resp.Payment_request, + Status = "unpaid" + }; + return invoice; + } + + public async Task GetInfo(CancellationToken cancellation = default(CancellationToken)) + { + var resp = await _rpcClient.GetInfoAsync(cancellation); + + var nodeInfo = new LightningNodeInformation + { + BlockHeight = (int?)resp.Block_height ?? 0, + NodeId = resp.Identity_pubkey + }; + + + var node = await _rpcClient.GetNodeInfoAsync(resp.Identity_pubkey, cancellation); + if (node.Node.Addresses == null || node.Node.Addresses.Count == 0) + throw new Exception("Lnd External IP not set, make sure you use --externalip=$EXTERNALIP parameter on lnd"); + + var firstNodeInfo = node.Node.Addresses.First(); + var externalHostPort = firstNodeInfo.Addr.Split(':'); + + nodeInfo.Address = externalHostPort[0]; + nodeInfo.P2PPort = ConvertInv.ToInt32(externalHostPort[1]); + + return nodeInfo; + } + + public async Task GetInvoice(string invoiceId, CancellationToken cancellation = default(CancellationToken)) + { + var resp = await _rpcClient.LookupInvoiceAsync(invoiceId, null, cancellation); + return ConvertLndInvoice(resp); + } + + public async Task Listen(CancellationToken cancellation = default(CancellationToken)) + { + var session = new LndInvoiceClientSession(this._rpcClient); + await session.StartListening(); + return session; + } + + internal static LightningInvoice ConvertLndInvoice(LnrpcInvoice resp) + { + var invoice = new LightningInvoice + { + // TODO: Verify id corresponds to R_hash + Id = BitString(resp.R_hash), + Amount = new LightMoney(ConvertInv.ToInt64(resp.Value), LightMoneyUnit.Satoshi), + BOLT11 = resp.Payment_request, + Status = "unpaid" + }; + + if (resp.Settle_date != null) + { + invoice.PaidAt = DateTimeOffset.FromUnixTimeSeconds(ConvertInv.ToInt64(resp.Settle_date)); + invoice.Status = "paid"; + } + else + { + var invoiceExpiry = ConvertInv.ToInt64(resp.Creation_date) + ConvertInv.ToInt64(resp.Expiry); + if (DateTimeOffset.FromUnixTimeSeconds(invoiceExpiry) > DateTimeOffset.UtcNow) + { + invoice.Status = "expired"; + } + } + return invoice; + } + + + // utility static methods... maybe move to separate class + private static string BitString(byte[] bytes) + { + return BitConverter.ToString(bytes) + .Replace("-", "", StringComparison.InvariantCulture) + .ToLower(CultureInfo.InvariantCulture); + } + + // Invariant culture conversion + public static class ConvertInv + { + public static int ToInt32(string str) + { + return Convert.ToInt32(str, CultureInfo.InvariantCulture.NumberFormat); + } + + public static long ToInt64(string str) + { + return Convert.ToInt64(str, CultureInfo.InvariantCulture.NumberFormat); + } + + public static string ToString(decimal d) + { + return d.ToString(CultureInfo.InvariantCulture); + } + + public static string ToString(double d) + { + return d.ToString(CultureInfo.InvariantCulture); + } + } + } +} diff --git a/BTCPayServer/Payments/Lightning/Lnd/LndRestSettings.cs b/BTCPayServer/Payments/Lightning/Lnd/LndRestSettings.cs new file mode 100644 index 000000000..a9250517e --- /dev/null +++ b/BTCPayServer/Payments/Lightning/Lnd/LndRestSettings.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace BTCPayServer.Payments.Lightning.Lnd +{ + public class LndRestSettings + { + public LndRestSettings() + { + + } + public LndRestSettings(Uri uri) + { + Uri = uri; + } + public Uri Uri { get; set; } + /// + /// The SHA256 of the PEM certificate + /// + public byte[] CertificateThumbprint { get; set; } + public byte[] Macaroon { get; set; } + public bool AllowInsecure { get; set; } + public string MacaroonFilePath { get; set; } + + public LndAuthentication CreateLndAuthentication() + { + if (Macaroon != null) + return new LndAuthentication.FixedMacaroonAuthentication(Macaroon); + if (!string.IsNullOrEmpty(MacaroonFilePath)) + return new LndAuthentication.MacaroonFileAuthentication(MacaroonFilePath); + return LndAuthentication.NullAuthentication.Instance; + } + } +} diff --git a/BTCPayServer/Payments/Lightning/Lnd/LndSwaggerClient.cs b/BTCPayServer/Payments/Lightning/Lnd/LndSwaggerClient.cs new file mode 100644 index 000000000..d74af7070 --- /dev/null +++ b/BTCPayServer/Payments/Lightning/Lnd/LndSwaggerClient.cs @@ -0,0 +1,8713 @@ +//---------------------- +// +// Generated using the NSwag toolchain v11.11.1.0 (NJsonSchema v9.9.11.0 (Newtonsoft.Json v9.0.0.0)) (http://NSwag.org) +// +//---------------------- + +using System.Diagnostics; + +namespace BTCPayServer.Payments.Lightning.Lnd +{ + #pragma warning disable // Disable all warnings + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "11.11.1.0")] + partial class LndSwaggerClient + { + private System.Lazy _settings; + private string _baseUrl = ""; + + private System.Net.Http.HttpClient _httpClient; + + public string BaseUrl + { + get { return _baseUrl; } + set { _baseUrl = value; } + } + + partial void UpdateJsonSerializerSettings(Newtonsoft.Json.JsonSerializerSettings settings); + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url); + partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder); + partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response); + + /// * lncli: `walletbalance` + /// WalletBalance returns total unspent outputs(confirmed and unconfirmed), all + /// confirmed unspent outputs and all unconfirmed unspent outputs under control + /// of the wallet. + /// A server side error occurred. + public System.Threading.Tasks.Task WalletBalanceAsync() + { + return WalletBalanceAsync(System.Threading.CancellationToken.None); + } + + /// * lncli: `walletbalance` + /// WalletBalance returns total unspent outputs(confirmed and unconfirmed), all + /// confirmed unspent outputs and all unconfirmed unspent outputs under control + /// of the wallet. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task WalletBalanceAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/balance/blockchain"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcWalletBalanceResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcWalletBalanceResponse); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `channelbalance` + /// ChannelBalance returns the total funds available across all open channels + /// in satoshis. + /// A server side error occurred. + public System.Threading.Tasks.Task ChannelBalanceAsync() + { + return ChannelBalanceAsync(System.Threading.CancellationToken.None); + } + + /// * lncli: `channelbalance` + /// ChannelBalance returns the total funds available across all open channels + /// in satoshis. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task ChannelBalanceAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/balance/channels"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcChannelBalanceResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcChannelBalanceResponse); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `listchannels` + /// ListChannels returns a description of all the open channels that this node + /// is a participant in. + /// A server side error occurred. + public System.Threading.Tasks.Task ListChannelsAsync(bool? active_only, bool? inactive_only, bool? public_only, bool? private_only) + { + return ListChannelsAsync(active_only, inactive_only, public_only, private_only, System.Threading.CancellationToken.None); + } + + /// * lncli: `listchannels` + /// ListChannels returns a description of all the open channels that this node + /// is a participant in. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task ListChannelsAsync(bool? active_only, bool? inactive_only, bool? public_only, bool? private_only, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/channels?"); + if (active_only != null) urlBuilder_.Append("active_only=").Append(System.Uri.EscapeDataString(System.Convert.ToString(active_only.Value, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + if (inactive_only != null) urlBuilder_.Append("inactive_only=").Append(System.Uri.EscapeDataString(System.Convert.ToString(inactive_only.Value, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + if (public_only != null) urlBuilder_.Append("public_only=").Append(System.Uri.EscapeDataString(System.Convert.ToString(public_only.Value, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + if (private_only != null) urlBuilder_.Append("private_only=").Append(System.Uri.EscapeDataString(System.Convert.ToString(private_only.Value, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + urlBuilder_.Length--; + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcListChannelsResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcListChannelsResponse); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * + /// OpenChannelSync is a synchronous version of the OpenChannel RPC call. This + /// call is meant to be consumed by clients to the REST proxy. As with all + /// other sync calls, all byte slices are intended to be populated as hex + /// encoded strings. + /// A server side error occurred. + public System.Threading.Tasks.Task OpenChannelSyncAsync(LnrpcOpenChannelRequest body) + { + return OpenChannelSyncAsync(body, System.Threading.CancellationToken.None); + } + + /// * + /// OpenChannelSync is a synchronous version of the OpenChannel RPC call. This + /// call is meant to be consumed by clients to the REST proxy. As with all + /// other sync calls, all byte slices are intended to be populated as hex + /// encoded strings. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task OpenChannelSyncAsync(LnrpcOpenChannelRequest body, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/channels"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType.MediaType = "application/json"; + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcChannelPoint); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcChannelPoint); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `pendingchannels` + /// PendingChannels returns a list of all the channels that are currently + /// considered "pending". A channel is pending if it has finished the funding + /// workflow and is waiting for confirmations for the funding txn, or is in the + /// process of closure, either initiated cooperatively or non-cooperatively. + /// A server side error occurred. + public System.Threading.Tasks.Task PendingChannelsAsync() + { + return PendingChannelsAsync(System.Threading.CancellationToken.None); + } + + /// * lncli: `pendingchannels` + /// PendingChannels returns a list of all the channels that are currently + /// considered "pending". A channel is pending if it has finished the funding + /// workflow and is waiting for confirmations for the funding txn, or is in the + /// process of closure, either initiated cooperatively or non-cooperatively. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task PendingChannelsAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/channels/pending"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcPendingChannelsResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcPendingChannelsResponse); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * + /// SendPaymentSync is the synchronous non-streaming version of SendPayment. + /// This RPC is intended to be consumed by clients of the REST proxy. + /// Additionally, this RPC expects the destination's public key and the payment + /// hash (if any) to be encoded as hex strings. + /// A server side error occurred. + public System.Threading.Tasks.Task SendPaymentSyncAsync(LnrpcSendRequest body) + { + return SendPaymentSyncAsync(body, System.Threading.CancellationToken.None); + } + + /// * + /// SendPaymentSync is the synchronous non-streaming version of SendPayment. + /// This RPC is intended to be consumed by clients of the REST proxy. + /// Additionally, this RPC expects the destination's public key and the payment + /// hash (if any) to be encoded as hex strings. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task SendPaymentSyncAsync(LnrpcSendRequest body, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/channels/transactions"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType.MediaType = "application/json"; + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcSendResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcSendResponse); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `closechannel` + /// CloseChannel attempts to close an active channel identified by its channel + /// outpoint (ChannelPoint). The actions of this method can additionally be + /// augmented to attempt a force close after a timeout period in the case of an + /// inactive peer. If a non-force close (cooperative closure) is requested, + /// then the user can specify either a target number of blocks until the + /// closure transaction is confirmed, or a manual fee rate. If neither are + /// specified, then a default lax, block confirmation target is used. + /// (streaming responses) + /// A server side error occurred. + public System.Threading.Tasks.Task CloseChannelAsync(string channel_point_funding_txid_str, long channel_point_output_index) + { + return CloseChannelAsync(channel_point_funding_txid_str, channel_point_output_index, System.Threading.CancellationToken.None); + } + + /// * lncli: `closechannel` + /// CloseChannel attempts to close an active channel identified by its channel + /// outpoint (ChannelPoint). The actions of this method can additionally be + /// augmented to attempt a force close after a timeout period in the case of an + /// inactive peer. If a non-force close (cooperative closure) is requested, + /// then the user can specify either a target number of blocks until the + /// closure transaction is confirmed, or a manual fee rate. If neither are + /// specified, then a default lax, block confirmation target is used. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// (streaming responses) + /// A server side error occurred. + public async System.Threading.Tasks.Task CloseChannelAsync(string channel_point_funding_txid_str, long channel_point_output_index, System.Threading.CancellationToken cancellationToken) + { + if (channel_point_funding_txid_str == null) + throw new System.ArgumentNullException("channel_point_funding_txid_str"); + + if (channel_point_output_index == null) + throw new System.ArgumentNullException("channel_point_output_index"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/channels/{channel_point.funding_txid_str}/{channel_point.output_index}"); + urlBuilder_.Replace("{channel_point.funding_txid_str}", System.Uri.EscapeDataString(System.Convert.ToString(channel_point_funding_txid_str, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Replace("{channel_point.output_index}", System.Uri.EscapeDataString(System.Convert.ToString(channel_point_output_index, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("DELETE"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcCloseStatusUpdate); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcCloseStatusUpdate); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `updatechanpolicy` + /// UpdateChannelPolicy allows the caller to update the fee schedule and + /// channel policies for all channels globally, or a particular channel. + /// A server side error occurred. + public System.Threading.Tasks.Task UpdateChannelPolicyAsync(LnrpcPolicyUpdateRequest body) + { + return UpdateChannelPolicyAsync(body, System.Threading.CancellationToken.None); + } + + /// * lncli: `updatechanpolicy` + /// UpdateChannelPolicy allows the caller to update the fee schedule and + /// channel policies for all channels globally, or a particular channel. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task UpdateChannelPolicyAsync(LnrpcPolicyUpdateRequest body, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/chanpolicy"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType.MediaType = "application/json"; + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(object); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(object); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `feereport` + /// FeeReport allows the caller to obtain a report detailing the current fee + /// schedule enforced by the node globally for each channel. + /// A server side error occurred. + public System.Threading.Tasks.Task FeeReportAsync() + { + return FeeReportAsync(System.Threading.CancellationToken.None); + } + + /// * lncli: `feereport` + /// FeeReport allows the caller to obtain a report detailing the current fee + /// schedule enforced by the node globally for each channel. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task FeeReportAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/fees"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcFeeReportResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcFeeReportResponse); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * + /// GenSeed is the first method that should be used to instantiate a new lnd + /// instance. This method allows a caller to generate a new aezeed cipher seed + /// given an optional passphrase. If provided, the passphrase will be necessary + /// to decrypt the cipherseed to expose the internal wallet seed. + /// * + /// aezeed_passphrase is an optional user provided passphrase that will be used + /// to encrypt the generated aezeed cipher seed. + /// * + /// seed_entropy is an optional 16-bytes generated via CSPRNG. If not + /// specified, then a fresh set of randomness will be used to create the seed. + /// A server side error occurred. + public System.Threading.Tasks.Task GenSeedAsync(byte[] aezeed_passphrase, byte[] seed_entropy) + { + return GenSeedAsync(aezeed_passphrase, seed_entropy, System.Threading.CancellationToken.None); + } + + /// * + /// GenSeed is the first method that should be used to instantiate a new lnd + /// instance. This method allows a caller to generate a new aezeed cipher seed + /// given an optional passphrase. If provided, the passphrase will be necessary + /// to decrypt the cipherseed to expose the internal wallet seed. + /// * + /// aezeed_passphrase is an optional user provided passphrase that will be used + /// to encrypt the generated aezeed cipher seed. + /// * + /// seed_entropy is an optional 16-bytes generated via CSPRNG. If not + /// specified, then a fresh set of randomness will be used to create the seed. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task GenSeedAsync(byte[] aezeed_passphrase, byte[] seed_entropy, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/genseed?"); + if (aezeed_passphrase != null) urlBuilder_.Append("aezeed_passphrase=").Append(System.Uri.EscapeDataString(System.Convert.ToString(aezeed_passphrase, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + if (seed_entropy != null) urlBuilder_.Append("seed_entropy=").Append(System.Uri.EscapeDataString(System.Convert.ToString(seed_entropy, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + urlBuilder_.Length--; + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcGenSeedResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcGenSeedResponse); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `getinfo` + /// GetInfo returns general information concerning the lightning node including + /// it's identity pubkey, alias, the chains it is connected to, and information + /// concerning the number of open+pending channels. + /// A server side error occurred. + public System.Threading.Tasks.Task GetInfoAsync() + { + return GetInfoAsync(System.Threading.CancellationToken.None); + } + + /// * lncli: `getinfo` + /// GetInfo returns general information concerning the lightning node including + /// it's identity pubkey, alias, the chains it is connected to, and information + /// concerning the number of open+pending channels. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task GetInfoAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/getinfo"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcGetInfoResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcGetInfoResponse); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `describegraph` + /// DescribeGraph returns a description of the latest graph state from the + /// point of view of the node. The graph information is partitioned into two + /// components: all the nodes/vertexes, and all the edges that connect the + /// vertexes themselves. As this is a directed graph, the edges also contain + /// the node directional specific routing policy which includes: the time lock + /// delta, fee information, etc. + /// A server side error occurred. + public System.Threading.Tasks.Task DescribeGraphAsync() + { + return DescribeGraphAsync(System.Threading.CancellationToken.None); + } + + /// * lncli: `describegraph` + /// DescribeGraph returns a description of the latest graph state from the + /// point of view of the node. The graph information is partitioned into two + /// components: all the nodes/vertexes, and all the edges that connect the + /// vertexes themselves. As this is a directed graph, the edges also contain + /// the node directional specific routing policy which includes: the time lock + /// delta, fee information, etc. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task DescribeGraphAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/graph"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcChannelGraph); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcChannelGraph); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `getchaninfo` + /// GetChanInfo returns the latest authenticated network announcement for the + /// given channel identified by its channel ID: an 8-byte integer which + /// uniquely identifies the location of transaction's funding output within the + /// blockchain. + /// A server side error occurred. + public System.Threading.Tasks.Task GetChanInfoAsync(string chan_id) + { + return GetChanInfoAsync(chan_id, System.Threading.CancellationToken.None); + } + + /// * lncli: `getchaninfo` + /// GetChanInfo returns the latest authenticated network announcement for the + /// given channel identified by its channel ID: an 8-byte integer which + /// uniquely identifies the location of transaction's funding output within the + /// blockchain. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task GetChanInfoAsync(string chan_id, System.Threading.CancellationToken cancellationToken) + { + if (chan_id == null) + throw new System.ArgumentNullException("chan_id"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/graph/edge/{chan_id}"); + urlBuilder_.Replace("{chan_id}", System.Uri.EscapeDataString(System.Convert.ToString(chan_id, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcChannelEdge); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcChannelEdge); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `getnetworkinfo` + /// GetNetworkInfo returns some basic stats about the known channel graph from + /// the point of view of the node. + /// A server side error occurred. + public System.Threading.Tasks.Task GetNetworkInfoAsync() + { + return GetNetworkInfoAsync(System.Threading.CancellationToken.None); + } + + /// * lncli: `getnetworkinfo` + /// GetNetworkInfo returns some basic stats about the known channel graph from + /// the point of view of the node. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task GetNetworkInfoAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/graph/info"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcNetworkInfo); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcNetworkInfo); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `getnodeinfo` + /// GetNodeInfo returns the latest advertised, aggregated, and authenticated + /// channel information for the specified node identified by its public key. + /// A server side error occurred. + public System.Threading.Tasks.Task GetNodeInfoAsync(string pub_key) + { + return GetNodeInfoAsync(pub_key, System.Threading.CancellationToken.None); + } + + /// * lncli: `getnodeinfo` + /// GetNodeInfo returns the latest advertised, aggregated, and authenticated + /// channel information for the specified node identified by its public key. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task GetNodeInfoAsync(string pub_key, System.Threading.CancellationToken cancellationToken) + { + if (pub_key == null) + throw new System.ArgumentNullException("pub_key"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/graph/node/{pub_key}"); + urlBuilder_.Replace("{pub_key}", System.Uri.EscapeDataString(System.Convert.ToString(pub_key, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcNodeInfo); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcNodeInfo); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `queryroutes` + /// QueryRoutes attempts to query the daemon's Channel Router for a possible + /// route to a target destination capable of carrying a specific amount of + /// satoshis. The retuned route contains the full details required to craft and + /// send an HTLC, also including the necessary information that should be + /// present within the Sphinx packet encapsulated within the HTLC. + /// / The max number of routes to return. + /// A server side error occurred. + public System.Threading.Tasks.Task QueryRoutesAsync(string pub_key, string amt, int? num_routes) + { + return QueryRoutesAsync(pub_key, amt, num_routes, System.Threading.CancellationToken.None); + } + + /// * lncli: `queryroutes` + /// QueryRoutes attempts to query the daemon's Channel Router for a possible + /// route to a target destination capable of carrying a specific amount of + /// satoshis. The retuned route contains the full details required to craft and + /// send an HTLC, also including the necessary information that should be + /// present within the Sphinx packet encapsulated within the HTLC. + /// / The max number of routes to return. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task QueryRoutesAsync(string pub_key, string amt, int? num_routes, System.Threading.CancellationToken cancellationToken) + { + if (pub_key == null) + throw new System.ArgumentNullException("pub_key"); + + if (amt == null) + throw new System.ArgumentNullException("amt"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/graph/routes/{pub_key}/{amt}?"); + urlBuilder_.Replace("{pub_key}", System.Uri.EscapeDataString(System.Convert.ToString(pub_key, System.Globalization.CultureInfo.InvariantCulture))); + urlBuilder_.Replace("{amt}", System.Uri.EscapeDataString(System.Convert.ToString(amt, System.Globalization.CultureInfo.InvariantCulture))); + if (num_routes != null) urlBuilder_.Append("num_routes=").Append(System.Uri.EscapeDataString(System.Convert.ToString(num_routes.Value, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + urlBuilder_.Length--; + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcQueryRoutesResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcQueryRoutesResponse); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * + /// InitWallet is used when lnd is starting up for the first time to fully + /// initialize the daemon and its internal wallet. At the very least a wallet + /// password must be provided. This will be used to encrypt sensitive material + /// on disk. + /// A server side error occurred. + public System.Threading.Tasks.Task InitWalletAsync(LnrpcInitWalletRequest body) + { + return InitWalletAsync(body, System.Threading.CancellationToken.None); + } + + /// * + /// InitWallet is used when lnd is starting up for the first time to fully + /// initialize the daemon and its internal wallet. At the very least a wallet + /// password must be provided. This will be used to encrypt sensitive material + /// on disk. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task InitWalletAsync(LnrpcInitWalletRequest body, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/initwallet"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType.MediaType = "application/json"; + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(object); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(object); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `lookupinvoice` + /// LookupInvoice attempts to look up an invoice according to its payment hash. + /// The passed payment hash *must* be exactly 32 bytes, if not, an error is + /// returned. + /// / The payment hash of the invoice to be looked up. + /// A server side error occurred. + public System.Threading.Tasks.Task LookupInvoiceAsync(string r_hash_str, byte[] r_hash) + { + return LookupInvoiceAsync(r_hash_str, r_hash, System.Threading.CancellationToken.None); + } + + /// * lncli: `lookupinvoice` + /// LookupInvoice attempts to look up an invoice according to its payment hash. + /// The passed payment hash *must* be exactly 32 bytes, if not, an error is + /// returned. + /// / The payment hash of the invoice to be looked up. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task LookupInvoiceAsync(string r_hash_str, byte[] r_hash, System.Threading.CancellationToken cancellationToken) + { + if (r_hash_str == null) + throw new System.ArgumentNullException("r_hash_str"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/invoice/{r_hash_str}?"); + urlBuilder_.Replace("{r_hash_str}", System.Uri.EscapeDataString(System.Convert.ToString(r_hash_str, System.Globalization.CultureInfo.InvariantCulture))); + if (r_hash != null) urlBuilder_.Append("r_hash=").Append(System.Uri.EscapeDataString(System.Convert.ToString(r_hash, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + urlBuilder_.Length--; + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcInvoice); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcInvoice); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `listinvoices` + /// ListInvoices returns a list of all the invoices currently stored within the + /// database. Any active debug invoices are ignored. + /// / Toggles if all invoices should be returned, or only those that are currently unsettled. + /// A server side error occurred. + public System.Threading.Tasks.Task ListInvoicesAsync(bool? pending_only) + { + return ListInvoicesAsync(pending_only, System.Threading.CancellationToken.None); + } + + /// * lncli: `listinvoices` + /// ListInvoices returns a list of all the invoices currently stored within the + /// database. Any active debug invoices are ignored. + /// / Toggles if all invoices should be returned, or only those that are currently unsettled. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task ListInvoicesAsync(bool? pending_only, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/invoices?"); + if (pending_only != null) urlBuilder_.Append("pending_only=").Append(System.Uri.EscapeDataString(System.Convert.ToString(pending_only.Value, System.Globalization.CultureInfo.InvariantCulture))).Append("&"); + urlBuilder_.Length--; + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcListInvoiceResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcListInvoiceResponse); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `addinvoice` + /// AddInvoice attempts to add a new invoice to the invoice database. Any + /// duplicated invoices are rejected, therefore all invoices *must* have a + /// unique payment preimage. + /// A server side error occurred. + public System.Threading.Tasks.Task AddInvoiceAsync(LnrpcInvoice body) + { + return AddInvoiceAsync(body, System.Threading.CancellationToken.None); + } + + /// * lncli: `addinvoice` + /// AddInvoice attempts to add a new invoice to the invoice database. Any + /// duplicated invoices are rejected, therefore all invoices *must* have a + /// unique payment preimage. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task AddInvoiceAsync(LnrpcInvoice body, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/invoices"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType.MediaType = "application/json"; + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcAddInvoiceResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcAddInvoiceResponse); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * + /// SubscribeInvoices returns a uni-directional stream (sever -> client) for + /// notifying the client of newly added/settled invoices. + /// (streaming responses) + /// A server side error occurred. + public System.Threading.Tasks.Task SubscribeInvoicesAsync() + { + return SubscribeInvoicesAsync(System.Threading.CancellationToken.None); + } + + /// * + /// SubscribeInvoices returns a uni-directional stream (sever -> client) for + /// notifying the client of newly added/settled invoices. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// (streaming responses) + /// A server side error occurred. + public async System.Threading.Tasks.Task SubscribeInvoicesAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/invoices/subscribe"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcInvoice); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcInvoice); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * + /// NewWitnessAddress creates a new witness address under control of the local wallet. + /// A server side error occurred. + public System.Threading.Tasks.Task NewWitnessAddressAsync() + { + return NewWitnessAddressAsync(System.Threading.CancellationToken.None); + } + + /// * + /// NewWitnessAddress creates a new witness address under control of the local wallet. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task NewWitnessAddressAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/newaddress"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcNewAddressResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcNewAddressResponse); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `listpayments` + /// ListPayments returns a list of all outgoing payments. + /// A server side error occurred. + public System.Threading.Tasks.Task ListPaymentsAsync() + { + return ListPaymentsAsync(System.Threading.CancellationToken.None); + } + + /// * lncli: `listpayments` + /// ListPayments returns a list of all outgoing payments. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task ListPaymentsAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/payments"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcListPaymentsResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcListPaymentsResponse); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * + /// DeleteAllPayments deletes all outgoing payments from DB. + /// A server side error occurred. + public System.Threading.Tasks.Task DeleteAllPaymentsAsync() + { + return DeleteAllPaymentsAsync(System.Threading.CancellationToken.None); + } + + /// * + /// DeleteAllPayments deletes all outgoing payments from DB. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task DeleteAllPaymentsAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/payments"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("DELETE"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(object); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(object); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `decodepayreq` + /// DecodePayReq takes an encoded payment request string and attempts to decode + /// it, returning a full description of the conditions encoded within the + /// payment request. + /// A server side error occurred. + public System.Threading.Tasks.Task DecodePayReqAsync(string pay_req) + { + return DecodePayReqAsync(pay_req, System.Threading.CancellationToken.None); + } + + /// * lncli: `decodepayreq` + /// DecodePayReq takes an encoded payment request string and attempts to decode + /// it, returning a full description of the conditions encoded within the + /// payment request. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task DecodePayReqAsync(string pay_req, System.Threading.CancellationToken cancellationToken) + { + if (pay_req == null) + throw new System.ArgumentNullException("pay_req"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/payreq/{pay_req}"); + urlBuilder_.Replace("{pay_req}", System.Uri.EscapeDataString(System.Convert.ToString(pay_req, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcPayReq); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcPayReq); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `listpeers` + /// ListPeers returns a verbose listing of all currently active peers. + /// A server side error occurred. + public System.Threading.Tasks.Task ListPeersAsync() + { + return ListPeersAsync(System.Threading.CancellationToken.None); + } + + /// * lncli: `listpeers` + /// ListPeers returns a verbose listing of all currently active peers. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task ListPeersAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/peers"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcListPeersResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcListPeersResponse); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `connect` + /// ConnectPeer attempts to establish a connection to a remote peer. This is at + /// the networking level, and is used for communication between nodes. This is + /// distinct from establishing a channel with a peer. + /// A server side error occurred. + public System.Threading.Tasks.Task ConnectPeerAsync(LnrpcConnectPeerRequest body) + { + return ConnectPeerAsync(body, System.Threading.CancellationToken.None); + } + + /// * lncli: `connect` + /// ConnectPeer attempts to establish a connection to a remote peer. This is at + /// the networking level, and is used for communication between nodes. This is + /// distinct from establishing a channel with a peer. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task ConnectPeerAsync(LnrpcConnectPeerRequest body, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/peers"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType.MediaType = "application/json"; + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(object); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(object); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `disconnect` + /// DisconnectPeer attempts to disconnect one peer from another identified by a + /// given pubKey. In the case that we currently have a pending or active channel + /// with the target peer, then this action will be not be allowed. + /// A server side error occurred. + public System.Threading.Tasks.Task DisconnectPeerAsync(string pub_key) + { + return DisconnectPeerAsync(pub_key, System.Threading.CancellationToken.None); + } + + /// * lncli: `disconnect` + /// DisconnectPeer attempts to disconnect one peer from another identified by a + /// given pubKey. In the case that we currently have a pending or active channel + /// with the target peer, then this action will be not be allowed. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task DisconnectPeerAsync(string pub_key, System.Threading.CancellationToken cancellationToken) + { + if (pub_key == null) + throw new System.ArgumentNullException("pub_key"); + + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/peers/{pub_key}"); + urlBuilder_.Replace("{pub_key}", System.Uri.EscapeDataString(System.Convert.ToString(pub_key, System.Globalization.CultureInfo.InvariantCulture))); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("DELETE"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(object); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(object); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `fwdinghistory` + /// ForwardingHistory allows the caller to query the htlcswitch for a record of + /// all HTLC's forwarded within the target time range, and integer offset + /// within that time range. If no time-range is specified, then the first chunk + /// of the past 24 hrs of forwarding history are returned. + /// A server side error occurred. + public System.Threading.Tasks.Task ForwardingHistoryAsync(LnrpcForwardingHistoryRequest body) + { + return ForwardingHistoryAsync(body, System.Threading.CancellationToken.None); + } + + /// * lncli: `fwdinghistory` + /// ForwardingHistory allows the caller to query the htlcswitch for a record of + /// all HTLC's forwarded within the target time range, and integer offset + /// within that time range. If no time-range is specified, then the first chunk + /// of the past 24 hrs of forwarding history are returned. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task ForwardingHistoryAsync(LnrpcForwardingHistoryRequest body, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/switch"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType.MediaType = "application/json"; + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcForwardingHistoryResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcForwardingHistoryResponse); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `listchaintxns` + /// GetTransactions returns a list describing all the known transactions + /// relevant to the wallet. + /// A server side error occurred. + public System.Threading.Tasks.Task GetTransactionsAsync() + { + return GetTransactionsAsync(System.Threading.CancellationToken.None); + } + + /// * lncli: `listchaintxns` + /// GetTransactions returns a list describing all the known transactions + /// relevant to the wallet. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task GetTransactionsAsync(System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/transactions"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + request_.Method = new System.Net.Http.HttpMethod("GET"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcTransactionDetails); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcTransactionDetails); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `sendcoins` + /// SendCoins executes a request to send coins to a particular address. Unlike + /// SendMany, this RPC call only allows creating a single output at a time. If + /// neither target_conf, or sat_per_byte are set, then the internal wallet will + /// consult its fee model to determine a fee for the default confirmation + /// target. + /// A server side error occurred. + public System.Threading.Tasks.Task SendCoinsAsync(LnrpcSendCoinsRequest body) + { + return SendCoinsAsync(body, System.Threading.CancellationToken.None); + } + + /// * lncli: `sendcoins` + /// SendCoins executes a request to send coins to a particular address. Unlike + /// SendMany, this RPC call only allows creating a single output at a time. If + /// neither target_conf, or sat_per_byte are set, then the internal wallet will + /// consult its fee model to determine a fee for the default confirmation + /// target. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task SendCoinsAsync(LnrpcSendCoinsRequest body, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/transactions"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType.MediaType = "application/json"; + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(LnrpcSendCoinsResponse); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(LnrpcSendCoinsResponse); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + /// * lncli: `unlock` + /// UnlockWallet is used at startup of lnd to provide a password to unlock + /// the wallet database. + /// A server side error occurred. + public System.Threading.Tasks.Task UnlockWalletAsync(LnrpcUnlockWalletRequest body) + { + return UnlockWalletAsync(body, System.Threading.CancellationToken.None); + } + + /// * lncli: `unlock` + /// UnlockWallet is used at startup of lnd to provide a password to unlock + /// the wallet database. + /// A cancellation token that can be used by other objects or threads to receive notice of cancellation. + /// A server side error occurred. + public async System.Threading.Tasks.Task UnlockWalletAsync(LnrpcUnlockWalletRequest body, System.Threading.CancellationToken cancellationToken) + { + var urlBuilder_ = new System.Text.StringBuilder(); + urlBuilder_.Append(BaseUrl).Append("/v1/unlockwallet"); + + var client_ = _httpClient; + try + { + using (var request_ = new System.Net.Http.HttpRequestMessage()) + { + var content_ = new System.Net.Http.StringContent(Newtonsoft.Json.JsonConvert.SerializeObject(body, _settings.Value)); + content_.Headers.ContentType.MediaType = "application/json"; + request_.Content = content_; + request_.Method = new System.Net.Http.HttpMethod("POST"); + request_.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); + + PrepareRequest(client_, request_, urlBuilder_); + var url_ = urlBuilder_.ToString(); + request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute); + PrepareRequest(client_, request_, url_); + + var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false); + try + { + var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value); + foreach (var item_ in response_.Content.Headers) + headers_[item_.Key] = item_.Value; + + ProcessResponse(client_, response_); + + var status_ = ((int)response_.StatusCode).ToString(); + if (status_ == "200") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + var result_ = default(object); + try + { + result_ = Newtonsoft.Json.JsonConvert.DeserializeObject(responseData_, _settings.Value); + return result_; + } + catch (System.Exception exception) + { + throw new SwaggerException("Could not deserialize the response body.", status_, responseData_, headers_, exception); + } + } + else + if (status_ != "200" && status_ != "204") + { + var responseData_ = await response_.Content.ReadAsStringAsync().ConfigureAwait(false); + throw new SwaggerException("The HTTP status code of the response was not expected (" + (int)response_.StatusCode + ").", status_, responseData_, headers_, null); + } + + return default(object); + } + finally + { + if (response_ != null) + response_.Dispose(); + } + } + } + finally + { + } + } + + } + + + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class PendingChannelsResponseClosedChannel : System.ComponentModel.INotifyPropertyChanged + { + private PendingChannelsResponsePendingChannel _channel; + private string _closing_txid; + + [Newtonsoft.Json.JsonProperty("channel", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public PendingChannelsResponsePendingChannel Channel + { + get { return _channel; } + set + { + if (_channel != value) + { + _channel = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("closing_txid", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Closing_txid + { + get { return _closing_txid; } + set + { + if (_closing_txid != value) + { + _closing_txid = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static PendingChannelsResponseClosedChannel FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class PendingChannelsResponseForceClosedChannel : System.ComponentModel.INotifyPropertyChanged + { + private PendingChannelsResponsePendingChannel _channel; + private string _closing_txid; + private string _limbo_balance; + private long? _maturity_height; + private int? _blocks_til_maturity; + private string _recovered_balance; + private System.Collections.ObjectModel.ObservableCollection _pending_htlcs; + + [Newtonsoft.Json.JsonProperty("channel", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public PendingChannelsResponsePendingChannel Channel + { + get { return _channel; } + set + { + if (_channel != value) + { + _channel = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("closing_txid", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Closing_txid + { + get { return _closing_txid; } + set + { + if (_closing_txid != value) + { + _closing_txid = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("limbo_balance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Limbo_balance + { + get { return _limbo_balance; } + set + { + if (_limbo_balance != value) + { + _limbo_balance = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("maturity_height", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Maturity_height + { + get { return _maturity_height; } + set + { + if (_maturity_height != value) + { + _maturity_height = value; + RaisePropertyChanged(); + } + } + } + + /// Remaining # of blocks until the commitment output can be swept. + /// Negative values indicate how many blocks have passed since becoming + /// mature. + [Newtonsoft.Json.JsonProperty("blocks_til_maturity", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int? Blocks_til_maturity + { + get { return _blocks_til_maturity; } + set + { + if (_blocks_til_maturity != value) + { + _blocks_til_maturity = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("recovered_balance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Recovered_balance + { + get { return _recovered_balance; } + set + { + if (_recovered_balance != value) + { + _recovered_balance = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("pending_htlcs", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Pending_htlcs + { + get { return _pending_htlcs; } + set + { + if (_pending_htlcs != value) + { + _pending_htlcs = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static PendingChannelsResponseForceClosedChannel FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class PendingChannelsResponsePendingChannel : System.ComponentModel.INotifyPropertyChanged + { + private string _remote_node_pub; + private string _channel_point; + private string _capacity; + private string _local_balance; + private string _remote_balance; + + [Newtonsoft.Json.JsonProperty("remote_node_pub", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Remote_node_pub + { + get { return _remote_node_pub; } + set + { + if (_remote_node_pub != value) + { + _remote_node_pub = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("channel_point", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Channel_point + { + get { return _channel_point; } + set + { + if (_channel_point != value) + { + _channel_point = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("capacity", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Capacity + { + get { return _capacity; } + set + { + if (_capacity != value) + { + _capacity = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("local_balance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Local_balance + { + get { return _local_balance; } + set + { + if (_local_balance != value) + { + _local_balance = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("remote_balance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Remote_balance + { + get { return _remote_balance; } + set + { + if (_remote_balance != value) + { + _remote_balance = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static PendingChannelsResponsePendingChannel FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class PendingChannelsResponsePendingOpenChannel : System.ComponentModel.INotifyPropertyChanged + { + private PendingChannelsResponsePendingChannel _channel; + private long? _confirmation_height; + private string _commit_fee; + private string _commit_weight; + private string _fee_per_kw; + + [Newtonsoft.Json.JsonProperty("channel", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public PendingChannelsResponsePendingChannel Channel + { + get { return _channel; } + set + { + if (_channel != value) + { + _channel = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("confirmation_height", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Confirmation_height + { + get { return _confirmation_height; } + set + { + if (_confirmation_height != value) + { + _confirmation_height = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// The amount calculated to be paid in fees for the current set of + /// commitment transactions. The fee amount is persisted with the channel + /// in order to allow the fee amount to be removed and recalculated with + /// each channel state update, including updates that happen after a system + /// restart. + [Newtonsoft.Json.JsonProperty("commit_fee", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Commit_fee + { + get { return _commit_fee; } + set + { + if (_commit_fee != value) + { + _commit_fee = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("commit_weight", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Commit_weight + { + get { return _commit_weight; } + set + { + if (_commit_weight != value) + { + _commit_weight = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// The required number of satoshis per kilo-weight that the requester will + /// pay at all times, for both the funding transaction and commitment + /// transaction. This value can later be updated once the channel is open. + [Newtonsoft.Json.JsonProperty("fee_per_kw", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Fee_per_kw + { + get { return _fee_per_kw; } + set + { + if (_fee_per_kw != value) + { + _fee_per_kw = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static PendingChannelsResponsePendingOpenChannel FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class PendingChannelsResponseWaitingCloseChannel : System.ComponentModel.INotifyPropertyChanged + { + private PendingChannelsResponsePendingChannel _channel; + private string _limbo_balance; + + [Newtonsoft.Json.JsonProperty("channel", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public PendingChannelsResponsePendingChannel Channel + { + get { return _channel; } + set + { + if (_channel != value) + { + _channel = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("limbo_balance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Limbo_balance + { + get { return _limbo_balance; } + set + { + if (_limbo_balance != value) + { + _limbo_balance = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static PendingChannelsResponseWaitingCloseChannel FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcAddInvoiceResponse : System.ComponentModel.INotifyPropertyChanged + { + private byte[] _r_hash; + private string _payment_request; + + [Newtonsoft.Json.JsonProperty("r_hash", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public byte[] R_hash + { + get { return _r_hash; } + set + { + if (_r_hash != value) + { + _r_hash = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// A bare-bones invoice for a payment within the Lightning Network. With the + /// details of the invoice, the sender has all the data necessary to send a + /// payment to the recipient. + [Newtonsoft.Json.JsonProperty("payment_request", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Payment_request + { + get { return _payment_request; } + set + { + if (_payment_request != value) + { + _payment_request = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcAddInvoiceResponse FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcChannel : System.ComponentModel.INotifyPropertyChanged + { + private bool? _active; + private string _remote_pubkey; + private string _channel_point; + private string _chan_id; + private string _capacity; + private string _local_balance; + private string _remote_balance; + private string _commit_fee; + private string _commit_weight; + private string _fee_per_kw; + private string _unsettled_balance; + private string _total_satoshis_sent; + private string _total_satoshis_received; + private string _num_updates; + private System.Collections.ObjectModel.ObservableCollection _pending_htlcs; + private long? _csv_delay; + private bool? _private; + + [Newtonsoft.Json.JsonProperty("active", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool? Active + { + get { return _active; } + set + { + if (_active != value) + { + _active = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("remote_pubkey", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Remote_pubkey + { + get { return _remote_pubkey; } + set + { + if (_remote_pubkey != value) + { + _remote_pubkey = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// The outpoint (txid:index) of the funding transaction. With this value, Bob + /// will be able to generate a signature for Alice's version of the commitment + /// transaction. + [Newtonsoft.Json.JsonProperty("channel_point", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Channel_point + { + get { return _channel_point; } + set + { + if (_channel_point != value) + { + _channel_point = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// The unique channel ID for the channel. The first 3 bytes are the block + /// height, the next 3 the index within the block, and the last 2 bytes are the + /// output index for the channel. + [Newtonsoft.Json.JsonProperty("chan_id", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Chan_id + { + get { return _chan_id; } + set + { + if (_chan_id != value) + { + _chan_id = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("capacity", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Capacity + { + get { return _capacity; } + set + { + if (_capacity != value) + { + _capacity = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("local_balance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Local_balance + { + get { return _local_balance; } + set + { + if (_local_balance != value) + { + _local_balance = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("remote_balance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Remote_balance + { + get { return _remote_balance; } + set + { + if (_remote_balance != value) + { + _remote_balance = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// The amount calculated to be paid in fees for the current set of commitment + /// transactions. The fee amount is persisted with the channel in order to + /// allow the fee amount to be removed and recalculated with each channel state + /// update, including updates that happen after a system restart. + [Newtonsoft.Json.JsonProperty("commit_fee", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Commit_fee + { + get { return _commit_fee; } + set + { + if (_commit_fee != value) + { + _commit_fee = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("commit_weight", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Commit_weight + { + get { return _commit_weight; } + set + { + if (_commit_weight != value) + { + _commit_weight = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// The required number of satoshis per kilo-weight that the requester will pay + /// at all times, for both the funding transaction and commitment transaction. + /// This value can later be updated once the channel is open. + [Newtonsoft.Json.JsonProperty("fee_per_kw", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Fee_per_kw + { + get { return _fee_per_kw; } + set + { + if (_fee_per_kw != value) + { + _fee_per_kw = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("unsettled_balance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Unsettled_balance + { + get { return _unsettled_balance; } + set + { + if (_unsettled_balance != value) + { + _unsettled_balance = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// The total number of satoshis we've sent within this channel. + [Newtonsoft.Json.JsonProperty("total_satoshis_sent", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Total_satoshis_sent + { + get { return _total_satoshis_sent; } + set + { + if (_total_satoshis_sent != value) + { + _total_satoshis_sent = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// The total number of satoshis we've received within this channel. + [Newtonsoft.Json.JsonProperty("total_satoshis_received", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Total_satoshis_received + { + get { return _total_satoshis_received; } + set + { + if (_total_satoshis_received != value) + { + _total_satoshis_received = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// The total number of updates conducted within this channel. + [Newtonsoft.Json.JsonProperty("num_updates", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Num_updates + { + get { return _num_updates; } + set + { + if (_num_updates != value) + { + _num_updates = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// The list of active, uncleared HTLCs currently pending within the channel. + [Newtonsoft.Json.JsonProperty("pending_htlcs", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Pending_htlcs + { + get { return _pending_htlcs; } + set + { + if (_pending_htlcs != value) + { + _pending_htlcs = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// The CSV delay expressed in relative blocks. If the channel is force + /// closed, we'll need to wait for this many blocks before we can regain our + /// funds. + [Newtonsoft.Json.JsonProperty("csv_delay", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Csv_delay + { + get { return _csv_delay; } + set + { + if (_csv_delay != value) + { + _csv_delay = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("private", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool? Private + { + get { return _private; } + set + { + if (_private != value) + { + _private = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcChannel FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcChannelBalanceResponse : System.ComponentModel.INotifyPropertyChanged + { + private string _balance; + private string _pending_open_balance; + + [Newtonsoft.Json.JsonProperty("balance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Balance + { + get { return _balance; } + set + { + if (_balance != value) + { + _balance = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("pending_open_balance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Pending_open_balance + { + get { return _pending_open_balance; } + set + { + if (_pending_open_balance != value) + { + _pending_open_balance = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcChannelBalanceResponse FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcChannelCloseUpdate : System.ComponentModel.INotifyPropertyChanged + { + private byte[] _closing_txid; + private bool? _success; + + [Newtonsoft.Json.JsonProperty("closing_txid", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public byte[] Closing_txid + { + get { return _closing_txid; } + set + { + if (_closing_txid != value) + { + _closing_txid = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("success", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool? Success + { + get { return _success; } + set + { + if (_success != value) + { + _success = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcChannelCloseUpdate FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + /// * + /// A fully authenticated channel along with all its unique attributes. + /// Once an authenticated channel announcement has been processed on the network, + /// then an instance of ChannelEdgeInfo encapsulating the channels attributes is + /// stored. The other portions relevant to routing policy of a channel are stored + /// within a ChannelEdgePolicy for each direction of the channel. + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcChannelEdge : System.ComponentModel.INotifyPropertyChanged + { + private string _channel_id; + private string _chan_point; + private long? _last_update; + private string _node1_pub; + private string _node2_pub; + private string _capacity; + private LnrpcRoutingPolicy _node1_policy; + private LnrpcRoutingPolicy _node2_policy; + + /// * + /// The unique channel ID for the channel. The first 3 bytes are the block + /// height, the next 3 the index within the block, and the last 2 bytes are the + /// output index for the channel. + [Newtonsoft.Json.JsonProperty("channel_id", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Channel_id + { + get { return _channel_id; } + set + { + if (_channel_id != value) + { + _channel_id = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("chan_point", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Chan_point + { + get { return _chan_point; } + set + { + if (_chan_point != value) + { + _chan_point = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("last_update", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Last_update + { + get { return _last_update; } + set + { + if (_last_update != value) + { + _last_update = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("node1_pub", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Node1_pub + { + get { return _node1_pub; } + set + { + if (_node1_pub != value) + { + _node1_pub = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("node2_pub", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Node2_pub + { + get { return _node2_pub; } + set + { + if (_node2_pub != value) + { + _node2_pub = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("capacity", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Capacity + { + get { return _capacity; } + set + { + if (_capacity != value) + { + _capacity = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("node1_policy", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public LnrpcRoutingPolicy Node1_policy + { + get { return _node1_policy; } + set + { + if (_node1_policy != value) + { + _node1_policy = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("node2_policy", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public LnrpcRoutingPolicy Node2_policy + { + get { return _node2_policy; } + set + { + if (_node2_policy != value) + { + _node2_policy = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcChannelEdge FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcChannelEdgeUpdate : System.ComponentModel.INotifyPropertyChanged + { + private string _chan_id; + private LnrpcChannelPoint _chan_point; + private string _capacity; + private LnrpcRoutingPolicy _routing_policy; + private string _advertising_node; + private string _connecting_node; + + /// * + /// The unique channel ID for the channel. The first 3 bytes are the block + /// height, the next 3 the index within the block, and the last 2 bytes are the + /// output index for the channel. + [Newtonsoft.Json.JsonProperty("chan_id", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Chan_id + { + get { return _chan_id; } + set + { + if (_chan_id != value) + { + _chan_id = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("chan_point", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public LnrpcChannelPoint Chan_point + { + get { return _chan_point; } + set + { + if (_chan_point != value) + { + _chan_point = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("capacity", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Capacity + { + get { return _capacity; } + set + { + if (_capacity != value) + { + _capacity = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("routing_policy", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public LnrpcRoutingPolicy Routing_policy + { + get { return _routing_policy; } + set + { + if (_routing_policy != value) + { + _routing_policy = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("advertising_node", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Advertising_node + { + get { return _advertising_node; } + set + { + if (_advertising_node != value) + { + _advertising_node = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("connecting_node", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Connecting_node + { + get { return _connecting_node; } + set + { + if (_connecting_node != value) + { + _connecting_node = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcChannelEdgeUpdate FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcChannelFeeReport : System.ComponentModel.INotifyPropertyChanged + { + private string _chan_point; + private string _base_fee_msat; + private string _fee_per_mil; + private double? _fee_rate; + + /// / The channel that this fee report belongs to. + [Newtonsoft.Json.JsonProperty("chan_point", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Chan_point + { + get { return _chan_point; } + set + { + if (_chan_point != value) + { + _chan_point = value; + RaisePropertyChanged(); + } + } + } + + /// / The base fee charged regardless of the number of milli-satoshis sent. + [Newtonsoft.Json.JsonProperty("base_fee_msat", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Base_fee_msat + { + get { return _base_fee_msat; } + set + { + if (_base_fee_msat != value) + { + _base_fee_msat = value; + RaisePropertyChanged(); + } + } + } + + /// / The amount charged per milli-satoshis transferred expressed in millionths of a satoshi. + [Newtonsoft.Json.JsonProperty("fee_per_mil", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Fee_per_mil + { + get { return _fee_per_mil; } + set + { + if (_fee_per_mil != value) + { + _fee_per_mil = value; + RaisePropertyChanged(); + } + } + } + + /// / The effective fee rate in milli-satoshis. Computed by dividing the fee_per_mil value by 1 million. + [Newtonsoft.Json.JsonProperty("fee_rate", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double? Fee_rate + { + get { return _fee_rate; } + set + { + if (_fee_rate != value) + { + _fee_rate = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcChannelFeeReport FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + /// / Returns a new instance of the directed channel graph. + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcChannelGraph : System.ComponentModel.INotifyPropertyChanged + { + private System.Collections.ObjectModel.ObservableCollection _nodes; + private System.Collections.ObjectModel.ObservableCollection _edges; + + [Newtonsoft.Json.JsonProperty("nodes", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Nodes + { + get { return _nodes; } + set + { + if (_nodes != value) + { + _nodes = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("edges", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Edges + { + get { return _edges; } + set + { + if (_edges != value) + { + _edges = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcChannelGraph FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcChannelOpenUpdate : System.ComponentModel.INotifyPropertyChanged + { + private LnrpcChannelPoint _channel_point; + + [Newtonsoft.Json.JsonProperty("channel_point", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public LnrpcChannelPoint Channel_point + { + get { return _channel_point; } + set + { + if (_channel_point != value) + { + _channel_point = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcChannelOpenUpdate FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcChannelPoint : System.ComponentModel.INotifyPropertyChanged + { + private byte[] _funding_txid_bytes; + private string _funding_txid_str; + private long? _output_index; + + [Newtonsoft.Json.JsonProperty("funding_txid_bytes", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public byte[] Funding_txid_bytes + { + get { return _funding_txid_bytes; } + set + { + if (_funding_txid_bytes != value) + { + _funding_txid_bytes = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("funding_txid_str", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Funding_txid_str + { + get { return _funding_txid_str; } + set + { + if (_funding_txid_str != value) + { + _funding_txid_str = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("output_index", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Output_index + { + get { return _output_index; } + set + { + if (_output_index != value) + { + _output_index = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcChannelPoint FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcCloseStatusUpdate : System.ComponentModel.INotifyPropertyChanged + { + private LnrpcPendingUpdate _close_pending; + private LnrpcConfirmationUpdate _confirmation; + private LnrpcChannelCloseUpdate _chan_close; + + [Newtonsoft.Json.JsonProperty("close_pending", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public LnrpcPendingUpdate Close_pending + { + get { return _close_pending; } + set + { + if (_close_pending != value) + { + _close_pending = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("confirmation", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public LnrpcConfirmationUpdate Confirmation + { + get { return _confirmation; } + set + { + if (_confirmation != value) + { + _confirmation = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("chan_close", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public LnrpcChannelCloseUpdate Chan_close + { + get { return _chan_close; } + set + { + if (_chan_close != value) + { + _chan_close = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcCloseStatusUpdate FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcClosedChannelUpdate : System.ComponentModel.INotifyPropertyChanged + { + private string _chan_id; + private string _capacity; + private long? _closed_height; + private LnrpcChannelPoint _chan_point; + + /// * + /// The unique channel ID for the channel. The first 3 bytes are the block + /// height, the next 3 the index within the block, and the last 2 bytes are the + /// output index for the channel. + [Newtonsoft.Json.JsonProperty("chan_id", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Chan_id + { + get { return _chan_id; } + set + { + if (_chan_id != value) + { + _chan_id = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("capacity", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Capacity + { + get { return _capacity; } + set + { + if (_capacity != value) + { + _capacity = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("closed_height", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Closed_height + { + get { return _closed_height; } + set + { + if (_closed_height != value) + { + _closed_height = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("chan_point", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public LnrpcChannelPoint Chan_point + { + get { return _chan_point; } + set + { + if (_chan_point != value) + { + _chan_point = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcClosedChannelUpdate FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcConfirmationUpdate : System.ComponentModel.INotifyPropertyChanged + { + private byte[] _block_sha; + private int? _block_height; + private long? _num_confs_left; + + [Newtonsoft.Json.JsonProperty("block_sha", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public byte[] Block_sha + { + get { return _block_sha; } + set + { + if (_block_sha != value) + { + _block_sha = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("block_height", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int? Block_height + { + get { return _block_height; } + set + { + if (_block_height != value) + { + _block_height = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("num_confs_left", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Num_confs_left + { + get { return _num_confs_left; } + set + { + if (_num_confs_left != value) + { + _num_confs_left = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcConfirmationUpdate FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcConnectPeerRequest : System.ComponentModel.INotifyPropertyChanged + { + private LnrpcLightningAddress _addr; + private bool? _perm; + + [Newtonsoft.Json.JsonProperty("addr", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public LnrpcLightningAddress Addr + { + get { return _addr; } + set + { + if (_addr != value) + { + _addr = value; + RaisePropertyChanged(); + } + } + } + + /// * If set, the daemon will attempt to persistently connect to the target + /// peer. Otherwise, the call will be synchronous. + [Newtonsoft.Json.JsonProperty("perm", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool? Perm + { + get { return _perm; } + set + { + if (_perm != value) + { + _perm = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcConnectPeerRequest FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcDebugLevelResponse : System.ComponentModel.INotifyPropertyChanged + { + private string _sub_systems; + + [Newtonsoft.Json.JsonProperty("sub_systems", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Sub_systems + { + get { return _sub_systems; } + set + { + if (_sub_systems != value) + { + _sub_systems = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcDebugLevelResponse FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcFeeReportResponse : System.ComponentModel.INotifyPropertyChanged + { + private System.Collections.ObjectModel.ObservableCollection _channel_fees; + private string _day_fee_sum; + private string _week_fee_sum; + private string _month_fee_sum; + + /// / An array of channel fee reports which describes the current fee schedule for each channel. + [Newtonsoft.Json.JsonProperty("channel_fees", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Channel_fees + { + get { return _channel_fees; } + set + { + if (_channel_fees != value) + { + _channel_fees = value; + RaisePropertyChanged(); + } + } + } + + /// / The total amount of fee revenue (in satoshis) the switch has collected over the past 24 hrs. + [Newtonsoft.Json.JsonProperty("day_fee_sum", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Day_fee_sum + { + get { return _day_fee_sum; } + set + { + if (_day_fee_sum != value) + { + _day_fee_sum = value; + RaisePropertyChanged(); + } + } + } + + /// / The total amount of fee revenue (in satoshis) the switch has collected over the past 1 week. + [Newtonsoft.Json.JsonProperty("week_fee_sum", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Week_fee_sum + { + get { return _week_fee_sum; } + set + { + if (_week_fee_sum != value) + { + _week_fee_sum = value; + RaisePropertyChanged(); + } + } + } + + /// / The total amount of fee revenue (in satoshis) the switch has collected over the past 1 month. + [Newtonsoft.Json.JsonProperty("month_fee_sum", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Month_fee_sum + { + get { return _month_fee_sum; } + set + { + if (_month_fee_sum != value) + { + _month_fee_sum = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcFeeReportResponse FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcForwardingEvent : System.ComponentModel.INotifyPropertyChanged + { + private string _timestamp; + private string _chan_id_in; + private string _chan_id_out; + private string _amt_in; + private string _amt_out; + private string _fee; + + /// / Timestamp is the time (unix epoch offset) that this circuit was completed. + [Newtonsoft.Json.JsonProperty("timestamp", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Timestamp + { + get { return _timestamp; } + set + { + if (_timestamp != value) + { + _timestamp = value; + RaisePropertyChanged(); + } + } + } + + /// / The incoming channel ID that carried the HTLC that created the circuit. + [Newtonsoft.Json.JsonProperty("chan_id_in", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Chan_id_in + { + get { return _chan_id_in; } + set + { + if (_chan_id_in != value) + { + _chan_id_in = value; + RaisePropertyChanged(); + } + } + } + + /// / The outgoing channel ID that carried the preimage that completed the circuit. + [Newtonsoft.Json.JsonProperty("chan_id_out", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Chan_id_out + { + get { return _chan_id_out; } + set + { + if (_chan_id_out != value) + { + _chan_id_out = value; + RaisePropertyChanged(); + } + } + } + + /// / The total amount of the incoming HTLC that created half the circuit. + [Newtonsoft.Json.JsonProperty("amt_in", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Amt_in + { + get { return _amt_in; } + set + { + if (_amt_in != value) + { + _amt_in = value; + RaisePropertyChanged(); + } + } + } + + /// / The total amount of the outgoign HTLC that created the second half of the circuit. + [Newtonsoft.Json.JsonProperty("amt_out", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Amt_out + { + get { return _amt_out; } + set + { + if (_amt_out != value) + { + _amt_out = value; + RaisePropertyChanged(); + } + } + } + + /// / The total fee that this payment circuit carried. + [Newtonsoft.Json.JsonProperty("fee", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Fee + { + get { return _fee; } + set + { + if (_fee != value) + { + _fee = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcForwardingEvent FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcForwardingHistoryRequest : System.ComponentModel.INotifyPropertyChanged + { + private string _start_time; + private string _end_time; + private long? _index_offset; + private long? _num_max_events; + + /// / Start time is the starting point of the forwarding history request. All records beyond this point will be included, respecting the end time, and the index offset. + [Newtonsoft.Json.JsonProperty("start_time", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Start_time + { + get { return _start_time; } + set + { + if (_start_time != value) + { + _start_time = value; + RaisePropertyChanged(); + } + } + } + + /// / End time is the end point of the forwarding history request. The response will carry at most 50k records between the start time and the end time. The index offset can be used to implement pagination. + [Newtonsoft.Json.JsonProperty("end_time", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string End_time + { + get { return _end_time; } + set + { + if (_end_time != value) + { + _end_time = value; + RaisePropertyChanged(); + } + } + } + + /// / Index offset is the offset in the time series to start at. As each response can only contain 50k records, callers can use this to skip around within a packed time series. + [Newtonsoft.Json.JsonProperty("index_offset", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Index_offset + { + get { return _index_offset; } + set + { + if (_index_offset != value) + { + _index_offset = value; + RaisePropertyChanged(); + } + } + } + + /// / The max number of events to return in the response to this query. + [Newtonsoft.Json.JsonProperty("num_max_events", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Num_max_events + { + get { return _num_max_events; } + set + { + if (_num_max_events != value) + { + _num_max_events = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcForwardingHistoryRequest FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcForwardingHistoryResponse : System.ComponentModel.INotifyPropertyChanged + { + private System.Collections.ObjectModel.ObservableCollection _forwarding_events; + private long? _last_offset_index; + + /// / A list of forwarding events from the time slice of the time series specified in the request. + [Newtonsoft.Json.JsonProperty("forwarding_events", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Forwarding_events + { + get { return _forwarding_events; } + set + { + if (_forwarding_events != value) + { + _forwarding_events = value; + RaisePropertyChanged(); + } + } + } + + /// / The index of the last time in the set of returned forwarding events. Can be used to seek further, pagination style. + [Newtonsoft.Json.JsonProperty("last_offset_index", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Last_offset_index + { + get { return _last_offset_index; } + set + { + if (_last_offset_index != value) + { + _last_offset_index = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcForwardingHistoryResponse FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcGenSeedResponse : System.ComponentModel.INotifyPropertyChanged + { + private System.Collections.ObjectModel.ObservableCollection _cipher_seed_mnemonic; + private byte[] _enciphered_seed; + + /// * + /// cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed + /// cipher seed obtained by the user. This field is optional, as if not + /// provided, then the daemon will generate a new cipher seed for the user. + /// Otherwise, then the daemon will attempt to recover the wallet state linked + /// to this cipher seed. + [Newtonsoft.Json.JsonProperty("cipher_seed_mnemonic", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Cipher_seed_mnemonic + { + get { return _cipher_seed_mnemonic; } + set + { + if (_cipher_seed_mnemonic != value) + { + _cipher_seed_mnemonic = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// enciphered_seed are the raw aezeed cipher seed bytes. This is the raw + /// cipher text before run through our mnemonic encoding scheme. + [Newtonsoft.Json.JsonProperty("enciphered_seed", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public byte[] Enciphered_seed + { + get { return _enciphered_seed; } + set + { + if (_enciphered_seed != value) + { + _enciphered_seed = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcGenSeedResponse FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcGetInfoResponse : System.ComponentModel.INotifyPropertyChanged + { + private string _identity_pubkey; + private string _alias; + private long? _num_pending_channels; + private long? _num_active_channels; + private long? _num_peers; + private long? _block_height; + private string _block_hash; + private bool? _synced_to_chain; + private bool? _testnet; + private System.Collections.ObjectModel.ObservableCollection _chains; + private System.Collections.ObjectModel.ObservableCollection _uris; + private string _best_header_timestamp; + private string _version; + + /// / The identity pubkey of the current node. + [Newtonsoft.Json.JsonProperty("identity_pubkey", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Identity_pubkey + { + get { return _identity_pubkey; } + set + { + if (_identity_pubkey != value) + { + _identity_pubkey = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("alias", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Alias + { + get { return _alias; } + set + { + if (_alias != value) + { + _alias = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("num_pending_channels", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Num_pending_channels + { + get { return _num_pending_channels; } + set + { + if (_num_pending_channels != value) + { + _num_pending_channels = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("num_active_channels", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Num_active_channels + { + get { return _num_active_channels; } + set + { + if (_num_active_channels != value) + { + _num_active_channels = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("num_peers", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Num_peers + { + get { return _num_peers; } + set + { + if (_num_peers != value) + { + _num_peers = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("block_height", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Block_height + { + get { return _block_height; } + set + { + if (_block_height != value) + { + _block_height = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("block_hash", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Block_hash + { + get { return _block_hash; } + set + { + if (_block_hash != value) + { + _block_hash = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("synced_to_chain", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool? Synced_to_chain + { + get { return _synced_to_chain; } + set + { + if (_synced_to_chain != value) + { + _synced_to_chain = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("testnet", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool? Testnet + { + get { return _testnet; } + set + { + if (_testnet != value) + { + _testnet = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("chains", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Chains + { + get { return _chains; } + set + { + if (_chains != value) + { + _chains = value; + RaisePropertyChanged(); + } + } + } + + /// / The URIs of the current node. + [Newtonsoft.Json.JsonProperty("uris", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Uris + { + get { return _uris; } + set + { + if (_uris != value) + { + _uris = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("best_header_timestamp", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Best_header_timestamp + { + get { return _best_header_timestamp; } + set + { + if (_best_header_timestamp != value) + { + _best_header_timestamp = value; + RaisePropertyChanged(); + } + } + } + + /// / The version of the LND software that the node is running. + [Newtonsoft.Json.JsonProperty("version", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Version + { + get { return _version; } + set + { + if (_version != value) + { + _version = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcGetInfoResponse FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcGraphTopologyUpdate : System.ComponentModel.INotifyPropertyChanged + { + private System.Collections.ObjectModel.ObservableCollection _node_updates; + private System.Collections.ObjectModel.ObservableCollection _channel_updates; + private System.Collections.ObjectModel.ObservableCollection _closed_chans; + + [Newtonsoft.Json.JsonProperty("node_updates", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Node_updates + { + get { return _node_updates; } + set + { + if (_node_updates != value) + { + _node_updates = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("channel_updates", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Channel_updates + { + get { return _channel_updates; } + set + { + if (_channel_updates != value) + { + _channel_updates = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("closed_chans", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Closed_chans + { + get { return _closed_chans; } + set + { + if (_closed_chans != value) + { + _closed_chans = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcGraphTopologyUpdate FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcHTLC : System.ComponentModel.INotifyPropertyChanged + { + private bool? _incoming; + private string _amount; + private byte[] _hash_lock; + private long? _expiration_height; + + [Newtonsoft.Json.JsonProperty("incoming", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool? Incoming + { + get { return _incoming; } + set + { + if (_incoming != value) + { + _incoming = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("amount", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Amount + { + get { return _amount; } + set + { + if (_amount != value) + { + _amount = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("hash_lock", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public byte[] Hash_lock + { + get { return _hash_lock; } + set + { + if (_hash_lock != value) + { + _hash_lock = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("expiration_height", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Expiration_height + { + get { return _expiration_height; } + set + { + if (_expiration_height != value) + { + _expiration_height = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcHTLC FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcHop : System.ComponentModel.INotifyPropertyChanged + { + private string _chan_id; + private string _chan_capacity; + private string _amt_to_forward; + private string _fee; + private long? _expiry; + private string _amt_to_forward_msat; + private string _fee_msat; + + /// * + /// The unique channel ID for the channel. The first 3 bytes are the block + /// height, the next 3 the index within the block, and the last 2 bytes are the + /// output index for the channel. + [Newtonsoft.Json.JsonProperty("chan_id", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Chan_id + { + get { return _chan_id; } + set + { + if (_chan_id != value) + { + _chan_id = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("chan_capacity", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Chan_capacity + { + get { return _chan_capacity; } + set + { + if (_chan_capacity != value) + { + _chan_capacity = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("amt_to_forward", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Amt_to_forward + { + get { return _amt_to_forward; } + set + { + if (_amt_to_forward != value) + { + _amt_to_forward = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("fee", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Fee + { + get { return _fee; } + set + { + if (_fee != value) + { + _fee = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("expiry", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Expiry + { + get { return _expiry; } + set + { + if (_expiry != value) + { + _expiry = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("amt_to_forward_msat", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Amt_to_forward_msat + { + get { return _amt_to_forward_msat; } + set + { + if (_amt_to_forward_msat != value) + { + _amt_to_forward_msat = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("fee_msat", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Fee_msat + { + get { return _fee_msat; } + set + { + if (_fee_msat != value) + { + _fee_msat = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcHop FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcHopHint : System.ComponentModel.INotifyPropertyChanged + { + private string _node_id; + private string _chan_id; + private long? _fee_base_msat; + private long? _fee_proportional_millionths; + private long? _cltv_expiry_delta; + + /// / The public key of the node at the start of the channel. + [Newtonsoft.Json.JsonProperty("node_id", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Node_id + { + get { return _node_id; } + set + { + if (_node_id != value) + { + _node_id = value; + RaisePropertyChanged(); + } + } + } + + /// / The unique identifier of the channel. + [Newtonsoft.Json.JsonProperty("chan_id", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Chan_id + { + get { return _chan_id; } + set + { + if (_chan_id != value) + { + _chan_id = value; + RaisePropertyChanged(); + } + } + } + + /// / The base fee of the channel denominated in millisatoshis. + [Newtonsoft.Json.JsonProperty("fee_base_msat", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Fee_base_msat + { + get { return _fee_base_msat; } + set + { + if (_fee_base_msat != value) + { + _fee_base_msat = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// The fee rate of the channel for sending one satoshi across it denominated in + /// millionths of a satoshi. + [Newtonsoft.Json.JsonProperty("fee_proportional_millionths", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Fee_proportional_millionths + { + get { return _fee_proportional_millionths; } + set + { + if (_fee_proportional_millionths != value) + { + _fee_proportional_millionths = value; + RaisePropertyChanged(); + } + } + } + + /// / The time-lock delta of the channel. + [Newtonsoft.Json.JsonProperty("cltv_expiry_delta", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Cltv_expiry_delta + { + get { return _cltv_expiry_delta; } + set + { + if (_cltv_expiry_delta != value) + { + _cltv_expiry_delta = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcHopHint FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcInitWalletRequest : System.ComponentModel.INotifyPropertyChanged + { + private byte[] _wallet_password; + private System.Collections.ObjectModel.ObservableCollection _cipher_seed_mnemonic; + private byte[] _aezeed_passphrase; + private int? _recovery_window; + + /// * + /// wallet_password is the passphrase that should be used to encrypt the + /// wallet. This MUST be at least 8 chars in length. After creation, this + /// password is required to unlock the daemon. + [Newtonsoft.Json.JsonProperty("wallet_password", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public byte[] Wallet_password + { + get { return _wallet_password; } + set + { + if (_wallet_password != value) + { + _wallet_password = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// cipher_seed_mnemonic is a 24-word mnemonic that encodes a prior aezeed + /// cipher seed obtained by the user. This may have been generated by the + /// GenSeed method, or be an existing seed. + [Newtonsoft.Json.JsonProperty("cipher_seed_mnemonic", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Cipher_seed_mnemonic + { + get { return _cipher_seed_mnemonic; } + set + { + if (_cipher_seed_mnemonic != value) + { + _cipher_seed_mnemonic = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// aezeed_passphrase is an optional user provided passphrase that will be used + /// to encrypt the generated aezeed cipher seed. + [Newtonsoft.Json.JsonProperty("aezeed_passphrase", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public byte[] Aezeed_passphrase + { + get { return _aezeed_passphrase; } + set + { + if (_aezeed_passphrase != value) + { + _aezeed_passphrase = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// recovery_window is an optional argument specifying the address lookahead + /// when restoring a wallet seed. The recovery window applies to each + /// invdividual branch of the BIP44 derivation paths. Supplying a recovery + /// window of zero indicates that no addresses should be recovered, such after + /// the first initialization of the wallet. + [Newtonsoft.Json.JsonProperty("recovery_window", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int? Recovery_window + { + get { return _recovery_window; } + set + { + if (_recovery_window != value) + { + _recovery_window = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcInitWalletRequest FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcInvoice : System.ComponentModel.INotifyPropertyChanged + { + private string _memo; + private byte[] _receipt; + private byte[] _r_preimage; + private byte[] _r_hash; + private string _value; + private bool? _settled; + private string _creation_date; + private string _settle_date; + private string _payment_request; + private byte[] _description_hash; + private string _expiry; + private string _fallback_addr; + private string _cltv_expiry; + private System.Collections.ObjectModel.ObservableCollection _route_hints; + private bool? _private; + + /// * + /// An optional memo to attach along with the invoice. Used for record keeping + /// purposes for the invoice's creator, and will also be set in the description + /// field of the encoded payment request if the description_hash field is not + /// being used. + [Newtonsoft.Json.JsonProperty("memo", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Memo + { + get { return _memo; } + set + { + if (_memo != value) + { + _memo = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("receipt", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public byte[] Receipt + { + get { return _receipt; } + set + { + if (_receipt != value) + { + _receipt = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("r_preimage", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public byte[] R_preimage + { + get { return _r_preimage; } + set + { + if (_r_preimage != value) + { + _r_preimage = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("r_hash", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public byte[] R_hash + { + get { return _r_hash; } + set + { + if (_r_hash != value) + { + _r_hash = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("value", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Value + { + get { return _value; } + set + { + if (_value != value) + { + _value = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("settled", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool? Settled + { + get { return _settled; } + set + { + if (_settled != value) + { + _settled = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("creation_date", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Creation_date + { + get { return _creation_date; } + set + { + if (_creation_date != value) + { + _creation_date = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("settle_date", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Settle_date + { + get { return _settle_date; } + set + { + if (_settle_date != value) + { + _settle_date = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// A bare-bones invoice for a payment within the Lightning Network. With the + /// details of the invoice, the sender has all the data necessary to send a + /// payment to the recipient. + [Newtonsoft.Json.JsonProperty("payment_request", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Payment_request + { + get { return _payment_request; } + set + { + if (_payment_request != value) + { + _payment_request = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// Hash (SHA-256) of a description of the payment. Used if the description of + /// payment (memo) is too long to naturally fit within the description field + /// of an encoded payment request. + [Newtonsoft.Json.JsonProperty("description_hash", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public byte[] Description_hash + { + get { return _description_hash; } + set + { + if (_description_hash != value) + { + _description_hash = value; + RaisePropertyChanged(); + } + } + } + + /// / Payment request expiry time in seconds. Default is 3600 (1 hour). + [Newtonsoft.Json.JsonProperty("expiry", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Expiry + { + get { return _expiry; } + set + { + if (_expiry != value) + { + _expiry = value; + RaisePropertyChanged(); + } + } + } + + /// / Fallback on-chain address. + [Newtonsoft.Json.JsonProperty("fallback_addr", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Fallback_addr + { + get { return _fallback_addr; } + set + { + if (_fallback_addr != value) + { + _fallback_addr = value; + RaisePropertyChanged(); + } + } + } + + /// / Delta to use for the time-lock of the CLTV extended to the final hop. + [Newtonsoft.Json.JsonProperty("cltv_expiry", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Cltv_expiry + { + get { return _cltv_expiry; } + set + { + if (_cltv_expiry != value) + { + _cltv_expiry = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// Route hints that can each be individually used to assist in reaching the + /// invoice's destination. + [Newtonsoft.Json.JsonProperty("route_hints", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Route_hints + { + get { return _route_hints; } + set + { + if (_route_hints != value) + { + _route_hints = value; + RaisePropertyChanged(); + } + } + } + + /// / Whether this invoice should include routing hints for private channels. + [Newtonsoft.Json.JsonProperty("private", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool? Private + { + get { return _private; } + set + { + if (_private != value) + { + _private = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcInvoice FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcLightningAddress : System.ComponentModel.INotifyPropertyChanged + { + private string _pubkey; + private string _host; + + [Newtonsoft.Json.JsonProperty("pubkey", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Pubkey + { + get { return _pubkey; } + set + { + if (_pubkey != value) + { + _pubkey = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("host", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Host + { + get { return _host; } + set + { + if (_host != value) + { + _host = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcLightningAddress FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + /// * + /// An individual vertex/node within the channel graph. A node is + /// connected to other nodes by one or more channel edges emanating from it. As the + /// graph is directed, a node will also have an incoming edge attached to it for + /// each outgoing edge. + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcLightningNode : System.ComponentModel.INotifyPropertyChanged + { + private long? _last_update; + private string _pub_key; + private string _alias; + private System.Collections.ObjectModel.ObservableCollection _addresses; + private string _color; + + [Newtonsoft.Json.JsonProperty("last_update", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Last_update + { + get { return _last_update; } + set + { + if (_last_update != value) + { + _last_update = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("pub_key", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Pub_key + { + get { return _pub_key; } + set + { + if (_pub_key != value) + { + _pub_key = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("alias", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Alias + { + get { return _alias; } + set + { + if (_alias != value) + { + _alias = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("addresses", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Addresses + { + get { return _addresses; } + set + { + if (_addresses != value) + { + _addresses = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("color", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Color + { + get { return _color; } + set + { + if (_color != value) + { + _color = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcLightningNode FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcListChannelsResponse : System.ComponentModel.INotifyPropertyChanged + { + private System.Collections.ObjectModel.ObservableCollection _channels; + + [Newtonsoft.Json.JsonProperty("channels", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Channels + { + get { return _channels; } + set + { + if (_channels != value) + { + _channels = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcListChannelsResponse FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcListInvoiceResponse : System.ComponentModel.INotifyPropertyChanged + { + private System.Collections.ObjectModel.ObservableCollection _invoices; + + [Newtonsoft.Json.JsonProperty("invoices", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Invoices + { + get { return _invoices; } + set + { + if (_invoices != value) + { + _invoices = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcListInvoiceResponse FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcListPaymentsResponse : System.ComponentModel.INotifyPropertyChanged + { + private System.Collections.ObjectModel.ObservableCollection _payments; + + [Newtonsoft.Json.JsonProperty("payments", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Payments + { + get { return _payments; } + set + { + if (_payments != value) + { + _payments = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcListPaymentsResponse FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcListPeersResponse : System.ComponentModel.INotifyPropertyChanged + { + private System.Collections.ObjectModel.ObservableCollection _peers; + + [Newtonsoft.Json.JsonProperty("peers", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Peers + { + get { return _peers; } + set + { + if (_peers != value) + { + _peers = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcListPeersResponse FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcNetworkInfo : System.ComponentModel.INotifyPropertyChanged + { + private long? _graph_diameter; + private double? _avg_out_degree; + private long? _max_out_degree; + private long? _num_nodes; + private long? _num_channels; + private string _total_network_capacity; + private double? _avg_channel_size; + private string _min_channel_size; + private string _max_channel_size; + + [Newtonsoft.Json.JsonProperty("graph_diameter", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Graph_diameter + { + get { return _graph_diameter; } + set + { + if (_graph_diameter != value) + { + _graph_diameter = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("avg_out_degree", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double? Avg_out_degree + { + get { return _avg_out_degree; } + set + { + if (_avg_out_degree != value) + { + _avg_out_degree = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("max_out_degree", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Max_out_degree + { + get { return _max_out_degree; } + set + { + if (_max_out_degree != value) + { + _max_out_degree = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("num_nodes", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Num_nodes + { + get { return _num_nodes; } + set + { + if (_num_nodes != value) + { + _num_nodes = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("num_channels", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Num_channels + { + get { return _num_channels; } + set + { + if (_num_channels != value) + { + _num_channels = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("total_network_capacity", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Total_network_capacity + { + get { return _total_network_capacity; } + set + { + if (_total_network_capacity != value) + { + _total_network_capacity = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("avg_channel_size", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double? Avg_channel_size + { + get { return _avg_channel_size; } + set + { + if (_avg_channel_size != value) + { + _avg_channel_size = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("min_channel_size", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Min_channel_size + { + get { return _min_channel_size; } + set + { + if (_min_channel_size != value) + { + _min_channel_size = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("max_channel_size", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Max_channel_size + { + get { return _max_channel_size; } + set + { + if (_max_channel_size != value) + { + _max_channel_size = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcNetworkInfo FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcNewAddressResponse : System.ComponentModel.INotifyPropertyChanged + { + private string _address; + + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Address + { + get { return _address; } + set + { + if (_address != value) + { + _address = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcNewAddressResponse FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcNodeAddress : System.ComponentModel.INotifyPropertyChanged + { + private string _network; + private string _addr; + + [Newtonsoft.Json.JsonProperty("network", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Network + { + get { return _network; } + set + { + if (_network != value) + { + _network = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("addr", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Addr + { + get { return _addr; } + set + { + if (_addr != value) + { + _addr = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcNodeAddress FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcNodeInfo : System.ComponentModel.INotifyPropertyChanged + { + private LnrpcLightningNode _node; + private long? _num_channels; + private string _total_capacity; + + /// * + /// An individual vertex/node within the channel graph. A node is + /// connected to other nodes by one or more channel edges emanating from it. As + /// the graph is directed, a node will also have an incoming edge attached to + /// it for each outgoing edge. + [Newtonsoft.Json.JsonProperty("node", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public LnrpcLightningNode Node + { + get { return _node; } + set + { + if (_node != value) + { + _node = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("num_channels", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Num_channels + { + get { return _num_channels; } + set + { + if (_num_channels != value) + { + _num_channels = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("total_capacity", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Total_capacity + { + get { return _total_capacity; } + set + { + if (_total_capacity != value) + { + _total_capacity = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcNodeInfo FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcNodeUpdate : System.ComponentModel.INotifyPropertyChanged + { + private System.Collections.ObjectModel.ObservableCollection _addresses; + private string _identity_key; + private byte[] _global_features; + private string _alias; + + [Newtonsoft.Json.JsonProperty("addresses", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Addresses + { + get { return _addresses; } + set + { + if (_addresses != value) + { + _addresses = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("identity_key", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Identity_key + { + get { return _identity_key; } + set + { + if (_identity_key != value) + { + _identity_key = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("global_features", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public byte[] Global_features + { + get { return _global_features; } + set + { + if (_global_features != value) + { + _global_features = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("alias", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Alias + { + get { return _alias; } + set + { + if (_alias != value) + { + _alias = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcNodeUpdate FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcOpenChannelRequest : System.ComponentModel.INotifyPropertyChanged + { + private byte[] _node_pubkey; + private string _node_pubkey_string; + private string _local_funding_amount; + private string _push_sat; + private int? _target_conf; + private string _sat_per_byte; + private bool? _private; + private string _min_htlc_msat; + private long? _remote_csv_delay; + + [Newtonsoft.Json.JsonProperty("node_pubkey", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public byte[] Node_pubkey + { + get { return _node_pubkey; } + set + { + if (_node_pubkey != value) + { + _node_pubkey = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("node_pubkey_string", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Node_pubkey_string + { + get { return _node_pubkey_string; } + set + { + if (_node_pubkey_string != value) + { + _node_pubkey_string = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("local_funding_amount", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Local_funding_amount + { + get { return _local_funding_amount; } + set + { + if (_local_funding_amount != value) + { + _local_funding_amount = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("push_sat", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Push_sat + { + get { return _push_sat; } + set + { + if (_push_sat != value) + { + _push_sat = value; + RaisePropertyChanged(); + } + } + } + + /// / The target number of blocks that the funding transaction should be confirmed by. + [Newtonsoft.Json.JsonProperty("target_conf", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int? Target_conf + { + get { return _target_conf; } + set + { + if (_target_conf != value) + { + _target_conf = value; + RaisePropertyChanged(); + } + } + } + + /// / A manual fee rate set in sat/byte that should be used when crafting the funding transaction. + [Newtonsoft.Json.JsonProperty("sat_per_byte", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Sat_per_byte + { + get { return _sat_per_byte; } + set + { + if (_sat_per_byte != value) + { + _sat_per_byte = value; + RaisePropertyChanged(); + } + } + } + + /// / Whether this channel should be private, not announced to the greater network. + [Newtonsoft.Json.JsonProperty("private", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool? Private + { + get { return _private; } + set + { + if (_private != value) + { + _private = value; + RaisePropertyChanged(); + } + } + } + + /// / The minimum value in millisatoshi we will require for incoming HTLCs on the channel. + [Newtonsoft.Json.JsonProperty("min_htlc_msat", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Min_htlc_msat + { + get { return _min_htlc_msat; } + set + { + if (_min_htlc_msat != value) + { + _min_htlc_msat = value; + RaisePropertyChanged(); + } + } + } + + /// / The delay we require on the remote's commitment transaction. If this is not set, it will be scaled automatically with the channel size. + [Newtonsoft.Json.JsonProperty("remote_csv_delay", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Remote_csv_delay + { + get { return _remote_csv_delay; } + set + { + if (_remote_csv_delay != value) + { + _remote_csv_delay = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcOpenChannelRequest FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcOpenStatusUpdate : System.ComponentModel.INotifyPropertyChanged + { + private LnrpcPendingUpdate _chan_pending; + private LnrpcConfirmationUpdate _confirmation; + private LnrpcChannelOpenUpdate _chan_open; + + [Newtonsoft.Json.JsonProperty("chan_pending", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public LnrpcPendingUpdate Chan_pending + { + get { return _chan_pending; } + set + { + if (_chan_pending != value) + { + _chan_pending = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("confirmation", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public LnrpcConfirmationUpdate Confirmation + { + get { return _confirmation; } + set + { + if (_confirmation != value) + { + _confirmation = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("chan_open", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public LnrpcChannelOpenUpdate Chan_open + { + get { return _chan_open; } + set + { + if (_chan_open != value) + { + _chan_open = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcOpenStatusUpdate FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcPayReq : System.ComponentModel.INotifyPropertyChanged + { + private string _destination; + private string _payment_hash; + private string _num_satoshis; + private string _timestamp; + private string _expiry; + private string _description; + private string _description_hash; + private string _fallback_addr; + private string _cltv_expiry; + private System.Collections.ObjectModel.ObservableCollection _route_hints; + + [Newtonsoft.Json.JsonProperty("destination", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Destination + { + get { return _destination; } + set + { + if (_destination != value) + { + _destination = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("payment_hash", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Payment_hash + { + get { return _payment_hash; } + set + { + if (_payment_hash != value) + { + _payment_hash = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("num_satoshis", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Num_satoshis + { + get { return _num_satoshis; } + set + { + if (_num_satoshis != value) + { + _num_satoshis = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("timestamp", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Timestamp + { + get { return _timestamp; } + set + { + if (_timestamp != value) + { + _timestamp = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("expiry", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Expiry + { + get { return _expiry; } + set + { + if (_expiry != value) + { + _expiry = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("description", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Description + { + get { return _description; } + set + { + if (_description != value) + { + _description = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("description_hash", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Description_hash + { + get { return _description_hash; } + set + { + if (_description_hash != value) + { + _description_hash = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("fallback_addr", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Fallback_addr + { + get { return _fallback_addr; } + set + { + if (_fallback_addr != value) + { + _fallback_addr = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("cltv_expiry", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Cltv_expiry + { + get { return _cltv_expiry; } + set + { + if (_cltv_expiry != value) + { + _cltv_expiry = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("route_hints", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Route_hints + { + get { return _route_hints; } + set + { + if (_route_hints != value) + { + _route_hints = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcPayReq FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcPayment : System.ComponentModel.INotifyPropertyChanged + { + private string _payment_hash; + private string _value; + private string _creation_date; + private System.Collections.ObjectModel.ObservableCollection _path; + private string _fee; + private string _payment_preimage; + + [Newtonsoft.Json.JsonProperty("payment_hash", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Payment_hash + { + get { return _payment_hash; } + set + { + if (_payment_hash != value) + { + _payment_hash = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("value", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Value + { + get { return _value; } + set + { + if (_value != value) + { + _value = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("creation_date", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Creation_date + { + get { return _creation_date; } + set + { + if (_creation_date != value) + { + _creation_date = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("path", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Path + { + get { return _path; } + set + { + if (_path != value) + { + _path = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("fee", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Fee + { + get { return _fee; } + set + { + if (_fee != value) + { + _fee = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("payment_preimage", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Payment_preimage + { + get { return _payment_preimage; } + set + { + if (_payment_preimage != value) + { + _payment_preimage = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcPayment FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcPeer : System.ComponentModel.INotifyPropertyChanged + { + private string _pub_key; + private string _address; + private string _bytes_sent; + private string _bytes_recv; + private string _sat_sent; + private string _sat_recv; + private bool? _inbound; + private string _ping_time; + + [Newtonsoft.Json.JsonProperty("pub_key", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Pub_key + { + get { return _pub_key; } + set + { + if (_pub_key != value) + { + _pub_key = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("address", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Address + { + get { return _address; } + set + { + if (_address != value) + { + _address = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("bytes_sent", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Bytes_sent + { + get { return _bytes_sent; } + set + { + if (_bytes_sent != value) + { + _bytes_sent = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("bytes_recv", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Bytes_recv + { + get { return _bytes_recv; } + set + { + if (_bytes_recv != value) + { + _bytes_recv = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("sat_sent", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Sat_sent + { + get { return _sat_sent; } + set + { + if (_sat_sent != value) + { + _sat_sent = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("sat_recv", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Sat_recv + { + get { return _sat_recv; } + set + { + if (_sat_recv != value) + { + _sat_recv = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("inbound", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool? Inbound + { + get { return _inbound; } + set + { + if (_inbound != value) + { + _inbound = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("ping_time", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Ping_time + { + get { return _ping_time; } + set + { + if (_ping_time != value) + { + _ping_time = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcPeer FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcPendingChannelsResponse : System.ComponentModel.INotifyPropertyChanged + { + private string _total_limbo_balance; + private System.Collections.ObjectModel.ObservableCollection _pending_open_channels; + private System.Collections.ObjectModel.ObservableCollection _pending_closing_channels; + private System.Collections.ObjectModel.ObservableCollection _pending_force_closing_channels; + private System.Collections.ObjectModel.ObservableCollection _waiting_close_channels; + + [Newtonsoft.Json.JsonProperty("total_limbo_balance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Total_limbo_balance + { + get { return _total_limbo_balance; } + set + { + if (_total_limbo_balance != value) + { + _total_limbo_balance = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("pending_open_channels", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Pending_open_channels + { + get { return _pending_open_channels; } + set + { + if (_pending_open_channels != value) + { + _pending_open_channels = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("pending_closing_channels", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Pending_closing_channels + { + get { return _pending_closing_channels; } + set + { + if (_pending_closing_channels != value) + { + _pending_closing_channels = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("pending_force_closing_channels", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Pending_force_closing_channels + { + get { return _pending_force_closing_channels; } + set + { + if (_pending_force_closing_channels != value) + { + _pending_force_closing_channels = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("waiting_close_channels", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Waiting_close_channels + { + get { return _waiting_close_channels; } + set + { + if (_waiting_close_channels != value) + { + _waiting_close_channels = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcPendingChannelsResponse FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcPendingHTLC : System.ComponentModel.INotifyPropertyChanged + { + private bool? _incoming; + private string _amount; + private string _outpoint; + private long? _maturity_height; + private int? _blocks_til_maturity; + private long? _stage; + + [Newtonsoft.Json.JsonProperty("incoming", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool? Incoming + { + get { return _incoming; } + set + { + if (_incoming != value) + { + _incoming = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("amount", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Amount + { + get { return _amount; } + set + { + if (_amount != value) + { + _amount = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("outpoint", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Outpoint + { + get { return _outpoint; } + set + { + if (_outpoint != value) + { + _outpoint = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("maturity_height", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Maturity_height + { + get { return _maturity_height; } + set + { + if (_maturity_height != value) + { + _maturity_height = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// The number of blocks remaining until the current stage can be swept. + /// Negative values indicate how many blocks have passed since becoming + /// mature. + [Newtonsoft.Json.JsonProperty("blocks_til_maturity", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int? Blocks_til_maturity + { + get { return _blocks_til_maturity; } + set + { + if (_blocks_til_maturity != value) + { + _blocks_til_maturity = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("stage", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Stage + { + get { return _stage; } + set + { + if (_stage != value) + { + _stage = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcPendingHTLC FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcPendingUpdate : System.ComponentModel.INotifyPropertyChanged + { + private byte[] _txid; + private long? _output_index; + + [Newtonsoft.Json.JsonProperty("txid", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public byte[] Txid + { + get { return _txid; } + set + { + if (_txid != value) + { + _txid = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("output_index", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Output_index + { + get { return _output_index; } + set + { + if (_output_index != value) + { + _output_index = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcPendingUpdate FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcPolicyUpdateRequest : System.ComponentModel.INotifyPropertyChanged + { + private bool? _global; + private LnrpcChannelPoint _chan_point; + private string _base_fee_msat; + private double? _fee_rate; + private long? _time_lock_delta; + + /// / If set, then this update applies to all currently active channels. + [Newtonsoft.Json.JsonProperty("global", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool? Global + { + get { return _global; } + set + { + if (_global != value) + { + _global = value; + RaisePropertyChanged(); + } + } + } + + /// / If set, this update will target a specific channel. + [Newtonsoft.Json.JsonProperty("chan_point", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public LnrpcChannelPoint Chan_point + { + get { return _chan_point; } + set + { + if (_chan_point != value) + { + _chan_point = value; + RaisePropertyChanged(); + } + } + } + + /// / The base fee charged regardless of the number of milli-satoshis sent. + [Newtonsoft.Json.JsonProperty("base_fee_msat", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Base_fee_msat + { + get { return _base_fee_msat; } + set + { + if (_base_fee_msat != value) + { + _base_fee_msat = value; + RaisePropertyChanged(); + } + } + } + + /// / The effective fee rate in milli-satoshis. The precision of this value goes up to 6 decimal places, so 1e-6. + [Newtonsoft.Json.JsonProperty("fee_rate", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public double? Fee_rate + { + get { return _fee_rate; } + set + { + if (_fee_rate != value) + { + _fee_rate = value; + RaisePropertyChanged(); + } + } + } + + /// / The required timelock delta for HTLCs forwarded over the channel. + [Newtonsoft.Json.JsonProperty("time_lock_delta", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Time_lock_delta + { + get { return _time_lock_delta; } + set + { + if (_time_lock_delta != value) + { + _time_lock_delta = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcPolicyUpdateRequest FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcQueryRoutesResponse : System.ComponentModel.INotifyPropertyChanged + { + private System.Collections.ObjectModel.ObservableCollection _routes; + + [Newtonsoft.Json.JsonProperty("routes", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Routes + { + get { return _routes; } + set + { + if (_routes != value) + { + _routes = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcQueryRoutesResponse FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + /// * + /// A path through the channel graph which runs over one or more channels in + /// succession. This struct carries all the information required to craft the + /// Sphinx onion packet, and send the payment along the first hop in the path. A + /// route is only selected as valid if all the channels have sufficient capacity to + /// carry the initial payment amount after fees are accounted for. + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcRoute : System.ComponentModel.INotifyPropertyChanged + { + private long? _total_time_lock; + private string _total_fees; + private string _total_amt; + private System.Collections.ObjectModel.ObservableCollection _hops; + private string _total_fees_msat; + private string _total_amt_msat; + + /// * + /// The cumulative (final) time lock across the entire route. This is the CLTV + /// value that should be extended to the first hop in the route. All other hops + /// will decrement the time-lock as advertised, leaving enough time for all + /// hops to wait for or present the payment preimage to complete the payment. + [Newtonsoft.Json.JsonProperty("total_time_lock", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Total_time_lock + { + get { return _total_time_lock; } + set + { + if (_total_time_lock != value) + { + _total_time_lock = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// The sum of the fees paid at each hop within the final route. In the case + /// of a one-hop payment, this value will be zero as we don't need to pay a fee + /// it ourself. + [Newtonsoft.Json.JsonProperty("total_fees", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Total_fees + { + get { return _total_fees; } + set + { + if (_total_fees != value) + { + _total_fees = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// The total amount of funds required to complete a payment over this route. + /// This value includes the cumulative fees at each hop. As a result, the HTLC + /// extended to the first-hop in the route will need to have at least this many + /// satoshis, otherwise the route will fail at an intermediate node due to an + /// insufficient amount of fees. + [Newtonsoft.Json.JsonProperty("total_amt", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Total_amt + { + get { return _total_amt; } + set + { + if (_total_amt != value) + { + _total_amt = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// Contains details concerning the specific forwarding details at each hop. + [Newtonsoft.Json.JsonProperty("hops", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Hops + { + get { return _hops; } + set + { + if (_hops != value) + { + _hops = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// The total fees in millisatoshis. + [Newtonsoft.Json.JsonProperty("total_fees_msat", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Total_fees_msat + { + get { return _total_fees_msat; } + set + { + if (_total_fees_msat != value) + { + _total_fees_msat = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// The total amount in millisatoshis. + [Newtonsoft.Json.JsonProperty("total_amt_msat", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Total_amt_msat + { + get { return _total_amt_msat; } + set + { + if (_total_amt_msat != value) + { + _total_amt_msat = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcRoute FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcRouteHint : System.ComponentModel.INotifyPropertyChanged + { + private System.Collections.ObjectModel.ObservableCollection _hop_hints; + + /// * + /// A list of hop hints that when chained together can assist in reaching a + /// specific destination. + [Newtonsoft.Json.JsonProperty("hop_hints", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Hop_hints + { + get { return _hop_hints; } + set + { + if (_hop_hints != value) + { + _hop_hints = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcRouteHint FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcRoutingPolicy : System.ComponentModel.INotifyPropertyChanged + { + private long? _time_lock_delta; + private string _min_htlc; + private string _fee_base_msat; + private string _fee_rate_milli_msat; + + [Newtonsoft.Json.JsonProperty("time_lock_delta", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public long? Time_lock_delta + { + get { return _time_lock_delta; } + set + { + if (_time_lock_delta != value) + { + _time_lock_delta = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("min_htlc", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Min_htlc + { + get { return _min_htlc; } + set + { + if (_min_htlc != value) + { + _min_htlc = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("fee_base_msat", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Fee_base_msat + { + get { return _fee_base_msat; } + set + { + if (_fee_base_msat != value) + { + _fee_base_msat = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("fee_rate_milli_msat", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Fee_rate_milli_msat + { + get { return _fee_rate_milli_msat; } + set + { + if (_fee_rate_milli_msat != value) + { + _fee_rate_milli_msat = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcRoutingPolicy FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcSendCoinsRequest : System.ComponentModel.INotifyPropertyChanged + { + private string _addr; + private string _amount; + private int? _target_conf; + private string _sat_per_byte; + + [Newtonsoft.Json.JsonProperty("addr", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Addr + { + get { return _addr; } + set + { + if (_addr != value) + { + _addr = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("amount", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Amount + { + get { return _amount; } + set + { + if (_amount != value) + { + _amount = value; + RaisePropertyChanged(); + } + } + } + + /// / The target number of blocks that this transaction should be confirmed by. + [Newtonsoft.Json.JsonProperty("target_conf", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int? Target_conf + { + get { return _target_conf; } + set + { + if (_target_conf != value) + { + _target_conf = value; + RaisePropertyChanged(); + } + } + } + + /// / A manual fee rate set in sat/byte that should be used when crafting the transaction. + [Newtonsoft.Json.JsonProperty("sat_per_byte", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Sat_per_byte + { + get { return _sat_per_byte; } + set + { + if (_sat_per_byte != value) + { + _sat_per_byte = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcSendCoinsRequest FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcSendCoinsResponse : System.ComponentModel.INotifyPropertyChanged + { + private string _txid; + + [Newtonsoft.Json.JsonProperty("txid", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Txid + { + get { return _txid; } + set + { + if (_txid != value) + { + _txid = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcSendCoinsResponse FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcSendManyResponse : System.ComponentModel.INotifyPropertyChanged + { + private string _txid; + + [Newtonsoft.Json.JsonProperty("txid", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Txid + { + get { return _txid; } + set + { + if (_txid != value) + { + _txid = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcSendManyResponse FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcSendRequest : System.ComponentModel.INotifyPropertyChanged + { + private byte[] _dest; + private string _dest_string; + private string _amt; + private byte[] _payment_hash; + private string _payment_hash_string; + private string _payment_request; + private int? _final_cltv_delta; + + [Newtonsoft.Json.JsonProperty("dest", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public byte[] Dest + { + get { return _dest; } + set + { + if (_dest != value) + { + _dest = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("dest_string", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Dest_string + { + get { return _dest_string; } + set + { + if (_dest_string != value) + { + _dest_string = value; + RaisePropertyChanged(); + } + } + } + + /// / Number of satoshis to send. + [Newtonsoft.Json.JsonProperty("amt", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Amt + { + get { return _amt; } + set + { + if (_amt != value) + { + _amt = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("payment_hash", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public byte[] Payment_hash + { + get { return _payment_hash; } + set + { + if (_payment_hash != value) + { + _payment_hash = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("payment_hash_string", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Payment_hash_string + { + get { return _payment_hash_string; } + set + { + if (_payment_hash_string != value) + { + _payment_hash_string = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// A bare-bones invoice for a payment within the Lightning Network. With the + /// details of the invoice, the sender has all the data necessary to send a + /// payment to the recipient. + [Newtonsoft.Json.JsonProperty("payment_request", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Payment_request + { + get { return _payment_request; } + set + { + if (_payment_request != value) + { + _payment_request = value; + RaisePropertyChanged(); + } + } + } + + /// / The CLTV delta from the current height that should be used to set the timelock for the final hop. + [Newtonsoft.Json.JsonProperty("final_cltv_delta", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int? Final_cltv_delta + { + get { return _final_cltv_delta; } + set + { + if (_final_cltv_delta != value) + { + _final_cltv_delta = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcSendRequest FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcSendResponse : System.ComponentModel.INotifyPropertyChanged + { + private string _payment_error; + private byte[] _payment_preimage; + private LnrpcRoute _payment_route; + + [Newtonsoft.Json.JsonProperty("payment_error", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Payment_error + { + get { return _payment_error; } + set + { + if (_payment_error != value) + { + _payment_error = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("payment_preimage", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public byte[] Payment_preimage + { + get { return _payment_preimage; } + set + { + if (_payment_preimage != value) + { + _payment_preimage = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("payment_route", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public LnrpcRoute Payment_route + { + get { return _payment_route; } + set + { + if (_payment_route != value) + { + _payment_route = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcSendResponse FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcSignMessageResponse : System.ComponentModel.INotifyPropertyChanged + { + private string _signature; + + [Newtonsoft.Json.JsonProperty("signature", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Signature + { + get { return _signature; } + set + { + if (_signature != value) + { + _signature = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcSignMessageResponse FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcTransaction : System.ComponentModel.INotifyPropertyChanged + { + private string _tx_hash; + private string _amount; + private int? _num_confirmations; + private string _block_hash; + private int? _block_height; + private string _time_stamp; + private string _total_fees; + private System.Collections.ObjectModel.ObservableCollection _dest_addresses; + + [Newtonsoft.Json.JsonProperty("tx_hash", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Tx_hash + { + get { return _tx_hash; } + set + { + if (_tx_hash != value) + { + _tx_hash = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("amount", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Amount + { + get { return _amount; } + set + { + if (_amount != value) + { + _amount = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("num_confirmations", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int? Num_confirmations + { + get { return _num_confirmations; } + set + { + if (_num_confirmations != value) + { + _num_confirmations = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("block_hash", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Block_hash + { + get { return _block_hash; } + set + { + if (_block_hash != value) + { + _block_hash = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("block_height", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int? Block_height + { + get { return _block_height; } + set + { + if (_block_height != value) + { + _block_height = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("time_stamp", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Time_stamp + { + get { return _time_stamp; } + set + { + if (_time_stamp != value) + { + _time_stamp = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("total_fees", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Total_fees + { + get { return _total_fees; } + set + { + if (_total_fees != value) + { + _total_fees = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("dest_addresses", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Dest_addresses + { + get { return _dest_addresses; } + set + { + if (_dest_addresses != value) + { + _dest_addresses = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcTransaction FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcTransactionDetails : System.ComponentModel.INotifyPropertyChanged + { + private System.Collections.ObjectModel.ObservableCollection _transactions; + + /// / The list of transactions relevant to the wallet. + [Newtonsoft.Json.JsonProperty("transactions", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public System.Collections.ObjectModel.ObservableCollection Transactions + { + get { return _transactions; } + set + { + if (_transactions != value) + { + _transactions = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcTransactionDetails FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcUnlockWalletRequest : System.ComponentModel.INotifyPropertyChanged + { + private byte[] _wallet_password; + private int? _recovery_window; + + /// * + /// wallet_password should be the current valid passphrase for the daemon. This + /// will be required to decrypt on-disk material that the daemon requires to + /// function properly. + [Newtonsoft.Json.JsonProperty("wallet_password", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public byte[] Wallet_password + { + get { return _wallet_password; } + set + { + if (_wallet_password != value) + { + _wallet_password = value; + RaisePropertyChanged(); + } + } + } + + /// * + /// recovery_window is an optional argument specifying the address lookahead + /// when restoring a wallet seed. The recovery window applies to each + /// invdividual branch of the BIP44 derivation paths. Supplying a recovery + /// window of zero indicates that no addresses should be recovered, such after + /// the first initialization of the wallet. + [Newtonsoft.Json.JsonProperty("recovery_window", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public int? Recovery_window + { + get { return _recovery_window; } + set + { + if (_recovery_window != value) + { + _recovery_window = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcUnlockWalletRequest FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcVerifyMessageResponse : System.ComponentModel.INotifyPropertyChanged + { + private bool? _valid; + private string _pubkey; + + [Newtonsoft.Json.JsonProperty("valid", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public bool? Valid + { + get { return _valid; } + set + { + if (_valid != value) + { + _valid = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("pubkey", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Pubkey + { + get { return _pubkey; } + set + { + if (_pubkey != value) + { + _pubkey = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcVerifyMessageResponse FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "9.9.11.0 (Newtonsoft.Json v9.0.0.0)")] + public partial class LnrpcWalletBalanceResponse : System.ComponentModel.INotifyPropertyChanged + { + private string _total_balance; + private string _confirmed_balance; + private string _unconfirmed_balance; + + [Newtonsoft.Json.JsonProperty("total_balance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Total_balance + { + get { return _total_balance; } + set + { + if (_total_balance != value) + { + _total_balance = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("confirmed_balance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Confirmed_balance + { + get { return _confirmed_balance; } + set + { + if (_confirmed_balance != value) + { + _confirmed_balance = value; + RaisePropertyChanged(); + } + } + } + + [Newtonsoft.Json.JsonProperty("unconfirmed_balance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] + public string Unconfirmed_balance + { + get { return _unconfirmed_balance; } + set + { + if (_unconfirmed_balance != value) + { + _unconfirmed_balance = value; + RaisePropertyChanged(); + } + } + } + + public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged; + + public string ToJson() + { + return Newtonsoft.Json.JsonConvert.SerializeObject(this); + } + + public static LnrpcWalletBalanceResponse FromJson(string data) + { + return Newtonsoft.Json.JsonConvert.DeserializeObject(data); + } + + protected virtual void RaisePropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = null) + { + var handler = PropertyChanged; + if (handler != null) + handler(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName)); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "11.11.1.0")] + public class SwaggerException : System.Exception + { + public string StatusCode { get; private set; } + + public string Response { get; private set; } + + public System.Collections.Generic.Dictionary> Headers { get; private set; } + + public SwaggerException(string message, string statusCode, string response, System.Collections.Generic.Dictionary> headers, System.Exception innerException) + : base(message, innerException) + { + StatusCode = statusCode; + Response = response; + Headers = headers; + + Debug.WriteLine($"SwaggerException: {response}"); + } + + public override string ToString() + { + return string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString()); + } + } + + [System.CodeDom.Compiler.GeneratedCode("NSwag", "11.11.1.0")] + public class SwaggerException : SwaggerException + { + public TResult Result { get; private set; } + + public SwaggerException(string message, string statusCode, string response, System.Collections.Generic.Dictionary> headers, TResult result, System.Exception innerException) + : base(message, statusCode, response, headers, innerException) + { + Result = result; + } + } + +} diff --git a/BTCPayServer/Payments/Lightning/Lnd/LndSwaggerClient.partial.cs b/BTCPayServer/Payments/Lightning/Lnd/LndSwaggerClient.partial.cs new file mode 100644 index 000000000..3ad72ee10 --- /dev/null +++ b/BTCPayServer/Payments/Lightning/Lnd/LndSwaggerClient.partial.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.Security; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using NBitcoin.DataEncoders; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace BTCPayServer.Payments.Lightning.Lnd +{ + public class LndException : Exception + { + public LndException(string message) : base(message) + { + + } + public LndException(LndError error) : base(error.Message) + { + if (error == null) + throw new ArgumentNullException(nameof(error)); + _Error = error; + } + + + private readonly LndError _Error; + public LndError Error + { + get + { + return _Error; + } + } + } + // {"grpc_code":2,"http_code":500,"message":"rpc error: code = Unknown desc = expected 1 macaroon, got 0","http_status":"Internal Server Error"} + public class LndError + { + [JsonProperty("grpc_code")] + public int GRPCCode { get; set; } + [JsonProperty("http_code")] + public int HttpCode { get; set; } + [JsonProperty("message")] + public string Message { get; set; } + [JsonProperty("http_status")] + public string HttpStatus { get; set; } + } + public partial class LndSwaggerClient + { + public LndSwaggerClient(LndRestSettings settings) + { + if (settings == null) + throw new ArgumentNullException(nameof(settings)); + _LndSettings = settings; + _Authentication = settings.CreateLndAuthentication(); + BaseUrl = settings.Uri.AbsoluteUri.TrimEnd('/'); + _httpClient = CreateHttpClient(settings); + _settings = new System.Lazy(() => + { + var json = new Newtonsoft.Json.JsonSerializerSettings(); + UpdateJsonSerializerSettings(json); + return json; + }); + } + LndRestSettings _LndSettings; + internal LndAuthentication _Authentication; + + partial void PrepareRequest(HttpClient client, HttpRequestMessage request, string url) + { + _Authentication.AddAuthentication(request); + } + + internal static HttpClient CreateHttpClient(LndRestSettings settings) + { + var handler = new HttpClientHandler + { + SslProtocols = SslProtocols.Tls12 + }; + + var expectedThumbprint = settings.CertificateThumbprint?.ToArray(); + if (expectedThumbprint != null) + { + handler.ServerCertificateCustomValidationCallback = (request, cert, chain, errors) => + { + var actualCert = chain.ChainElements[chain.ChainElements.Count - 1].Certificate; + var hash = actualCert.GetCertHash(System.Security.Cryptography.HashAlgorithmName.SHA256); + return hash.SequenceEqual(expectedThumbprint); + }; + } + + if (settings.AllowInsecure) + { + handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; + } + else + { + if (settings.Uri.Scheme == "http") + throw new InvalidOperationException("AllowInsecure is set to false, but the URI is not using https"); + } + return new HttpClient(handler); + } + + internal HttpClient CreateHttpClient() + { + return LndSwaggerClient.CreateHttpClient(_LndSettings); + } + + internal T Deserialize(string str) + { + return JsonConvert.DeserializeObject(str, _settings.Value); + } + } +} diff --git a/BTCPayServer/Program.cs b/BTCPayServer/Program.cs index aed3bf2d9..8c6322285 100644 --- a/BTCPayServer/Program.cs +++ b/BTCPayServer/Program.cs @@ -31,9 +31,14 @@ namespace BTCPayServer var logger = loggerFactory.CreateLogger("Configuration"); try { + // This is the only way toat LoadArgs can print to console. Because LoadArgs is called by the HostBuilder before Logs.Configure is called var conf = new DefaultConfiguration() { Logger = logger }.CreateConfiguration(args); if (conf == null) return; + Logs.Configure(loggerFactory); + new BTCPayServerOptions().LoadArgs(conf); + Logs.Configure(null); + ///// host = new WebHostBuilder() .UseKestrel() @@ -64,6 +69,8 @@ namespace BTCPayServer finally { processor.Dispose(); + if(host == null) + Logs.Configuration.LogError("Configuration error"); if (host != null) host.Dispose(); loggerProvider.Dispose(); diff --git a/BTCPayServer/Properties/launchSettings.json b/BTCPayServer/Properties/launchSettings.json index 3ffbd3c4e..62fd14257 100644 --- a/BTCPayServer/Properties/launchSettings.json +++ b/BTCPayServer/Properties/launchSettings.json @@ -3,16 +3,17 @@ "Docker-Regtest": { "commandName": "Project", "launchBrowser": true, - "environmentVariables": { - "BTCPAY_NETWORK": "regtest", - "BTCPAY_LTCEXPLORERURL": "http://127.0.0.1:32838/", - "BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/", - "ASPNETCORE_ENVIRONMENT": "Development", - "BTCPAY_CHAINS": "btc,ltc", - "BTCPAY_BTCLIGHTNING": "http://api-token:foiewnccewuify@127.0.0.1:54938/", - "BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver", - "BTCPAY_BUNDLEJSCSS": "false" - }, + "environmentVariables": { + "BTCPAY_NETWORK": "regtest", + "BTCPAY_LTCEXPLORERURL": "http://127.0.0.1:32838/", + "BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/", + "ASPNETCORE_ENVIRONMENT": "Development", + "BTCPAY_CHAINS": "btc,ltc", + "BTCPAY_BTCLIGHTNING": "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify", + "BTCPAY_BTCEXTERNALLNDGRPC": "type=lnd-grpc;server=https://lnd:lnd@127.0.0.1:53280/;allowinsecure=true", + "BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver", + "BTCPAY_BUNDLEJSCSS": "false" + }, "applicationUrl": "http://127.0.0.1:14142/" } } diff --git a/BTCPayServer/Security/ContentSecurityPolicies.cs b/BTCPayServer/Security/ContentSecurityPolicies.cs new file mode 100644 index 000000000..3411ca65c --- /dev/null +++ b/BTCPayServer/Security/ContentSecurityPolicies.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BTCPayServer.Security +{ + public class ConsentSecurityPolicy + { + public ConsentSecurityPolicy(string name, string value) + { + _Value = value; + _Name = name; + } + + + private readonly string _Name; + public string Name + { + get + { + return _Name; + } + } + + private readonly string _Value; + public string Value + { + get + { + return _Value; + } + } + + + public override bool Equals(object obj) + { + ConsentSecurityPolicy item = obj as ConsentSecurityPolicy; + if (item == null) + return false; + return GetHashCode().Equals(item.GetHashCode()); + } + public static bool operator ==(ConsentSecurityPolicy a, ConsentSecurityPolicy b) + { + if (System.Object.ReferenceEquals(a, b)) + return true; + if (((object)a == null) || ((object)b == null)) + return false; + return a.GetHashCode() == b.GetHashCode(); + } + + public static bool operator !=(ConsentSecurityPolicy a, ConsentSecurityPolicy b) + { + return !(a == b); + } + + public override int GetHashCode() + { + return HashCode.Combine(Name, Value); + } + } + public class ContentSecurityPolicies + { + public ContentSecurityPolicies() + { + + } + HashSet _Policies = new HashSet(); + public void Add(ConsentSecurityPolicy policy) + { + if (_Policies.Any(p => p.Name == policy.Name && p.Value == policy.Name)) + return; + _Policies.Add(policy); + } + + public IEnumerable Rules => _Policies; + public bool HasRules => _Policies.Count != 0; + + public override string ToString() + { + StringBuilder value = new StringBuilder(); + bool firstGroup = true; + foreach(var group in Rules.GroupBy(r => r.Name)) + { + if (!firstGroup) + { + value.Append(';'); + } + List values = new List(); + values.Add(group.Key); + foreach (var v in group) + { + values.Add(v.Value); + } + foreach(var i in authorized) + { + values.Add(i); + } + value.Append(String.Join(" ", values.OfType().ToArray())); + firstGroup = false; + } + return value.ToString(); + } + + internal void Clear() + { + authorized.Clear(); + _Policies.Clear(); + } + + HashSet authorized = new HashSet(); + internal void AddAllAuthorized(string v) + { + authorized.Add(v); + } + + public IEnumerable Authorized => authorized; + } +} diff --git a/BTCPayServer/Services/BTCPayServerEnvironment.cs b/BTCPayServer/Services/BTCPayServerEnvironment.cs index a8e5d1105..bdcb187d9 100644 --- a/BTCPayServer/Services/BTCPayServerEnvironment.cs +++ b/BTCPayServer/Services/BTCPayServerEnvironment.cs @@ -7,13 +7,17 @@ using System.Threading.Tasks; using System.Text; using NBXplorer; using NBitcoin; +using Microsoft.AspNetCore.Http; namespace BTCPayServer.Services { public class BTCPayServerEnvironment { - public BTCPayServerEnvironment(IHostingEnvironment env, BTCPayNetworkProvider provider) + public BTCPayServerEnvironment(IHostingEnvironment env, BTCPayNetworkProvider provider, IHttpContextAccessor httpContext) { + ExpectedHost = httpContext.HttpContext.Request.Host.Value; + ExpectedDomain = httpContext.HttpContext.Request.Host.Host; + ExpectedProtocol = httpContext.HttpContext.Request.Scheme; Version = typeof(BTCPayServerEnvironment).GetTypeInfo().Assembly.GetCustomAttribute().Version; #if DEBUG Build = "Debug"; @@ -27,6 +31,11 @@ namespace BTCPayServer.Services { get; set; } + + public string ExpectedDomain { get; set; } + public string ExpectedHost { get; set; } + public string ExpectedProtocol { get; set; } + public NetworkType NetworkType { get; set; } public string Version { diff --git a/BTCPayServer/Services/HardwareWalletService.cs b/BTCPayServer/Services/HardwareWalletService.cs index c7695a365..733b7f2f2 100644 --- a/BTCPayServer/Services/HardwareWalletService.cs +++ b/BTCPayServer/Services/HardwareWalletService.cs @@ -20,9 +20,9 @@ namespace BTCPayServer.Services public HardwareWalletException(string message) : base(message) { } public HardwareWalletException(string message, Exception inner) : base(message, inner) { } } - public class HardwareWalletService + public class HardwareWalletService : IDisposable { - class WebSocketTransport : LedgerWallet.Transports.ILedgerTransport + class WebSocketTransport : LedgerWallet.Transports.ILedgerTransport, IDisposable { private readonly WebSocket webSocket; @@ -33,26 +33,36 @@ namespace BTCPayServer.Services this.webSocket = webSocket; } - public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(10); - public async Task Exchange(byte[][] apdus) + SemaphoreSlim _Semaphore = new SemaphoreSlim(1, 1); + public async Task Exchange(byte[][] apdus, CancellationToken cancellationToken) { + await _Semaphore.WaitAsync(); List responses = new List(); - using (CancellationTokenSource cts = new CancellationTokenSource(Timeout)) + try { foreach (var apdu in apdus) { - await this.webSocket.SendAsync(new ArraySegment(apdu), WebSocketMessageType.Binary, true, cts.Token); + await this.webSocket.SendAsync(new ArraySegment(apdu), WebSocketMessageType.Binary, true, cancellationToken); } foreach (var apdu in apdus) { byte[] response = new byte[300]; - var result = await this.webSocket.ReceiveAsync(new ArraySegment(response), cts.Token); + var result = await this.webSocket.ReceiveAsync(new ArraySegment(response), cancellationToken); Array.Resize(ref response, result.Count); responses.Add(response); } } + finally + { + _Semaphore.Release(); + } return responses.ToArray(); } + + public void Dispose() + { + _Semaphore.Dispose(); + } } private readonly LedgerClient _Ledger; @@ -72,20 +82,20 @@ namespace BTCPayServer.Services _Ledger = new LedgerClient(_Transport); } - public async Task Test() + public async Task Test(CancellationToken cancellation) { - var version = await _Ledger.GetFirmwareVersionAsync(); + var version = await Ledger.GetFirmwareVersionAsync(cancellation); return new LedgerTestResult() { Success = true }; } - public async Task GetExtPubKey(BTCPayNetwork network, int account) + public async Task GetExtPubKey(BTCPayNetwork network, int account, CancellationToken cancellation) { if (network == null) throw new ArgumentNullException(nameof(network)); var segwit = network.NBitcoinNetwork.Consensus.SupportSegwit; var path = network.GetRootKeyPath().Derive(account, true); - var pubkey = await GetExtPubKey(_Ledger, network, path, false); + var pubkey = await GetExtPubKey(Ledger, network, path, false, cancellation); var derivation = new DerivationStrategyFactory(network.NBitcoinNetwork).CreateDirectDerivationStrategy(pubkey, new DerivationStrategyOptions() { P2SH = segwit, @@ -94,11 +104,11 @@ namespace BTCPayServer.Services return new GetXPubResult() { ExtPubKey = derivation.ToString(), KeyPath = path }; } - private static async Task GetExtPubKey(LedgerClient ledger, BTCPayNetwork network, KeyPath account, bool onlyChaincode) + private static async Task GetExtPubKey(LedgerClient ledger, BTCPayNetwork network, KeyPath account, bool onlyChaincode, CancellationToken cancellation) { try { - var pubKey = await ledger.GetWalletPubKeyAsync(account); + var pubKey = await ledger.GetWalletPubKeyAsync(account, cancellation: cancellation); try { pubKey.GetAddress(network.NBitcoinNetwork); @@ -108,7 +118,7 @@ namespace BTCPayServer.Services if (network.NBitcoinNetwork.NetworkType == NetworkType.Mainnet) throw new Exception($"The opened ledger app does not seems to support {network.NBitcoinNetwork.Name}."); } - var fingerprint = onlyChaincode ? new byte[4] : (await ledger.GetWalletPubKeyAsync(account.Parent)).UncompressedPublicKey.Compress().Hash.ToBytes().Take(4).ToArray(); + var fingerprint = onlyChaincode ? new byte[4] : (await ledger.GetWalletPubKeyAsync(account.Parent, cancellation: cancellation)).UncompressedPublicKey.Compress().Hash.ToBytes().Take(4).ToArray(); var extpubkey = new ExtPubKey(pubKey.UncompressedPublicKey.Compress(), pubKey.ChainCode, (byte)account.Indexes.Length, fingerprint, account.Indexes.Last()).GetWif(network.NBitcoinNetwork); return extpubkey; } @@ -118,10 +128,10 @@ namespace BTCPayServer.Services } } - public async Task GetKeyPath(BTCPayNetwork network, DirectDerivationStrategy directStrategy) + public async Task GetKeyPath(BTCPayNetwork network, DirectDerivationStrategy directStrategy, CancellationToken cancellation) { List derivations = new List(); - if(network.NBitcoinNetwork.Consensus.SupportSegwit) + if (network.NBitcoinNetwork.Consensus.SupportSegwit) derivations.Add(new KeyPath("49'")); derivations.Add(new KeyPath("44'")); KeyPath foundKeyPath = null; @@ -132,7 +142,7 @@ namespace BTCPayServer.Services { try { - var extpubkey = await GetExtPubKey(_Ledger, network, account, true); + var extpubkey = await GetExtPubKey(Ledger, network, account, true, cancellation); if (directStrategy.Root.PubKey == extpubkey.ExtPubKey.PubKey) { foundKeyPath = account; @@ -150,10 +160,16 @@ namespace BTCPayServer.Services public async Task SignTransactionAsync(SignatureRequest[] signatureRequests, Transaction unsigned, - KeyPath changeKeyPath) + KeyPath changeKeyPath, + CancellationToken cancellationToken) { - _Transport.Timeout = TimeSpan.FromMinutes(5); - return await Ledger.SignTransactionAsync(signatureRequests, unsigned, changeKeyPath); + return await Ledger.SignTransactionAsync(signatureRequests, unsigned, changeKeyPath, cancellationToken); + } + + public void Dispose() + { + if (_Transport != null) + _Transport.Dispose(); } } diff --git a/BTCPayServer/Services/Invoices/InvoiceEntity.cs b/BTCPayServer/Services/Invoices/InvoiceEntity.cs index c770e4a27..f0d3826c0 100644 --- a/BTCPayServer/Services/Invoices/InvoiceEntity.cs +++ b/BTCPayServer/Services/Invoices/InvoiceEntity.cs @@ -709,7 +709,8 @@ namespace BTCPayServer.Services.Invoices accounting.Due = Money.Max(accounting.TotalDue - accounting.Paid, Money.Zero); accounting.DueUncapped = accounting.TotalDue - accounting.Paid; accounting.NetworkFee = accounting.TotalDue - totalDueNoNetworkCost; - accounting.MinimumTotalDue = Money.Max(Money.Satoshis(1), Money.Satoshis(accounting.TotalDue.Satoshi * (1.0m - ((decimal)ParentEntity.PaymentTolerance / 100.0m)))); + var minimumTotalDueSatoshi = Math.Max(1.0m, accounting.TotalDue.Satoshi * (1.0m - ((decimal)ParentEntity.PaymentTolerance / 100.0m))); + accounting.MinimumTotalDue = Money.Satoshis(minimumTotalDueSatoshi); return accounting; } diff --git a/BTCPayServer/Services/Invoices/InvoiceRepository.cs b/BTCPayServer/Services/Invoices/InvoiceRepository.cs index 61c79259e..1ef1b4764 100644 --- a/BTCPayServer/Services/Invoices/InvoiceRepository.cs +++ b/BTCPayServer/Services/Invoices/InvoiceRepository.cs @@ -19,6 +19,7 @@ using System.Globalization; using BTCPayServer.Models.InvoicingModels; using BTCPayServer.Logging; using BTCPayServer.Payments; +using System.Data.Common; namespace BTCPayServer.Services.Invoices { @@ -101,7 +102,7 @@ namespace BTCPayServer.Services.Invoices } } - public async Task CreateInvoiceAsync(string storeId, InvoiceEntity invoice, IEnumerable creationLogs, BTCPayNetworkProvider networkProvider) + public async Task CreateInvoiceAsync(string storeId, InvoiceEntity invoice, InvoiceLogs creationLogs, BTCPayNetworkProvider networkProvider) { List textSearch = new List(); invoice = Clone(invoice, null); @@ -147,13 +148,13 @@ namespace BTCPayServer.Services.Invoices } context.PendingInvoices.Add(new PendingInvoiceData() { Id = invoice.Id }); - foreach(var log in creationLogs) + foreach(var log in creationLogs.ToList()) { context.InvoiceEvents.Add(new InvoiceEventData() { InvoiceDataId = invoice.Id, - Message = log, - Timestamp = invoice.InvoiceTime, + Message = log.Log, + Timestamp = log.Timestamp, UniqueId = Encoders.Hex.EncodeData(RandomUtils.GetBytes(10)) }); } @@ -244,7 +245,11 @@ namespace BTCPayServer.Services.Invoices Timestamp = DateTimeOffset.UtcNow, UniqueId = Encoders.Hex.EncodeData(RandomUtils.GetBytes(10)) }); - await context.SaveChangesAsync(); + try + { + await context.SaveChangesAsync(); + } + catch(DbUpdateException) { } // Probably the invoice does not exists anymore } } diff --git a/BTCPayServer/Services/LightningConfigurationProvider.cs b/BTCPayServer/Services/LightningConfigurationProvider.cs new file mode 100644 index 000000000..467fbcbc4 --- /dev/null +++ b/BTCPayServer/Services/LightningConfigurationProvider.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using NBitcoin; + +namespace BTCPayServer.Services +{ + public class LightningConfigurationProvider + { + ConcurrentDictionary _Map = new ConcurrentDictionary(); + public ulong KeepConfig(ulong secret, LightningConfigurations configuration) + { + CleanExpired(); + _Map.AddOrReplace(secret, (DateTimeOffset.UtcNow + TimeSpan.FromMinutes(10), configuration)); + return secret; + } + + public LightningConfigurations GetConfig(ulong secret) + { + CleanExpired(); + if (!_Map.TryGetValue(secret, out var value)) + return null; + return value.config; + } + + private void CleanExpired() + { + foreach(var item in _Map) + { + if(item.Value.expiration < DateTimeOffset.UtcNow) + { + _Map.TryRemove(item.Key, out var unused); + } + } + } + } + + public class LightningConfigurations + { + public List Configurations { get; set; } = new List(); + } + public class LightningConfiguration + { + public string Type { get; set; } + public string CryptoCode { get; set; } + public string Host { get; set; } + public int Port { get; set; } + public bool SSL { get; set; } + public string CertificateThumbprint { get; set; } + public string Macaroon { get; set; } + } +} diff --git a/BTCPayServer/Services/MigrationSettings.cs b/BTCPayServer/Services/MigrationSettings.cs new file mode 100644 index 000000000..1fbf860fa --- /dev/null +++ b/BTCPayServer/Services/MigrationSettings.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace BTCPayServer.Services +{ + public class MigrationSettings + { + public bool UnreachableStoreCheck { get; set; } + public bool DeprecatedLightningConnectionStringCheck { get; set; } + } +} diff --git a/BTCPayServer/Services/Stores/StoreRepository.cs b/BTCPayServer/Services/Stores/StoreRepository.cs index 9f4e0fbee..a920ce236 100644 --- a/BTCPayServer/Services/Stores/StoreRepository.cs +++ b/BTCPayServer/Services/Stores/StoreRepository.cs @@ -112,6 +112,20 @@ namespace BTCPayServer.Services.Stores } } + public async Task CleanUnreachableStores() + { + using (var ctx = _ContextFactory.CreateContext()) + { + if (!ctx.Database.SupportDropForeignKey()) + return; + foreach (var store in await ctx.Stores.Where(s => s.UserStores.Where(u => u.Role == StoreRoles.Owner).Count() == 0).ToArrayAsync()) + { + ctx.Stores.Remove(store); + } + await ctx.SaveChangesAsync(); + } + } + public async Task RemoveStoreUser(string storeId, string userId) { using (var ctx = _ContextFactory.CreateContext()) @@ -120,6 +134,27 @@ namespace BTCPayServer.Services.Stores ctx.UserStore.Add(userStore); ctx.Entry(userStore).State = EntityState.Deleted; await ctx.SaveChangesAsync(); + + } + await DeleteStoreIfOrphan(storeId); + } + + private async Task DeleteStoreIfOrphan(string storeId) + { + using (var ctx = _ContextFactory.CreateContext()) + { + if (ctx.Database.SupportDropForeignKey()) + { + if (await ctx.UserStore.Where(u => u.StoreDataId == storeId && u.Role == StoreRoles.Owner).CountAsync() == 0) + { + var store = await ctx.Stores.FindAsync(storeId); + if (store != null) + { + ctx.Stores.Remove(store); + await ctx.SaveChangesAsync(); + } + } + } } } @@ -158,6 +193,7 @@ namespace BTCPayServer.Services.Stores ctx.UserStore.Remove(storeUser); await ctx.SaveChangesAsync(); } + await DeleteStoreIfOrphan(storeId); } public async Task UpdateStore(StoreData store) @@ -169,5 +205,28 @@ namespace BTCPayServer.Services.Stores await ctx.SaveChangesAsync().ConfigureAwait(false); } } + + public async Task DeleteStore(string storeId) + { + using (var ctx = _ContextFactory.CreateContext()) + { + if (!ctx.Database.SupportDropForeignKey()) + return false; + var store = await ctx.Stores.FindAsync(storeId); + if (store == null) + return false; + ctx.Stores.Remove(store); + await ctx.SaveChangesAsync(); + return true; + } + } + + public bool CanDeleteStores() + { + using (var ctx = _ContextFactory.CreateContext()) + { + return ctx.Database.SupportDropForeignKey(); + } + } } } diff --git a/BTCPayServer/Services/Wallets/BTCPayWallet.cs b/BTCPayServer/Services/Wallets/BTCPayWallet.cs index 2877c91c3..14be0bbf6 100644 --- a/BTCPayServer/Services/Wallets/BTCPayWallet.cs +++ b/BTCPayServer/Services/Wallets/BTCPayWallet.cs @@ -151,6 +151,11 @@ namespace BTCPayServer.Services.Wallets return await completionSource.Task; } + public Task FetchTransactions(DerivationStrategyBase derivationStrategyBase) + { + return _Client.GetTransactionsAsync(derivationStrategyBase, null, false); + } + public Task BroadcastTransactionsAsync(List transactions) { var tasks = transactions.Select(t => _Client.BroadcastAsync(t)).ToArray(); diff --git a/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml b/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml index 766def9b6..81f0109a3 100644 --- a/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml +++ b/BTCPayServer/Views/Apps/UpdatePointOfSale.cshtml @@ -74,8 +74,8 @@ @section Scripts { - - + + } diff --git a/BTCPayServer/Views/Home/Home.cshtml b/BTCPayServer/Views/Home/Home.cshtml index c5851118c..352803a69 100644 --- a/BTCPayServer/Views/Home/Home.cshtml +++ b/BTCPayServer/Views/Home/Home.cshtml @@ -57,11 +57,14 @@

Video tutorials

-
- +
+
+
-
diff --git a/BTCPayServer/Views/Invoice/Checkout-Body.cshtml b/BTCPayServer/Views/Invoice/Checkout-Body.cshtml index aa596e9fc..e73067e82 100644 --- a/BTCPayServer/Views/Invoice/Checkout-Body.cshtml +++ b/BTCPayServer/Views/Invoice/Checkout-Body.cshtml @@ -42,14 +42,30 @@
-
- @foreach (var crypto in Model.AvailableCryptos) - { - - @crypto.PaymentMethodId - - } -
+ @if (Model.AvailableCryptos.Count > 1) + { +
+ + {{srvModel.paymentMethodName}} ({{srvModel.cryptoCode}}) + + +
+
+ +
+ }
diff --git a/BTCPayServer/Views/Invoice/Checkout.cshtml b/BTCPayServer/Views/Invoice/Checkout.cshtml index 26268045f..1ef0a6909 100644 --- a/BTCPayServer/Views/Invoice/Checkout.cshtml +++ b/BTCPayServer/Views/Invoice/Checkout.cshtml @@ -20,12 +20,15 @@ + - - @if(Model.CustomCSSLink != null) + @if (Model.CustomCSSLink != null) { } + +
diff --git a/BTCPayServer/Views/Shared/SyncModal.cshtml b/BTCPayServer/Views/Shared/SyncModal.cshtml index aa456721a..dfc710b08 100644 --- a/BTCPayServer/Views/Shared/SyncModal.cshtml +++ b/BTCPayServer/Views/Shared/SyncModal.cshtml @@ -10,8 +10,8 @@ + + @RenderBody()