mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-19 06:54:19 +01:00
Prepare Eclair integration
This commit is contained in:
37
BTCPayServer.Tests/EclairTester.cs
Normal file
37
BTCPayServer.Tests/EclairTester.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Eclair;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Tests
|
||||||
|
{
|
||||||
|
public class EclairTester
|
||||||
|
{
|
||||||
|
ServerTester parent;
|
||||||
|
public EclairTester(ServerTester parent, string environmentName, string defaultRPC, string defaultHost)
|
||||||
|
{
|
||||||
|
this.parent = parent;
|
||||||
|
RPC = new EclairRPCClient(new Uri(parent.GetEnvironment(environmentName, defaultRPC)), parent.Network);
|
||||||
|
P2PHost = parent.GetEnvironment(environmentName + "_HOST", defaultHost);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EclairRPCClient RPC { get; }
|
||||||
|
public string P2PHost { get; }
|
||||||
|
|
||||||
|
NodeInfo _NodeInfo;
|
||||||
|
public async Task<NodeInfo> GetNodeInfoAsync()
|
||||||
|
{
|
||||||
|
if (_NodeInfo != null)
|
||||||
|
return _NodeInfo;
|
||||||
|
var info = await RPC.GetInfoAsync();
|
||||||
|
_NodeInfo = new NodeInfo(info.NodeId, P2PHost, info.Port);
|
||||||
|
return _NodeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodeInfo GetNodeInfo()
|
||||||
|
{
|
||||||
|
return GetNodeInfoAsync().GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using BTCPayServer.Controllers;
|
using BTCPayServer.Controllers;
|
||||||
|
using System.Linq;
|
||||||
using BTCPayServer.Models.AccountViewModels;
|
using BTCPayServer.Models.AccountViewModels;
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@@ -16,6 +17,7 @@ using System.Runtime.CompilerServices;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using BTCPayServer.Eclair;
|
||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
@@ -56,9 +58,42 @@ namespace BTCPayServer.Tests
|
|||||||
PayTester.Port = int.Parse(GetEnvironment("TESTS_PORT", Utils.FreeTcpPort().ToString()));
|
PayTester.Port = int.Parse(GetEnvironment("TESTS_PORT", Utils.FreeTcpPort().ToString()));
|
||||||
PayTester.HostName = GetEnvironment("TESTS_HOSTNAME", "127.0.0.1");
|
PayTester.HostName = GetEnvironment("TESTS_HOSTNAME", "127.0.0.1");
|
||||||
PayTester.Start();
|
PayTester.Start();
|
||||||
|
|
||||||
|
MerchantEclair = new EclairTester(this, "TEST_ECLAIR1", "http://127.0.0.1:30992/", "eclair1");
|
||||||
|
CustomerEclair = new EclairTester(this, "TEST_ECLAIR2", "http://127.0.0.1:30993/", "eclair2");
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetEnvironment(string variable, string defaultValue)
|
|
||||||
|
/// <summary>
|
||||||
|
/// This will setup a channel going from customer to merchant
|
||||||
|
/// </summary>
|
||||||
|
public void PrepareLightning()
|
||||||
|
{
|
||||||
|
PrepareLightningAsync().GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PrepareLightningAsync()
|
||||||
|
{
|
||||||
|
// Activate segwit
|
||||||
|
var blockCount = ExplorerNode.GetBlockCountAsync();
|
||||||
|
// Fetch node info, but that in cache
|
||||||
|
var merchant = MerchantEclair.GetNodeInfoAsync();
|
||||||
|
var customer = CustomerEclair.GetNodeInfoAsync();
|
||||||
|
var channels = CustomerEclair.RPC.ChannelsAsync();
|
||||||
|
var connect = CustomerEclair.RPC.ConnectAsync(merchant.Result);
|
||||||
|
await Task.WhenAll(blockCount, merchant, customer, channels, connect);
|
||||||
|
|
||||||
|
// Mine until segwit is activated
|
||||||
|
if (blockCount.Result <= 432)
|
||||||
|
{
|
||||||
|
ExplorerNode.Generate(433 - blockCount.Result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public EclairTester MerchantEclair { get; set; }
|
||||||
|
public EclairTester CustomerEclair { get; set; }
|
||||||
|
|
||||||
|
internal string GetEnvironment(string variable, string defaultValue)
|
||||||
{
|
{
|
||||||
var var = Environment.GetEnvironmentVariable(variable);
|
var var = Environment.GetEnvironmentVariable(variable);
|
||||||
return String.IsNullOrEmpty(var) ? defaultValue : var;
|
return String.IsNullOrEmpty(var) ? defaultValue : var;
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ using BTCPayServer.Data;
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using BTCPayServer.Services.Rates;
|
using BTCPayServer.Services.Rates;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
using BTCPayServer.Eclair;
|
||||||
|
|
||||||
namespace BTCPayServer.Tests
|
namespace BTCPayServer.Tests
|
||||||
{
|
{
|
||||||
@@ -115,6 +116,24 @@ namespace BTCPayServer.Tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanUseLightMoney()
|
||||||
|
{
|
||||||
|
var light = LightMoney.MilliSatoshis(1);
|
||||||
|
Assert.Equal("0.00000000001", light.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void CanSendLightningPayment()
|
||||||
|
{
|
||||||
|
|
||||||
|
using (var tester = ServerTester.Create())
|
||||||
|
{
|
||||||
|
tester.Start();
|
||||||
|
tester.PrepareLightning();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void CanUseServerInitiatedPairingCode()
|
public void CanUseServerInitiatedPairingCode()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
version: "3"
|
version: "3"
|
||||||
|
|
||||||
|
# Run `docker-compose up dev` for bootstrapping your development environment
|
||||||
|
# Doing so will expose eclair API, NBXplorer, Bitcoind RPC and postgres port to the host so that tests can Run,
|
||||||
|
# The Visual Studio launch setting `Docker-Regtest` is configured to use this environment.
|
||||||
services:
|
services:
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
@@ -17,9 +20,24 @@ services:
|
|||||||
- "80"
|
- "80"
|
||||||
links:
|
links:
|
||||||
- nbxplorer
|
- nbxplorer
|
||||||
|
- eclair1
|
||||||
|
- eclair2
|
||||||
extra_hosts:
|
extra_hosts:
|
||||||
- "tests:127.0.0.1"
|
- "tests:127.0.0.1"
|
||||||
|
|
||||||
|
# The dev container is not actually used, it is just handy to run `docker-compose up dev` to start all services
|
||||||
|
dev:
|
||||||
|
image: nicolasdorier/docker-bitcoin:0.15.0.1
|
||||||
|
environment:
|
||||||
|
BITCOIN_EXTRA_ARGS: |
|
||||||
|
regtest=1
|
||||||
|
connect=bitcoind:39388
|
||||||
|
links:
|
||||||
|
- bitcoind
|
||||||
|
- nbxplorer
|
||||||
|
- eclair1
|
||||||
|
- eclair2
|
||||||
|
|
||||||
nbxplorer:
|
nbxplorer:
|
||||||
image: nicolasdorier/nbxplorer:1.0.0.29
|
image: nicolasdorier/nbxplorer:1.0.0.29
|
||||||
ports:
|
ports:
|
||||||
@@ -39,28 +57,73 @@ services:
|
|||||||
- bitcoind
|
- bitcoind
|
||||||
- postgres
|
- postgres
|
||||||
|
|
||||||
eclair:
|
eclair1:
|
||||||
image: nicolasdorier/docker-bitcoin:0.15.0.1
|
image: acinq/eclair:latest
|
||||||
|
environment:
|
||||||
|
JAVA_OPTS: >
|
||||||
|
-Xmx512m
|
||||||
|
-Declair.printToConsole
|
||||||
|
-Declair.bitcoind.host=bitcoind
|
||||||
|
-Declair.bitcoind.rpcport=43782
|
||||||
|
-Declair.bitcoind.rpcuser=ceiwHEbqWI83
|
||||||
|
-Declair.bitcoind.rpcpassword=DwubwWsoo3
|
||||||
|
-Declair.bitcoind.zmq=tcp://bitcoind:29000
|
||||||
|
-Declair.chain=regtest
|
||||||
|
-Declair.api.binding-ip=0.0.0.0
|
||||||
|
links:
|
||||||
|
- bitcoind
|
||||||
|
ports:
|
||||||
|
- "30992:8080" # api port
|
||||||
|
expose:
|
||||||
|
- "9735" # server port
|
||||||
|
- "8080" # api port
|
||||||
|
|
||||||
|
eclair2:
|
||||||
|
image: acinq/eclair:latest
|
||||||
|
environment:
|
||||||
|
JAVA_OPTS: >
|
||||||
|
-Xmx512m
|
||||||
|
-Declair.printToConsole
|
||||||
|
-Declair.bitcoind.host=bitcoind
|
||||||
|
-Declair.bitcoind.rpcport=43782
|
||||||
|
-Declair.bitcoind.rpcuser=ceiwHEbqWI83
|
||||||
|
-Declair.bitcoind.rpcpassword=DwubwWsoo3
|
||||||
|
-Declair.bitcoind.zmq=tcp://bitcoind:29000
|
||||||
|
-Declair.chain=regtest
|
||||||
|
-Declair.api.binding-ip=0.0.0.0
|
||||||
|
links:
|
||||||
|
- bitcoind
|
||||||
|
ports:
|
||||||
|
- "30993:8080" # api port
|
||||||
|
expose:
|
||||||
|
- "9735" # server port
|
||||||
|
- "8080" # api port
|
||||||
|
|
||||||
bitcoind:
|
bitcoind:
|
||||||
container_name: btcpayserver_dev_bitcoind
|
container_name: btcpayserver_dev_bitcoind
|
||||||
image: nicolasdorier/docker-bitcoin:0.15.0.1
|
image: nicolasdorier/docker-bitcoin:0.15.0.1
|
||||||
ports:
|
|
||||||
- "43782:43782"
|
|
||||||
- "39388:39388"
|
|
||||||
environment:
|
environment:
|
||||||
BITCOIN_EXTRA_ARGS: |
|
BITCOIN_EXTRA_ARGS: |
|
||||||
rpcuser=ceiwHEbqWI83
|
rpcuser=ceiwHEbqWI83
|
||||||
rpcpassword=DwubwWsoo3
|
rpcpassword=DwubwWsoo3
|
||||||
regtest=1
|
regtest=1
|
||||||
|
server=1
|
||||||
rpcport=43782
|
rpcport=43782
|
||||||
port=39388
|
port=39388
|
||||||
whitelist=0.0.0.0/0
|
whitelist=0.0.0.0/0
|
||||||
|
zmqpubrawblock=tcp://0.0.0.0:29000
|
||||||
|
zmqpubrawtx=tcp://0.0.0.0:29000
|
||||||
|
txindex=1
|
||||||
|
ports:
|
||||||
|
- "43782:43782" # RPC
|
||||||
expose:
|
expose:
|
||||||
- "43782"
|
- "43782" # RPC
|
||||||
- "39388"
|
- "39388" # P2P
|
||||||
|
- "29000" # zmq
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:9.6.5
|
image: postgres:9.6.5
|
||||||
ports:
|
ports:
|
||||||
- "39372:5432"
|
- "39372:5432"
|
||||||
|
expose:
|
||||||
|
- "5432"
|
||||||
|
|||||||
14
BTCPayServer/Eclair/AllChannelResponse.cs
Normal file
14
BTCPayServer/Eclair/AllChannelResponse.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Eclair
|
||||||
|
{
|
||||||
|
public class AllChannelResponse
|
||||||
|
{
|
||||||
|
public string ShortChannelId { get; set; }
|
||||||
|
public string NodeId1 { get; set; }
|
||||||
|
public string NodeId2 { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
21
BTCPayServer/Eclair/ChannelResponse.cs
Normal file
21
BTCPayServer/Eclair/ChannelResponse.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Eclair
|
||||||
|
{
|
||||||
|
public class ChannelResponse
|
||||||
|
{
|
||||||
|
|
||||||
|
public string NodeId { get; set; }
|
||||||
|
public string ChannelId { get; set; }
|
||||||
|
public string State { get; set; }
|
||||||
|
}
|
||||||
|
public static class ChannelStates
|
||||||
|
{
|
||||||
|
public const string WAIT_FOR_FUNDING_CONFIRMED = "WAIT_FOR_FUNDING_CONFIRMED";
|
||||||
|
|
||||||
|
public const string NORMAL = "NORMAL";
|
||||||
|
}
|
||||||
|
}
|
||||||
230
BTCPayServer/Eclair/EclairRPCClient.cs
Normal file
230
BTCPayServer/Eclair/EclairRPCClient.cs
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NBitcoin;
|
||||||
|
using NBitcoin.JsonConverters;
|
||||||
|
using NBitcoin.RPC;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Eclair
|
||||||
|
{
|
||||||
|
public class EclairRPCClient
|
||||||
|
{
|
||||||
|
public EclairRPCClient(Uri address, Network network)
|
||||||
|
{
|
||||||
|
if (address == null)
|
||||||
|
throw new ArgumentNullException(nameof(address));
|
||||||
|
if (network == null)
|
||||||
|
throw new ArgumentNullException(nameof(network));
|
||||||
|
Address = address;
|
||||||
|
Network = network;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Network Network { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
|
public GetInfoResponse GetInfo()
|
||||||
|
{
|
||||||
|
return GetInfoAsync().GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<GetInfoResponse> GetInfoAsync()
|
||||||
|
{
|
||||||
|
return SendCommandAsync<GetInfoResponse>(new RPCRequest("getinfo", new object[] { }));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<T> SendCommandAsync<T>(RPCRequest request, bool throwIfRPCError = true)
|
||||||
|
{
|
||||||
|
var response = await SendCommandAsync(request, throwIfRPCError);
|
||||||
|
return Serializer.ToObject<T>(response.ResultString, Network);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<RPCResponse> SendCommandAsync(RPCRequest request, bool throwIfRPCError = true)
|
||||||
|
{
|
||||||
|
RPCResponse response = null;
|
||||||
|
HttpWebRequest webRequest = response == null ? CreateWebRequest() : null;
|
||||||
|
if (response == null)
|
||||||
|
{
|
||||||
|
var writer = new StringWriter();
|
||||||
|
request.WriteJSON(writer);
|
||||||
|
writer.Flush();
|
||||||
|
var json = writer.ToString();
|
||||||
|
var bytes = Encoding.UTF8.GetBytes(json);
|
||||||
|
#if !(PORTABLE || NETCORE)
|
||||||
|
webRequest.ContentLength = bytes.Length;
|
||||||
|
#endif
|
||||||
|
var dataStream = await webRequest.GetRequestStreamAsync().ConfigureAwait(false);
|
||||||
|
await dataStream.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
|
||||||
|
await dataStream.FlushAsync().ConfigureAwait(false);
|
||||||
|
dataStream.Dispose();
|
||||||
|
}
|
||||||
|
WebResponse webResponse = null;
|
||||||
|
WebResponse errorResponse = null;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
webResponse = response == null ? await webRequest.GetResponseAsync().ConfigureAwait(false) : null;
|
||||||
|
response = response ?? RPCResponse.Load(await ToMemoryStreamAsync(webResponse.GetResponseStream()).ConfigureAwait(false));
|
||||||
|
|
||||||
|
if (throwIfRPCError)
|
||||||
|
response.ThrowIfError();
|
||||||
|
}
|
||||||
|
catch (WebException ex)
|
||||||
|
{
|
||||||
|
if (ex.Response == null || ex.Response.ContentLength == 0 ||
|
||||||
|
!ex.Response.ContentType.Equals("application/json", StringComparison.Ordinal))
|
||||||
|
throw;
|
||||||
|
errorResponse = ex.Response;
|
||||||
|
response = RPCResponse.Load(await ToMemoryStreamAsync(errorResponse.GetResponseStream()).ConfigureAwait(false));
|
||||||
|
if (throwIfRPCError)
|
||||||
|
response.ThrowIfError();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (errorResponse != null)
|
||||||
|
{
|
||||||
|
errorResponse.Dispose();
|
||||||
|
errorResponse = null;
|
||||||
|
}
|
||||||
|
if (webResponse != null)
|
||||||
|
{
|
||||||
|
webResponse.Dispose();
|
||||||
|
webResponse = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AllChannelResponse[] AllChannels()
|
||||||
|
{
|
||||||
|
return AllChannelsAsync().GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AllChannelResponse[]> AllChannelsAsync()
|
||||||
|
{
|
||||||
|
return await SendCommandAsync<AllChannelResponse[]>(new RPCRequest("allchannels", new object[] { })).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string[] Channels()
|
||||||
|
{
|
||||||
|
return ChannelsAsync().GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string[]> ChannelsAsync()
|
||||||
|
{
|
||||||
|
return await SendCommandAsync<string[]>(new RPCRequest("channels", new object[] { })).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close(string channelId)
|
||||||
|
{
|
||||||
|
CloseAsync(channelId).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task CloseAsync(string channelId)
|
||||||
|
{
|
||||||
|
if (channelId == null)
|
||||||
|
throw new ArgumentNullException(nameof(channelId));
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await SendCommandAsync(new RPCRequest("close", new object[] { channelId })).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
catch (RPCException ex) when (ex.Message == "closing already in progress")
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChannelResponse Channel(string channelId)
|
||||||
|
{
|
||||||
|
return ChannelAsync(channelId).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<ChannelResponse> ChannelAsync(string channelId)
|
||||||
|
{
|
||||||
|
if (channelId == null)
|
||||||
|
throw new ArgumentNullException(nameof(channelId));
|
||||||
|
return await SendCommandAsync<ChannelResponse>(new RPCRequest("channel", new object[] { channelId })).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string[] AllNodes()
|
||||||
|
{
|
||||||
|
return AllNodesAsync().GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string[]> AllNodesAsync()
|
||||||
|
{
|
||||||
|
return await SendCommandAsync<string[]>(new RPCRequest("allnodes", new object[] { })).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Uri Address { get; private set; }
|
||||||
|
|
||||||
|
private HttpWebRequest CreateWebRequest()
|
||||||
|
{
|
||||||
|
var webRequest = (HttpWebRequest)WebRequest.Create(Address.AbsoluteUri);
|
||||||
|
webRequest.ContentType = "application/json";
|
||||||
|
webRequest.Method = "POST";
|
||||||
|
return webRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private async Task<Stream> ToMemoryStreamAsync(Stream stream)
|
||||||
|
{
|
||||||
|
MemoryStream ms = new MemoryStream();
|
||||||
|
await stream.CopyToAsync(ms).ConfigureAwait(false);
|
||||||
|
ms.Position = 0;
|
||||||
|
return ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Open(NodeInfo node, Money fundingSatoshi, LightMoney pushAmount = null)
|
||||||
|
{
|
||||||
|
return OpenAsync(node, fundingSatoshi, pushAmount).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Connect(NodeInfo node)
|
||||||
|
{
|
||||||
|
return ConnectAsync(node).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> ConnectAsync(NodeInfo node)
|
||||||
|
{
|
||||||
|
if (node == null)
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
return (await SendCommandAsync(new RPCRequest("connect", new object[] { node.NodeId, node.Host, node.Port })).ConfigureAwait(false)).ResultString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Receive(LightMoney amount, string description = null)
|
||||||
|
{
|
||||||
|
return ReceiveAsync(amount, description).GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> ReceiveAsync(LightMoney amount, string description = null)
|
||||||
|
{
|
||||||
|
if (amount == null)
|
||||||
|
throw new ArgumentNullException(nameof(amount));
|
||||||
|
List<object> args = new List<object>();
|
||||||
|
args.Add(amount.MilliSatoshi);
|
||||||
|
if(description != null)
|
||||||
|
{
|
||||||
|
args.Add(description);
|
||||||
|
}
|
||||||
|
return (await SendCommandAsync(new RPCRequest("receive", args.ToArray())).ConfigureAwait(false)).ResultString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> OpenAsync(NodeInfo node, Money fundingSatoshi, LightMoney pushAmount = null)
|
||||||
|
{
|
||||||
|
if (fundingSatoshi == null)
|
||||||
|
throw new ArgumentNullException(nameof(fundingSatoshi));
|
||||||
|
if (node == null)
|
||||||
|
throw new ArgumentNullException(nameof(node));
|
||||||
|
pushAmount = pushAmount ?? LightMoney.Zero;
|
||||||
|
|
||||||
|
var result = await SendCommandAsync(new RPCRequest("open", new object[] { node.NodeId, node.Host, node.Port, fundingSatoshi.Satoshi, pushAmount.MilliSatoshi }));
|
||||||
|
|
||||||
|
return result.ResultString;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
18
BTCPayServer/Eclair/GetInfoResponse.cs
Normal file
18
BTCPayServer/Eclair/GetInfoResponse.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using NBitcoin;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Eclair
|
||||||
|
{
|
||||||
|
public class GetInfoResponse
|
||||||
|
{
|
||||||
|
public string NodeId { get; set; }
|
||||||
|
public string Alias { get; set; }
|
||||||
|
public int Port { get; set; }
|
||||||
|
public uint256 ChainHash { get; set; }
|
||||||
|
public int BlockHeight { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
569
BTCPayServer/Eclair/LightMoney.cs
Normal file
569
BTCPayServer/Eclair/LightMoney.cs
Normal file
@@ -0,0 +1,569 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Eclair
|
||||||
|
{
|
||||||
|
public enum LightMoneyUnit : ulong
|
||||||
|
{
|
||||||
|
BTC = 100000000000,
|
||||||
|
MilliBTC = 100000000,
|
||||||
|
Bit = 100000,
|
||||||
|
Satoshi = 1000,
|
||||||
|
MilliSatoshi = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
public class LightMoney : IComparable, IComparable<LightMoney>, IEquatable<LightMoney>
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
// for decimal.TryParse. None of the NumberStyles' composed values is useful for bitcoin style
|
||||||
|
private const NumberStyles BitcoinStyle =
|
||||||
|
NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite
|
||||||
|
| NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint;
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse a bitcoin amount (Culture Invariant)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bitcoin"></param>
|
||||||
|
/// <param name="nRet"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static bool TryParse(string bitcoin, out LightMoney nRet)
|
||||||
|
{
|
||||||
|
nRet = null;
|
||||||
|
|
||||||
|
decimal value;
|
||||||
|
if (!decimal.TryParse(bitcoin, BitcoinStyle, CultureInfo.InvariantCulture, out value))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
nRet = new LightMoney(value, LightMoneyUnit.BTC);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (OverflowException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Parse a bitcoin amount (Culture Invariant)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="bitcoin"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static LightMoney Parse(string bitcoin)
|
||||||
|
{
|
||||||
|
LightMoney result;
|
||||||
|
if (TryParse(bitcoin, out result))
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
throw new FormatException("Impossible to parse the string in a bitcoin amount");
|
||||||
|
}
|
||||||
|
|
||||||
|
long _MilliSatoshis;
|
||||||
|
public long MilliSatoshi
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _MilliSatoshis;
|
||||||
|
}
|
||||||
|
// used as a central point where long.MinValue checking can be enforced
|
||||||
|
private set
|
||||||
|
{
|
||||||
|
CheckLongMinValue(value);
|
||||||
|
_MilliSatoshis = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get absolute value of the instance
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public LightMoney Abs()
|
||||||
|
{
|
||||||
|
var a = this;
|
||||||
|
if (a < LightMoney.Zero)
|
||||||
|
a = -a;
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LightMoney(int satoshis)
|
||||||
|
{
|
||||||
|
MilliSatoshi = satoshis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LightMoney(uint satoshis)
|
||||||
|
{
|
||||||
|
MilliSatoshi = satoshis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LightMoney(long satoshis)
|
||||||
|
{
|
||||||
|
MilliSatoshi = satoshis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LightMoney(ulong satoshis)
|
||||||
|
{
|
||||||
|
// overflow check.
|
||||||
|
// ulong.MaxValue is greater than long.MaxValue
|
||||||
|
checked
|
||||||
|
{
|
||||||
|
MilliSatoshi = (long)satoshis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LightMoney(decimal amount, LightMoneyUnit unit)
|
||||||
|
{
|
||||||
|
// sanity check. Only valid units are allowed
|
||||||
|
CheckMoneyUnit(unit, "unit");
|
||||||
|
checked
|
||||||
|
{
|
||||||
|
var satoshi = amount * (long)unit;
|
||||||
|
MilliSatoshi = (long)satoshi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Split the Money in parts without loss
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parts">The number of parts (must be more than 0)</param>
|
||||||
|
/// <returns>The splitted money</returns>
|
||||||
|
public IEnumerable<LightMoney> Split(int parts)
|
||||||
|
{
|
||||||
|
if (parts <= 0)
|
||||||
|
throw new ArgumentOutOfRangeException("Parts should be more than 0", "parts");
|
||||||
|
long remain;
|
||||||
|
long result = DivRem(_MilliSatoshis, parts, out remain);
|
||||||
|
|
||||||
|
for (int i = 0; i < parts; i++)
|
||||||
|
{
|
||||||
|
yield return LightMoney.Satoshis(result + (remain > 0 ? 1 : 0));
|
||||||
|
remain--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long DivRem(long a, long b, out long result)
|
||||||
|
{
|
||||||
|
result = a % b;
|
||||||
|
return a / b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LightMoney FromUnit(decimal amount, LightMoneyUnit unit)
|
||||||
|
{
|
||||||
|
return new LightMoney(amount, unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert Money to decimal (same as ToDecimal)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="unit"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public decimal ToUnit(LightMoneyUnit unit)
|
||||||
|
{
|
||||||
|
CheckMoneyUnit(unit, "unit");
|
||||||
|
// overflow safe because (long / int) always fit in decimal
|
||||||
|
// decimal operations are checked by default
|
||||||
|
return (decimal)MilliSatoshi / (int)unit;
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Convert Money to decimal (same as ToUnit)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="unit"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public decimal ToDecimal(LightMoneyUnit unit)
|
||||||
|
{
|
||||||
|
return ToUnit(unit);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LightMoney Coins(decimal coins)
|
||||||
|
{
|
||||||
|
// overflow safe.
|
||||||
|
// decimal operations are checked by default
|
||||||
|
return new LightMoney(coins * (ulong)LightMoneyUnit.BTC, LightMoneyUnit.MilliBTC);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LightMoney Bits(decimal bits)
|
||||||
|
{
|
||||||
|
// overflow safe.
|
||||||
|
// decimal operations are checked by default
|
||||||
|
return new LightMoney(bits * (ulong)LightMoneyUnit.Bit, LightMoneyUnit.MilliBTC);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LightMoney Cents(decimal cents)
|
||||||
|
{
|
||||||
|
// overflow safe.
|
||||||
|
// decimal operations are checked by default
|
||||||
|
return new LightMoney(cents * (ulong)LightMoneyUnit.Bit, LightMoneyUnit.MilliBTC);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LightMoney Satoshis(decimal sats)
|
||||||
|
{
|
||||||
|
return new LightMoney(sats * (ulong)LightMoneyUnit.Satoshi, LightMoneyUnit.MilliBTC);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LightMoney Satoshis(ulong sats)
|
||||||
|
{
|
||||||
|
return new LightMoney(sats);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LightMoney Satoshis(long sats)
|
||||||
|
{
|
||||||
|
return new LightMoney(sats);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LightMoney MilliSatoshis(long msats)
|
||||||
|
{
|
||||||
|
return new LightMoney(msats);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LightMoney MilliSatoshis(ulong msats)
|
||||||
|
{
|
||||||
|
return new LightMoney(msats);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IEquatable<Money> Members
|
||||||
|
|
||||||
|
public bool Equals(LightMoney other)
|
||||||
|
{
|
||||||
|
if (other == null)
|
||||||
|
return false;
|
||||||
|
return _MilliSatoshis.Equals(other._MilliSatoshis);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(LightMoney other)
|
||||||
|
{
|
||||||
|
if (other == null)
|
||||||
|
return 1;
|
||||||
|
return _MilliSatoshis.CompareTo(other._MilliSatoshis);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region IComparable Members
|
||||||
|
|
||||||
|
public int CompareTo(object obj)
|
||||||
|
{
|
||||||
|
if (obj == null)
|
||||||
|
return 1;
|
||||||
|
LightMoney m = obj as LightMoney;
|
||||||
|
if (m != null)
|
||||||
|
return _MilliSatoshis.CompareTo(m._MilliSatoshis);
|
||||||
|
#if !(PORTABLE || NETCORE)
|
||||||
|
return _MilliSatoshis.CompareTo(obj);
|
||||||
|
#else
|
||||||
|
return _Satoshis.CompareTo((long)obj);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public static LightMoney operator -(LightMoney left, LightMoney right)
|
||||||
|
{
|
||||||
|
if (left == null)
|
||||||
|
throw new ArgumentNullException("left");
|
||||||
|
if (right == null)
|
||||||
|
throw new ArgumentNullException("right");
|
||||||
|
return new LightMoney(checked(left._MilliSatoshis - right._MilliSatoshis));
|
||||||
|
}
|
||||||
|
public static LightMoney operator -(LightMoney left)
|
||||||
|
{
|
||||||
|
if (left == null)
|
||||||
|
throw new ArgumentNullException("left");
|
||||||
|
return new LightMoney(checked(-left._MilliSatoshis));
|
||||||
|
}
|
||||||
|
public static LightMoney operator +(LightMoney left, LightMoney right)
|
||||||
|
{
|
||||||
|
if (left == null)
|
||||||
|
throw new ArgumentNullException("left");
|
||||||
|
if (right == null)
|
||||||
|
throw new ArgumentNullException("right");
|
||||||
|
return new LightMoney(checked(left._MilliSatoshis + right._MilliSatoshis));
|
||||||
|
}
|
||||||
|
public static LightMoney operator *(int left, LightMoney right)
|
||||||
|
{
|
||||||
|
if (right == null)
|
||||||
|
throw new ArgumentNullException("right");
|
||||||
|
return LightMoney.Satoshis(checked(left * right._MilliSatoshis));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LightMoney operator *(LightMoney right, int left)
|
||||||
|
{
|
||||||
|
if (right == null)
|
||||||
|
throw new ArgumentNullException("right");
|
||||||
|
return LightMoney.Satoshis(checked(right._MilliSatoshis * left));
|
||||||
|
}
|
||||||
|
public static LightMoney operator *(long left, LightMoney right)
|
||||||
|
{
|
||||||
|
if (right == null)
|
||||||
|
throw new ArgumentNullException("right");
|
||||||
|
return LightMoney.Satoshis(checked(left * right._MilliSatoshis));
|
||||||
|
}
|
||||||
|
public static LightMoney operator *(LightMoney right, long left)
|
||||||
|
{
|
||||||
|
if (right == null)
|
||||||
|
throw new ArgumentNullException("right");
|
||||||
|
return LightMoney.Satoshis(checked(left * right._MilliSatoshis));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LightMoney operator /(LightMoney left, long right)
|
||||||
|
{
|
||||||
|
if (left == null)
|
||||||
|
throw new ArgumentNullException("left");
|
||||||
|
return new LightMoney(checked(left._MilliSatoshis / right));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator <(LightMoney left, LightMoney right)
|
||||||
|
{
|
||||||
|
if (left == null)
|
||||||
|
throw new ArgumentNullException("left");
|
||||||
|
if (right == null)
|
||||||
|
throw new ArgumentNullException("right");
|
||||||
|
return left._MilliSatoshis < right._MilliSatoshis;
|
||||||
|
}
|
||||||
|
public static bool operator >(LightMoney left, LightMoney right)
|
||||||
|
{
|
||||||
|
if (left == null)
|
||||||
|
throw new ArgumentNullException("left");
|
||||||
|
if (right == null)
|
||||||
|
throw new ArgumentNullException("right");
|
||||||
|
return left._MilliSatoshis > right._MilliSatoshis;
|
||||||
|
}
|
||||||
|
public static bool operator <=(LightMoney left, LightMoney right)
|
||||||
|
{
|
||||||
|
if (left == null)
|
||||||
|
throw new ArgumentNullException("left");
|
||||||
|
if (right == null)
|
||||||
|
throw new ArgumentNullException("right");
|
||||||
|
return left._MilliSatoshis <= right._MilliSatoshis;
|
||||||
|
}
|
||||||
|
public static bool operator >=(LightMoney left, LightMoney right)
|
||||||
|
{
|
||||||
|
if (left == null)
|
||||||
|
throw new ArgumentNullException("left");
|
||||||
|
if (right == null)
|
||||||
|
throw new ArgumentNullException("right");
|
||||||
|
return left._MilliSatoshis >= right._MilliSatoshis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator LightMoney(long value)
|
||||||
|
{
|
||||||
|
return new LightMoney(value);
|
||||||
|
}
|
||||||
|
public static implicit operator LightMoney(int value)
|
||||||
|
{
|
||||||
|
return new LightMoney(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator LightMoney(uint value)
|
||||||
|
{
|
||||||
|
return new LightMoney(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator LightMoney(ulong value)
|
||||||
|
{
|
||||||
|
return new LightMoney(checked((long)value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator long(LightMoney value)
|
||||||
|
{
|
||||||
|
return value.MilliSatoshi;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator ulong(LightMoney value)
|
||||||
|
{
|
||||||
|
return checked((ulong)value.MilliSatoshi);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static implicit operator LightMoney(string value)
|
||||||
|
{
|
||||||
|
return LightMoney.Parse(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object obj)
|
||||||
|
{
|
||||||
|
LightMoney item = obj as LightMoney;
|
||||||
|
if (item == null)
|
||||||
|
return false;
|
||||||
|
return _MilliSatoshis.Equals(item._MilliSatoshis);
|
||||||
|
}
|
||||||
|
public static bool operator ==(LightMoney a, LightMoney b)
|
||||||
|
{
|
||||||
|
if (Object.ReferenceEquals(a, b))
|
||||||
|
return true;
|
||||||
|
if (((object)a == null) || ((object)b == null))
|
||||||
|
return false;
|
||||||
|
return a._MilliSatoshis == b._MilliSatoshis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(LightMoney a, LightMoney b)
|
||||||
|
{
|
||||||
|
return !(a == b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return _MilliSatoshis.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a culture invariant string representation of Bitcoin amount
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return ToString(false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a culture invariant string representation of Bitcoin amount
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fplus">True if show + for a positive amount</param>
|
||||||
|
/// <param name="trimExcessZero">True if trim excess zeroes</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public string ToString(bool fplus, bool trimExcessZero = true)
|
||||||
|
{
|
||||||
|
var fmt = string.Format("{{0:{0}{1}B}}",
|
||||||
|
(fplus ? "+" : null),
|
||||||
|
(trimExcessZero ? "2" : "11"));
|
||||||
|
return string.Format(BitcoinFormatter.Formatter, fmt, _MilliSatoshis);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static LightMoney _Zero = new LightMoney(0);
|
||||||
|
public static LightMoney Zero
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return _Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class BitcoinFormatter : IFormatProvider, ICustomFormatter
|
||||||
|
{
|
||||||
|
public static readonly BitcoinFormatter Formatter = new BitcoinFormatter();
|
||||||
|
|
||||||
|
public object GetFormat(Type formatType)
|
||||||
|
{
|
||||||
|
return formatType == typeof(ICustomFormatter) ? this : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Format(string format, object arg, IFormatProvider formatProvider)
|
||||||
|
{
|
||||||
|
if (!this.Equals(formatProvider))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
var i = 0;
|
||||||
|
var plus = format[i] == '+';
|
||||||
|
if (plus)
|
||||||
|
i++;
|
||||||
|
int decPos = 0;
|
||||||
|
if (int.TryParse(format.Substring(i, 1), out decPos))
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
var unit = format[i];
|
||||||
|
var unitToUseInCalc = LightMoneyUnit.BTC;
|
||||||
|
switch (unit)
|
||||||
|
{
|
||||||
|
case 'B':
|
||||||
|
unitToUseInCalc = LightMoneyUnit.BTC;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
var val = Convert.ToDecimal(arg) / (long)unitToUseInCalc;
|
||||||
|
var zeros = new string('0', decPos);
|
||||||
|
var rest = new string('#', 11 - decPos);
|
||||||
|
var fmt = plus && val > 0 ? "+" : string.Empty;
|
||||||
|
|
||||||
|
fmt += "{0:0" + (decPos > 0 ? "." + zeros + rest : string.Empty) + "}";
|
||||||
|
return string.Format(CultureInfo.InvariantCulture, fmt, val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tell if amount is almost equal to this instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="amount"></param>
|
||||||
|
/// <param name="dust">more or less amount</param>
|
||||||
|
/// <returns>true if equals, else false</returns>
|
||||||
|
public bool Almost(LightMoney amount, LightMoney dust)
|
||||||
|
{
|
||||||
|
if (amount == null)
|
||||||
|
throw new ArgumentNullException("amount");
|
||||||
|
if (dust == null)
|
||||||
|
throw new ArgumentNullException("dust");
|
||||||
|
return (amount - this).Abs() <= dust;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tell if amount is almost equal to this instance
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="amount"></param>
|
||||||
|
/// <param name="margin">error margin (between 0 and 1)</param>
|
||||||
|
/// <returns>true if equals, else false</returns>
|
||||||
|
public bool Almost(LightMoney amount, decimal margin)
|
||||||
|
{
|
||||||
|
if (amount == null)
|
||||||
|
throw new ArgumentNullException("amount");
|
||||||
|
if (margin < 0.0m || margin > 1.0m)
|
||||||
|
throw new ArgumentOutOfRangeException("margin", "margin should be between 0 and 1");
|
||||||
|
var dust = LightMoney.Satoshis((decimal)this.MilliSatoshi * margin);
|
||||||
|
return Almost(amount, dust);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LightMoney Min(LightMoney a, LightMoney b)
|
||||||
|
{
|
||||||
|
if (a == null)
|
||||||
|
throw new ArgumentNullException("a");
|
||||||
|
if (b == null)
|
||||||
|
throw new ArgumentNullException("b");
|
||||||
|
if (a <= b)
|
||||||
|
return a;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LightMoney Max(LightMoney a, LightMoney b)
|
||||||
|
{
|
||||||
|
if (a == null)
|
||||||
|
throw new ArgumentNullException("a");
|
||||||
|
if (b == null)
|
||||||
|
throw new ArgumentNullException("b");
|
||||||
|
if (a >= b)
|
||||||
|
return a;
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CheckLongMinValue(long value)
|
||||||
|
{
|
||||||
|
if (value == long.MinValue)
|
||||||
|
throw new OverflowException("satoshis amount should be greater than long.MinValue");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CheckMoneyUnit(LightMoneyUnit value, string paramName)
|
||||||
|
{
|
||||||
|
var typeOfMoneyUnit = typeof(LightMoneyUnit);
|
||||||
|
if (!Enum.IsDefined(typeOfMoneyUnit, value))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Invalid value for MoneyUnit", paramName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IComparable Members
|
||||||
|
|
||||||
|
int IComparable.CompareTo(object obj)
|
||||||
|
{
|
||||||
|
return this.CompareTo(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
24
BTCPayServer/Eclair/NodeInfo.cs
Normal file
24
BTCPayServer/Eclair/NodeInfo.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Eclair
|
||||||
|
{
|
||||||
|
public class NodeInfo
|
||||||
|
{
|
||||||
|
public NodeInfo(string nodeId, string host, int port)
|
||||||
|
{
|
||||||
|
if (host == null)
|
||||||
|
throw new ArgumentNullException(nameof(host));
|
||||||
|
if (nodeId == null)
|
||||||
|
throw new ArgumentNullException(nameof(nodeId));
|
||||||
|
Port = port;
|
||||||
|
Host = host;
|
||||||
|
NodeId = nodeId;
|
||||||
|
}
|
||||||
|
public string NodeId { get; private set; }
|
||||||
|
public string Host { get; private set; }
|
||||||
|
public int Port { get; private set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user