Merge branch 'master' into MonetaryUnit

This commit is contained in:
Sotiris Blad
2020-08-03 19:18:08 +03:00
committed by GitHub
131 changed files with 3354 additions and 2148 deletions

6
.circleci/can-build.sh Executable file
View File

@@ -0,0 +1,6 @@
#!/bin/sh
set -e
echo "Checking if it is possible to build Bitcoin only..."
cd ../BTCPayServer.Tests
docker-compose -f "docker-compose.yml" build

View File

@@ -7,7 +7,7 @@ jobs:
- checkout
- run:
command: |
cd .circleci && ./run-tests.sh "Fast=Fast"
cd .circleci && ./run-tests.sh "Fast=Fast" && ./can-build.sh
selenium_tests:
machine:
enabled: true
@@ -49,8 +49,10 @@ jobs:
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
#
sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-amd64 -f amd64.Dockerfile .
sudo docker build --pull --build-arg CONFIGURATION_NAME=Altcoins-Release -t $DOCKERHUB_REPO:$LATEST_TAG-altcoins-amd64 -f amd64.Dockerfile .
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-amd64
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-altcoins-amd64
arm32v7:
machine:
@@ -63,8 +65,10 @@ jobs:
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
#
sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 -f arm32v7.Dockerfile .
sudo docker build --pull --build-arg CONFIGURATION_NAME=Altcoins-Release -t $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm32v7 -f arm32v7.Dockerfile .
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm32v7
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm32v7
arm64v8:
machine:
@@ -77,8 +81,10 @@ jobs:
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
#
sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-arm64v8 -f arm64v8.Dockerfile .
sudo docker build --build-arg CONFIGURATION_NAME=Altcoins-Release --pull -t $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm64v8 -f arm64v8.Dockerfile .
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm64v8
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm64v8
multiarch:
machine:
@@ -99,6 +105,13 @@ jobs:
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 --os linux --arch arm --variant v7
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-arm64v8 --os linux --arch arm64 --variant v8
sudo docker manifest push $DOCKERHUB_REPO:$LATEST_TAG -p
sudo docker manifest create --amend $DOCKERHUB_REPO:$LATEST_TAG-altcoins $DOCKERHUB_REPO:$LATEST_TAG-altcoins-amd64 $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm32v7 $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm64v8
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG-altcoins $DOCKERHUB_REPO:$LATEST_TAG-altcoins-amd64 --os linux --arch amd64
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG-altcoins $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm32v7 --os linux --arch arm --variant v7
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG-altcoins $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm64v8 --os linux --arch arm64 --variant v8
sudo docker manifest push $DOCKERHUB_REPO:$LATEST_TAG-altcoins -p
workflows:
version: 2

View File

@@ -3,7 +3,7 @@ set -e
cd ../BTCPayServer.Tests
docker-compose -v
docker-compose down --v
docker-compose pull
docker-compose build
docker-compose run -e "TEST_FILTERS=$1" tests
docker-compose -f "docker-compose.altcoins.yml" down --v
docker-compose -f "docker-compose.altcoins.yml" pull
docker-compose -f "docker-compose.altcoins.yml" build
docker-compose -f "docker-compose.altcoins.yml" run -e "TEST_FILTERS=$1" tests

6
.gitignore vendored
View File

@@ -292,5 +292,9 @@ __pycache__/
BTCPayServer/wwwroot/bundles/*
!BTCPayServer/wwwroot/bundles/.gitignore
.vscode
.vscode/*
!.vscode/launch.json
!.vscode/tasks.json
!.vscode/extensions.json
BTCPayServer/testpwd
.DS_Store

3
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"recommendations": ["ms-dotnettools.csharp"]
}

33
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,33 @@
{
// Use IntelliSense to find out which attributes exist for C# debugging
// Use hover for the description of the existing attributes
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
"version": "0.2.0",
"configurations": [
{
"name": ".NET Core Launch (web)",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path.
"program": "${workspaceFolder}/BTCPayServer/bin/Debug/netcoreapp3.1/BTCPayServer.dll",
"args": [],
"cwd": "${workspaceFolder}/BTCPayServer",
"stopAtEntry": false,
// Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
"serverReadyAction": {
"action": "openExternally",
"pattern": "\\bListening on\\s+(https?://\\S+)"
},
"env": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"sourceFileMap": {
"/Views": "${workspaceFolder}/Views"
},
"logging": {
"moduleLoad": false
}
}
]
}

42
.vscode/tasks.json vendored Normal file
View File

@@ -0,0 +1,42 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "build",
"command": "dotnet",
"type": "process",
"args": [
"build",
"${workspaceFolder}/BTCPayServer/BTCPayServer.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "publish",
"command": "dotnet",
"type": "process",
"args": [
"publish",
"${workspaceFolder}/BTCPayServer/BTCPayServer.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
},
{
"label": "watch",
"command": "dotnet",
"type": "process",
"args": [
"watch",
"run",
"${workspaceFolder}/BTCPayServer/BTCPayServer.csproj",
"/property:GenerateFullPaths=true",
"/consoleloggerparameters:NoSummary"
],
"problemMatcher": "$msCompile"
}
]
}

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>

View File

@@ -1,39 +0,0 @@
using System;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.JsonConverters
{
public class DecimalStringJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(decimal) || objectType == typeof(decimal?));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
switch (token.Type)
{
case JTokenType.Float:
case JTokenType.Integer:
case JTokenType.String:
return decimal.Parse(token.ToString(), CultureInfo.InvariantCulture);
case JTokenType.Null when objectType == typeof(decimal?):
return null;
default:
throw new JsonSerializationException("Unexpected token type: " +
token.Type);
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value != null)
writer.WriteValue(((decimal)value).ToString(CultureInfo.InvariantCulture));
}
}
}

View File

@@ -0,0 +1,56 @@
using System;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.JsonConverters
{
public class NumericStringJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(decimal) ||
objectType == typeof(decimal?) ||
objectType == typeof(double) ||
objectType == typeof(double?));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
switch (token.Type)
{
case JTokenType.Float:
case JTokenType.Integer:
case JTokenType.String:
if (objectType == typeof(decimal) || objectType == typeof(decimal?) )
return decimal.Parse(token.ToString(), CultureInfo.InvariantCulture);
if (objectType == typeof(double) || objectType == typeof(double?))
return double.Parse(token.ToString(), CultureInfo.InvariantCulture);
throw new JsonSerializationException("Unexpected object type: " + objectType);
case JTokenType.Null when objectType == typeof(decimal?) || objectType == typeof(double?):
return null;
default:
throw new JsonSerializationException("Unexpected token type: " +
token.Type);
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
switch (value)
{
case null:
break;
case decimal x:
writer.WriteValue(x.ToString(CultureInfo.InvariantCulture));
break;
case double x:
writer.WriteValue(x.ToString(CultureInfo.InvariantCulture));
break;
}
}
}
}

View File

@@ -6,7 +6,7 @@ namespace BTCPayServer.Client.Models
public class CreatePayoutRequest
{
public string Destination { get; set; }
[JsonConverter(typeof(DecimalStringJsonConverter))]
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal? Amount { get; set; }
public string PaymentMethod { get; set; }
}

View File

@@ -8,7 +8,7 @@ namespace BTCPayServer.Client.Models
public class CreatePullPaymentRequest
{
public string Name { get; set; }
[JsonProperty(ItemConverterType = typeof(DecimalStringJsonConverter))]
[JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))]
public decimal Amount { get; set; }
public string Currency { get; set; }
[JsonConverter(typeof(TimeSpanJsonConverter))]

View File

@@ -8,7 +8,7 @@ namespace BTCPayServer.Client.Models
{
public class PaymentRequestBaseData
{
[JsonProperty(ItemConverterType = typeof(DecimalStringJsonConverter))]
[JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))]
public decimal Amount { get; set; }
public string Currency { get; set; }
public DateTime? ExpiryDate { get; set; }

View File

@@ -21,9 +21,9 @@ namespace BTCPayServer.Client.Models
public string PullPaymentId { get; set; }
public string Destination { get; set; }
public string PaymentMethod { get; set; }
[JsonConverter(typeof(DecimalStringJsonConverter))]
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Amount { get; set; }
[JsonConverter(typeof(DecimalStringJsonConverter))]
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal? PaymentMethodAmount { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public PayoutState State { get; set; }

View File

@@ -14,7 +14,7 @@ namespace BTCPayServer.Client.Models
public string Id { get; set; }
public string Name { get; set; }
public string Currency { get; set; }
[JsonConverter(typeof(DecimalStringJsonConverter))]
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Amount { get; set; }
[JsonConverter(typeof(TimeSpanJsonConverter))]
public TimeSpan? Period { get; set; }

View File

@@ -1,3 +1,4 @@
#if ALTCOINS
using NBitcoin;
using NBitcoin.Altcoins;
using NBitcoin.Altcoins.Elements;
@@ -34,3 +35,4 @@ namespace BTCPayServer
}
#endif

View File

@@ -1,3 +1,4 @@
#if ALTCOINS
using NBitcoin;
namespace BTCPayServer
@@ -81,3 +82,4 @@ namespace BTCPayServer
}
#endif

View File

@@ -1,3 +1,4 @@
#if ALTCOINS
using System.Collections.Generic;
using System.Linq;
using NBitcoin;
@@ -58,3 +59,4 @@ namespace BTCPayServer
}
}
}
#endif

View File

@@ -0,0 +1,18 @@
#if ALTCOINS
using System.Collections.Generic;
using System.Linq;
namespace BTCPayServer
{
public static class LiquidExtensions
{
public static IEnumerable<string> GetAllElementsSubChains(this BTCPayNetworkProvider networkProvider)
{
var elementsBased = networkProvider.GetAll().OfType<ElementsBTCPayNetwork>();
var parentChains = elementsBased.Select(network => network.NetworkCryptoCode.ToUpperInvariant()).Distinct();
return networkProvider.GetAll().OfType<ElementsBTCPayNetwork>()
.Where(network => parentChains.Contains(network.NetworkCryptoCode)).Select(network => network.CryptoCode.ToUpperInvariant());
}
}
}
#endif

View File

@@ -20,7 +20,8 @@ namespace BTCPayServer
"XMR_X = XMR_BTC * BTC_X",
"XMR_BTC = kraken(XMR_BTC)"
},
CryptoImagePath = "/imlegacy/monero.svg"
CryptoImagePath = "/imlegacy/monero.svg",
UriScheme = "monero"
});
}
}

View File

@@ -3,5 +3,6 @@ namespace BTCPayServer
public class MoneroLikeSpecificBtcPayNetwork : BTCPayNetworkBase
{
public int MaxTrackedConfirmation = 10;
public string UriScheme { get; set; }
}
}

View File

@@ -131,4 +131,4 @@ namespace BTCPayServer
return network as T;
}
}
}
}

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" />
@@ -6,4 +6,7 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="NBXplorer.Client" Version="3.0.16" />
</ItemGroup>
<ItemGroup Condition="'$(Altcoins)' != 'true'">
<Compile Remove="Altcoins\**\*.cs"></Compile>
</ItemGroup>
</Project>

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" />
<ItemGroup>

View File

@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" />

File diff suppressed because it is too large Load Diff

View File

@@ -66,6 +66,7 @@ namespace BTCPayServer.Tests
Assert.NotNull(options.NetworkProvider.GetNetwork("USDT"));
}
[Fact]
[Trait("Altcoins", "Altcoins")]
public async Task ElementsAssetsAreHandledCorrectly()

View File

@@ -1,11 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="../Build/Common.csproj" />
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework Condition="'$(TargetFrameworkOverride)' != ''">$(TargetFrameworkOverride)</TargetFramework>
<IsPackable>false</IsPackable>
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
<LangVersion>8.0</LangVersion>
<UserSecretsId>AB0AC1DD-9D26-485B-9416-56A33F268117</UserSecretsId>
<!--https://devblogs.microsoft.com/aspnet/testing-asp-net-core-mvc-web-apps-in-memory/-->
<PreserveCompilationContext>true</PreserveCompilationContext>
@@ -21,6 +17,9 @@
<PropertyGroup Condition="'$(CI_TESTS)' == 'true'">
<DefineConstants>$(DefineConstants);SHORT_TIMEOUT</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<DefineConstants>$(DefineConstants);ALTCOINS</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
@@ -33,11 +32,16 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(Altcoins)' != 'true'">
<Compile Remove="AltcoinTests\**\*.cs"></Compile>
</ItemGroup>
<ItemGroup>
<None Update=".dockerignore">
<DependentUpon>Dockerfile</DependentUpon>
</None>
<None Update="docker-compose.altcoins.yml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="docker-compose.yml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>

View File

@@ -103,53 +103,6 @@ namespace BTCPayServer.Tests
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Altcoins", "Altcoins")]
[Trait("Lightning", "Lightning")]
public async Task CanUsePaymentMethodDropdown()
{
using (var s = SeleniumTester.Create())
{
s.Server.ActivateLTC();
s.Server.ActivateLightning();
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
var store = s.CreateNewStore();
s.AddDerivationScheme("BTC");
//check that there is no dropdown since only one payment method is set
var invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com");
s.GoToInvoiceCheckout(invoiceId);
s.Driver.FindElement(By.ClassName("payment__currencies_noborder"));
s.GoToHome();
s.GoToStore(store.storeId);
s.AddDerivationScheme("LTC");
s.AddLightningNode("BTC", LightningConnectionType.CLightning);
//there should be three now
invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com");
s.GoToInvoiceCheckout(invoiceId);
var currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies"));
Assert.Contains("BTC", currencyDropdownButton.Text);
currencyDropdownButton.Click();
var elements = s.Driver.FindElement(By.ClassName("vex-content")).FindElements(By.ClassName("vexmenuitem"));
Assert.Equal(3, elements.Count);
elements.Single(element => element.Text.Contains("LTC")).Click();
currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies"));
Assert.Contains("LTC", currencyDropdownButton.Text);
currencyDropdownButton.Click();
elements = s.Driver.FindElement(By.ClassName("vex-content")).FindElements(By.ClassName("vexmenuitem"));
elements.Single(element => element.Text.Contains("Lightning")).Click();
currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies"));
Assert.Contains("Lightning", currencyDropdownButton.Text);
s.Driver.Quit();
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Lightning", "Lightning")]
public async Task CanUseLightningSatsFeature()

View File

@@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/core/sdk:3.1.101 AS builder
FROM mcr.microsoft.com/dotnet/core/sdk:3.1.202 AS builder
RUN apt-get update && apt-get install -y --no-install-recommends chromium-driver \
&& rm -rf /var/lib/apt/lists/*
@@ -21,6 +21,9 @@ ENV SCREEN_HEIGHT 600 \
SCREEN_WIDTH 1200
COPY . .
RUN cd BTCPayServer.Tests && dotnet build /p:CI_TESTS=true /p:RazorCompileOnBuild=true
ARG CONFIGURATION_NAME=Release
RUN cd BTCPayServer.Tests && dotnet build --configuration ${CONFIGURATION_NAME} /p:CI_TESTS=true /p:RazorCompileOnBuild=true
WORKDIR /source/BTCPayServer.Tests
ENV CONFIGURATION_NAME=${CONFIGURATION_NAME}
ENTRYPOINT ["./docker-entrypoint.sh"]

View File

@@ -734,17 +734,18 @@ namespace BTCPayServer.Tests
[Fact(Timeout = TestTimeout)]
[Trait("Fast", "Fast")]
public void DecimalStringJsonConverterTests()
public void NumericJsonConverterTests()
{
JsonReader Get(string val)
{
return new JsonTextReader(new StringReader(val));
}
var jsonConverter = new DecimalStringJsonConverter();
var jsonConverter = new NumericStringJsonConverter();
Assert.True(jsonConverter.CanConvert(typeof(decimal)));
Assert.True(jsonConverter.CanConvert(typeof(decimal?)));
Assert.False(jsonConverter.CanConvert(typeof(double)));
Assert.True(jsonConverter.CanConvert(typeof(double)));
Assert.True(jsonConverter.CanConvert(typeof(double?)));
Assert.False(jsonConverter.CanConvert(typeof(float)));
Assert.False(jsonConverter.CanConvert(typeof(int)));
Assert.False(jsonConverter.CanConvert(typeof(string)));
@@ -755,12 +756,20 @@ namespace BTCPayServer.Tests
Assert.Equal(1m, jsonConverter.ReadJson(Get(numberJson), typeof(decimal), null, null));
Assert.Equal(1.2m, jsonConverter.ReadJson(Get(numberDecimalJson), typeof(decimal), null, null));
Assert.Null(jsonConverter.ReadJson(Get("null"), typeof(decimal?), null, null));
Assert.Equal((double)1.0, jsonConverter.ReadJson(Get(numberJson), typeof(double), null, null));
Assert.Equal((double)1.2, jsonConverter.ReadJson(Get(numberDecimalJson), typeof(double), null, null));
Assert.Null(jsonConverter.ReadJson(Get("null"), typeof(double?), null, null));
Assert.Throws<JsonSerializationException>(() =>
{
jsonConverter.ReadJson(Get("null"), typeof(decimal), null, null);
});Assert.Throws<JsonSerializationException>(() =>
{
jsonConverter.ReadJson(Get("null"), typeof(double), null, null);
});
Assert.Equal(1.2m, jsonConverter.ReadJson(Get(stringJson), typeof(decimal), null, null));
Assert.Equal(1.2m, jsonConverter.ReadJson(Get(stringJson), typeof(decimal?), null, null));
Assert.Equal(1.2, jsonConverter.ReadJson(Get(stringJson), typeof(double), null, null));
Assert.Equal(1.2, jsonConverter.ReadJson(Get(stringJson), typeof(double?), null, null));
}
}
}

View File

@@ -1,74 +1,52 @@
# How to be started for development
# Tooling
BTCPay Server tests depend on having a proper environment running with Postgres, Bitcoind, NBxplorer configured.
You can however use the `docker-compose.yml` of this folder to get it running.
In addition, when you run a debug session of BTCPay (Hitting F5 on Visual Studio Code or Visual Studio 2017), it will run the launch profile called `Docker-Regtest`. This launch profile depends on this `docker-compose` running.
This is running a bitcoind instance on regtest, a private bitcoin blockchain for testing on which you can generate blocks yourself.
```
docker-compose up dev
```
You can run the tests while it is running through your favorite IDE, or with
```
dotnet test
```
Once you want to stop
```
docker-compose down
```
If you want to stop, and remove all existing data
```
docker-compose down --v
```
You can run tests on `MySql` database instead of `Postgres` by setting environnement variable `TESTS_DB` equals to `MySql`.
This README describe some useful tooling that you may need during development and testing.
To learn how to get started with your local development environment, read [our documentation](https://docs.btcpayserver.org/LocalDevelopment/).
## How to manually test payments
### Using the test bitcoin-cli
You can call bitcoin-cli inside the container with `docker exec`, for example, if you want to send `0.23111090` to `mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf`:
```
You can call bitcoin-cli inside the container with `docker exec`.
For example, if you want to send `0.23111090` to `mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf`:
```sh
./docker-bitcoin-cli.sh sendtoaddress "mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf" 0.23111090
```
If you are using Powershell:
```powershell
.\docker-bitcoin-cli.ps1 sendtoaddress "mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf" 0.23111090
```
You can also generate blocks:
```powershell
.\docker-bitcoin-generate.ps1 3
```
### Using the test litecoin-cli
Same as bitcoin-cli, but with `.\docker-litecoin-cli.ps1` and `.\docker-litecoin-cli.sh` instead.
Same as bitcoin-cli, but with `.\docker-litecoin-cli.ps1` and `.\docker-litecoin-cli.sh` instead.
### Using the test lightning-cli
If you are using Linux:
```
```sh
./docker-customer-lightning-cli.sh pay lnbcrt100u1pd2e6uspp5ajnadvhazjrz55twd5k6yeg9u87wpw0q2fdr7g960yl5asv5fmnqdq9d3hkccqpxmedyrk0ehw5ueqx5e0r4qrrv74cewddfcvsxaawqz7634cmjj39sqwy5tvhz0hasktkk6t9pqfdh3edmf3z09zst5y7khv3rvxh8ctqqw6mwhh
```
If you are using Powershell:
```
```powershell
.\docker-customer-lightning-cli.ps1 pay lnbcrt100u1pd2e6uspp5ajnadvhazjrz55twd5k6yeg9u87wpw0q2fdr7g960yl5asv5fmnqdq9d3hkccqpxmedyrk0ehw5ueqx5e0r4qrrv74cewddfcvsxaawqz7634cmjj39sqwy5tvhz0hasktkk6t9pqfdh3edmf3z09zst5y7khv3rvxh8ctqqw6mwhh
```
If you get this message:
```
```json
{ "code" : 205, "message" : "Could not find a route", "data" : { "getroute_tries" : 1, "sendpay_tries" : 0 } }
```

View File

@@ -12,6 +12,7 @@ using BTCPayServer.Views.Wallets;
using Microsoft.EntityFrameworkCore;
using NBitcoin;
using NBitcoin.Payment;
using NBitpayClient;
using OpenQA.Selenium;
using Xunit;
using Xunit.Abstractions;
@@ -686,80 +687,6 @@ namespace BTCPayServer.Tests
}
}
[Fact]
[Trait("Selenium", "Selenium")]
[Trait("Altcoins", "Altcoins")]
public async Task CanCreateRefunds()
{
using (var s = SeleniumTester.Create())
{
s.Server.ActivateLTC();
await s.StartAsync();
var user = s.Server.NewAccount();
await user.GrantAccessAsync();
s.GoToLogin();
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
user.RegisterDerivationScheme("BTC");
await s.Server.ExplorerNode.GenerateAsync(1);
foreach (var multiCurrency in new[] { false, true })
{
if (multiCurrency)
user.RegisterDerivationScheme("LTC");
foreach (var rateSelection in new[] { "FiatText", "CurrentRateText", "RateThenText" })
await CanCreateRefundsCore(s, user, multiCurrency, rateSelection);
}
}
}
private static async Task CanCreateRefundsCore(SeleniumTester s, TestAccount user, bool multiCurrency, string rateSelection)
{
s.GoToHome();
s.Server.PayTester.ChangeRate("BTC_USD", new Rating.BidAsk(5000.0m, 5100.0m));
var invoice = await user.BitPay.CreateInvoiceAsync(new NBitpayClient.Invoice()
{
Currency = "USD",
Price = 5000.0m
});
var info = invoice.CryptoInfo.First(o => o.CryptoCode == "BTC");
var totalDue = decimal.Parse(info.TotalDue, CultureInfo.InvariantCulture);
var paid = totalDue + 0.1m;
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(info.Address, Network.RegTest), Money.Coins(paid));
await s.Server.ExplorerNode.GenerateAsync(1);
await TestUtils.EventuallyAsync(async () =>
{
invoice = await user.BitPay.GetInvoiceAsync(invoice.Id);
Assert.Equal("confirmed", invoice.Status);
});
// BTC crash by 50%
s.Server.PayTester.ChangeRate("BTC_USD", new Rating.BidAsk(5000.0m / 2.0m, 5100.0m / 2.0m));
s.GoToInvoice(invoice.Id);
s.Driver.FindElement(By.Id("refundlink")).Click();
if (multiCurrency)
{
s.Driver.FindElement(By.Id("SelectedPaymentMethod")).SendKeys("BTC" + Keys.Enter);
s.Driver.FindElement(By.Id("ok")).Click();
}
Assert.Contains("$5,500.00", s.Driver.PageSource); // Should propose reimburse in fiat
Assert.Contains("1.10000000 ₿", s.Driver.PageSource); // Should propose reimburse in BTC at the rate of before
Assert.Contains("2.20000000 ₿", s.Driver.PageSource); // Should propose reimburse in BTC at the current rate
s.Driver.FindElement(By.Id(rateSelection)).Click();
s.Driver.FindElement(By.Id("ok")).Click();
Assert.Contains("pull-payments", s.Driver.Url);
if (rateSelection == "FiatText")
Assert.Contains("$5,500.00", s.Driver.PageSource);
if (rateSelection == "CurrentRateText")
Assert.Contains("2.20000000 ₿", s.Driver.PageSource);
if (rateSelection == "RateThenText")
Assert.Contains("1.10000000 ₿", s.Driver.PageSource);
s.GoToHome();
s.GoToInvoices();
s.GoToInvoice(invoice.Id);
s.Driver.FindElement(By.Id("refundlink")).Click();
Assert.Contains("pull-payments", s.Driver.Url);
}
[Fact]
[Trait("Selenium", "Selenium")]
public async Task CanUsePullPaymentsViaUI()

View File

@@ -63,7 +63,7 @@ namespace BTCPayServer.Tests
PayTester.SSHConnection = GetEnvironment("TESTS_SSHCONNECTION", "root@127.0.0.1:21622");
PayTester.SocksEndpoint = GetEnvironment("TESTS_SOCKSENDPOINT", "localhost:9050");
}
#if ALTCOINS
public void ActivateLTC()
{
LTCExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_LTCRPCCONNECTION", "server=http://127.0.0.1:43783;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork<BTCPayNetwork>("LTC").NBitcoinNetwork);
@@ -78,7 +78,7 @@ namespace BTCPayServer.Tests
PayTester.Chains.Add("LBTC");
PayTester.LBTCNBXplorerUri = LBTCExplorerClient.Address;
}
#endif
public void ActivateLightning()
{
var btc = NetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBitcoinNetwork;
@@ -170,20 +170,21 @@ namespace BTCPayServer.Tests
{
get; set;
}
#if ALTCOINS
public RPCClient LTCExplorerNode
{
get; set;
}
public RPCClient LBTCExplorerNode { get; set; }
public ExplorerClient LTCExplorerClient { get; set; }
public ExplorerClient LBTCExplorerClient { get; set; }
#endif
public ExplorerClient ExplorerClient
{
get; set;
}
public ExplorerClient LTCExplorerClient { get; set; }
public ExplorerClient LBTCExplorerClient { get; set; }
readonly HttpClient _Http = new HttpClient();

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,419 @@
version: "3"
# Run `docker-compose up dev` for bootstrapping your development environment
# Doing so will expose NBXplorer, Bitcoind RPC and postgres port to the host so that tests can Run,
# The Visual Studio launch setting `Docker-regtest` is configured to use this environment.
services:
tests:
build:
context: ..
dockerfile: BTCPayServer.Tests/Dockerfile
args:
CONFIGURATION_NAME: Altcoins-Release
environment:
TESTS_BTCRPCCONNECTION: server=http://bitcoind:43782;ceiwHEbqWI83:DwubwWsoo3
TESTS_LTCRPCCONNECTION: server=http://litecoind:43782;ceiwHEbqWI83:DwubwWsoo3
TESTS_BTCNBXPLORERURL: http://nbxplorer:32838/
TESTS_LTCNBXPLORERURL: http://nbxplorer:32838/
TESTS_DB: "Postgres"
TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver
TESTS_HOSTNAME: tests
TESTS_RUN_EXTERNAL_INTEGRATION: ${TESTS_RUN_EXTERNAL_INTEGRATION:-false}
TESTS_AzureBlobStorageConnectionString: ${TESTS_AzureBlobStorageConnectionString:-none}
TEST_MERCHANTLIGHTNINGD: "type=clightning;server=unix://etc/merchant_lightningd_datadir/lightning-rpc"
TEST_CUSTOMERLIGHTNINGD: "type=clightning;server=unix://etc/customer_lightningd_datadir/lightning-rpc"
TEST_MERCHANTCHARGE: "type=charge;server=http://lightning-charged:9112/;api-token=foiewnccewuify"
TEST_MERCHANTLND: "https://lnd:lnd@merchant_lnd:8080/"
TESTS_INCONTAINER: "true"
TESTS_SSHCONNECTION: "root@sshd:22"
TESTS_SSHPASSWORD: ""
TESTS_SSHKEYFILE: ""
TESTS_SOCKSENDPOINT: "tor:9050"
expose:
- "80"
links:
- dev
extra_hosts:
- "tests:127.0.0.1"
volumes:
- "sshd_datadir:/root/.ssh"
- "customer_lightningd_datadir:/etc/customer_lightningd_datadir"
- "merchant_lightningd_datadir:/etc/merchant_lightningd_datadir"
# The dev container is not actually used, it is just handy to run `docker-compose up dev` to start all services
dev:
image: alpine:3.7
command: [ "/bin/sh", "-c", "trap : TERM INT; while :; do echo Ready to code and debug like a rockstar!!!; sleep 2073600; done & wait" ]
links:
- nbxplorer
- postgres
- customer_lightningd
- merchant_lightningd
- lightning-charged
- customer_lnd
- merchant_lnd
- sshd
- tor
- monero_wallet
sshd:
build:
context: .
dockerfile: sshd.Dockerfile
ports:
- "21622:22"
expose:
- 22
volumes:
- "sshd_datadir:/root/.ssh"
devlnd:
image: btcpayserver/bitcoin:0.19.0.1
environment:
BITCOIN_NETWORK: regtest
BITCOIN_EXTRA_ARGS: |
deprecatedrpc=signrawtransaction
connect=bitcoind:39388
links:
- nbxplorer
- postgres
- customer_lnd
- merchant_lnd
nbxplorer:
image: nicolasdorier/nbxplorer:2.1.35
restart: unless-stopped
ports:
- "32838:32838"
expose:
- "32838"
environment:
NBXPLORER_NETWORK: regtest
NBXPLORER_CHAINS: "btc,ltc,lbtc"
NBXPLORER_BTCRPCURL: http://bitcoind:43782/
NBXPLORER_BTCNODEENDPOINT: bitcoind:39388
NBXPLORER_BTCRPCUSER: ceiwHEbqWI83
NBXPLORER_BTCRPCPASSWORD: DwubwWsoo3
NBXPLORER_LTCRPCURL: http://litecoind:43782/
NBXPLORER_LTCNODEENDPOINT: litecoind:39388
NBXPLORER_LTCRPCUSER: ceiwHEbqWI83
NBXPLORER_LTCRPCPASSWORD: DwubwWsoo3
NBXPLORER_LBTCRPCURL: "http://elementsd-liquid:19332/"
NBXPLORER_LBTCNODEENDPOINT: "elementsd-liquid:19444"
NBXPLORER_LBTCRPCUSER: "liquid"
NBXPLORER_LBTCRPCPASSWORD: "liquid"
NBXPLORER_BIND: 0.0.0.0:32838
NBXPLORER_MINGAPSIZE: 5
NBXPLORER_MAXGAPSIZE: 10
NBXPLORER_VERBOSE: 1
NBXPLORER_NOAUTH: 1
links:
- bitcoind
- litecoind
- elementsd-liquid
bitcoind:
restart: unless-stopped
image: btcpayserver/bitcoin:0.19.0.1
environment:
BITCOIN_NETWORK: regtest
BITCOIN_EXTRA_ARGS: |-
rpcuser=ceiwHEbqWI83
rpcpassword=DwubwWsoo3
rpcport=43782
rpcbind=0.0.0.0:43782
port=39388
whitelist=0.0.0.0/0
zmqpubrawblock=tcp://0.0.0.0:28332
zmqpubrawtx=tcp://0.0.0.0:28333
deprecatedrpc=signrawtransaction
ports:
- "43782:43782"
- "39388:39388"
expose:
- "43782" # RPC
- "39388" # P2P
- "28332" # ZMQ
- "28333" # ZMQ
volumes:
- "bitcoin_datadir:/data"
customer_lightningd:
image: btcpayserver/lightning:v0.8.2-dev
stop_signal: SIGKILL
restart: unless-stopped
environment:
EXPOSE_TCP: "true"
LIGHTNINGD_CHAIN: "btc"
LIGHTNINGD_NETWORK: "regtest"
LIGHTNINGD_OPT: |
bitcoin-datadir=/etc/bitcoin
bitcoin-rpcconnect=bitcoind
announce-addr=customer_lightningd
log-level=debug
funding-confirms=1
dev-fast-gossip
dev-bitcoind-poll=1
ports:
- "30992:9835" # api port
expose:
- "9735" # server port
- "9835" # api port
volumes:
- "bitcoin_datadir:/etc/bitcoin"
- "customer_lightningd_datadir:/root/.lightning"
links:
- bitcoind
lightning-charged:
image: shesek/lightning-charge:0.4.19-standalone
restart: unless-stopped
environment:
NETWORK: regtest
API_TOKEN: foiewnccewuify
BITCOIND_RPCCONNECT: bitcoind
volumes:
- "bitcoin_datadir:/etc/bitcoin"
- "lightning_charge_datadir:/data"
- "merchant_lightningd_datadir:/etc/lightning"
expose:
- "9112" # Charge
- "9735" # Lightning
ports:
- "54938:9112" # Charge
links:
- bitcoind
- merchant_lightningd
merchant_lightningd:
image: btcpayserver/lightning:v0.8.2-dev
stop_signal: SIGKILL
environment:
EXPOSE_TCP: "true"
LIGHTNINGD_CHAIN: "btc"
LIGHTNINGD_NETWORK: "regtest"
LIGHTNINGD_OPT: |
bitcoin-datadir=/etc/bitcoin
bitcoin-rpcconnect=bitcoind
announce-addr=merchant_lightningd
funding-confirms=1
log-level=debug
dev-fast-gossip
dev-bitcoind-poll=1
ports:
- "30993:9835" # api port
expose:
- "9735" # server port
- "9835" # api port
volumes:
- "bitcoin_datadir:/etc/bitcoin"
- "merchant_lightningd_datadir:/root/.lightning"
links:
- bitcoind
litecoind:
restart: unless-stopped
image: nicolasdorier/docker-litecoin:0.16.3
environment:
BITCOIN_EXTRA_ARGS: |-
rpcuser=ceiwHEbqWI83
rpcpassword=DwubwWsoo3
regtest=1
rpcport=43782
port=39388
whitelist=0.0.0.0/0
ports:
- "43783:43782"
expose:
- "43782" # RPC
- "39388" # P2P
elementsd-liquid:
restart: always
container_name: btcpayserver_elementsd_liquid
image: btcpayserver/elements:0.18.1.7
environment:
ELEMENTS_CHAIN: elementsregtest
ELEMENTS_EXTRA_ARGS: |
mainchainrpcport=43782
mainchainrpchost=bitcoind
mainchainrpcuser=liquid
mainchainrpcpassword=liquid
rpcport=19332
rpcbind=0.0.0.0:19332
rpcauth=liquid:c8bf1a8961d97f224cb21224aaa8235d$$402f4a8907683d057b8c58a42940b6e54d1638322a42986ae28ebb844e603ae6
port=19444
whitelist=0.0.0.0/0
validatepegin=0
initialfreecoins=210000000000000
con_dyna_deploy_start=99999999999
expose:
- "19332"
- "19444"
ports:
- "19332:19332"
- "19444:19444"
volumes:
- "elementsd_liquid_datadir:/data"
postgres:
image: postgres:9.6.5
ports:
- "39372:5432"
expose:
- "5432"
merchant_lnd:
image: btcpayserver/lnd:v0.10.2-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"
LND_ENVIRONMENT: "regtest"
LND_EXPLORERURL: "http://nbxplorer:32838/"
LND_EXTRA_ARGS: |
restlisten=0.0.0.0:8080
rpclisten=127.0.0.1:10008
rpclisten=0.0.0.0:10009
bitcoin.node=bitcoind
bitcoind.rpchost=bitcoind:43782
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
bitcoind.zmqpubrawtx=tcp://bitcoind:28333
externalip=merchant_lnd:9735
bitcoin.defaultchanconfs=1
no-macaroons=1
debuglevel=debug
trickledelay=1000
ports:
- "35531:8080"
expose:
- "9735"
volumes:
- "merchant_lnd_datadir:/data"
- "bitcoin_datadir:/deps/.bitcoin"
links:
- bitcoind
customer_lnd:
image: btcpayserver/lnd:v0.10.2-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"
LND_ENVIRONMENT: "regtest"
LND_EXPLORERURL: "http://nbxplorer:32838/"
LND_EXTRA_ARGS: |
restlisten=0.0.0.0:8080
rpclisten=127.0.0.1:10008
rpclisten=0.0.0.0:10009
bitcoin.node=bitcoind
bitcoind.rpchost=bitcoind:43782
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
bitcoind.zmqpubrawtx=tcp://bitcoind:28333
externalip=customer_lnd:10009
bitcoin.defaultchanconfs=1
no-macaroons=1
debuglevel=debug
trickledelay=1000
ports:
- "35532:8080"
expose:
- "8080"
- "10009"
volumes:
- "customer_lnd_datadir:/root/.lnd"
- "bitcoin_datadir:/deps/.bitcoin"
links:
- bitcoind
tor:
restart: unless-stopped
image: btcpayserver/tor:0.4.1.5
container_name: tor
environment:
TOR_PASSWORD: btcpayserver
ports:
- "9050:9050" # SOCKS
- "9051:9051" # Tor Control
volumes:
- "tor_datadir:/home/tor/.tor"
- "torrcdir:/usr/local/etc/tor"
- "tor_servicesdir:/var/lib/tor/hidden_services"
monerod:
image: btcpayserver/monero:0.15.0.1-amd64
restart: unless-stopped
container_name: xmr_monerod
entrypoint: sleep 999999
# entrypoint: monerod --fixed-difficulty 200 --rpc-bind-ip=0.0.0.0 --confirm-external-bind --rpc-bind-port=18081 --block-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/block?cryptoCode=xmr&hash=%s" --testnet --no-igd --hide-my-port --offline
volumes:
- "monero_data:/home/monero/.bitmonero"
ports:
- "18081:18081"
monero_wallet:
image: btcpayserver/monero:0.15.0.1-amd64
restart: unless-stopped
container_name: xmr_wallet_rpc
entrypoint: monero-wallet-rpc --testnet --rpc-bind-ip=0.0.0.0 --disable-rpc-login --confirm-external-bind --rpc-bind-port=18082 --non-interactive --trusted-daemon --daemon-address=monerod:18081 --wallet-file=/wallet/wallet.keys --password-file=/wallet/password --tx-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/tx?cryptoCode=xmr&hash=%s"
ports:
- "18082:18082"
volumes:
- "./monero_wallet:/wallet"
depends_on:
- monerod
litecoind:
restart: unless-stopped
image: nicolasdorier/docker-litecoin:0.16.3
environment:
BITCOIN_EXTRA_ARGS: |-
rpcuser=ceiwHEbqWI83
rpcpassword=DwubwWsoo3
regtest=1
rpcport=43782
port=39388
whitelist=0.0.0.0/0
ports:
- "43783:43782"
expose:
- "43782" # RPC
- "39388" # P2P
elementsd-liquid:
restart: always
container_name: btcpayserver_elementsd_liquid
image: btcpayserver/elements:0.18.1.7
environment:
ELEMENTS_CHAIN: elementsregtest
ELEMENTS_EXTRA_ARGS: |
mainchainrpcport=43782
mainchainrpchost=bitcoind
mainchainrpcuser=liquid
mainchainrpcpassword=liquid
rpcport=19332
rpcbind=0.0.0.0:19332
rpcauth=liquid:c8bf1a8961d97f224cb21224aaa8235d$$402f4a8907683d057b8c58a42940b6e54d1638322a42986ae28ebb844e603ae6
port=19444
whitelist=0.0.0.0/0
validatepegin=0
initialfreecoins=210000000000000
con_dyna_deploy_start=99999999999
expose:
- "19332"
- "19444"
ports:
- "19332:19332"
- "19444:19444"
volumes:
- "elementsd_liquid_datadir:/data"
volumes:
sshd_datadir:
bitcoin_datadir:
elementsd_liquid_datadir:
customer_lightningd_datadir:
merchant_lightningd_datadir:
lightning_charge_datadir:
customer_lnd_datadir:
merchant_lnd_datadir:
tor_datadir:
torrcdir:
tor_servicesdir:
monero_data:

View File

@@ -1,28 +0,0 @@
version: "3"
services:
monerod:
image: btcpayserver/monero:0.15.0.1-amd64
restart: unless-stopped
container_name: xmr_monerod
entrypoint: sleep 999999
# entrypoint: monerod --fixed-difficulty 200 --rpc-bind-ip=0.0.0.0 --confirm-external-bind --rpc-bind-port=18081 --block-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/block?cryptoCode=xmr&hash=%s" --testnet --no-igd --hide-my-port --offline
volumes:
- "monero_data:/home/monero/.bitmonero"
ports:
- "18081:18081"
monero_wallet:
image: btcpayserver/monero:0.15.0.1-amd64
restart: unless-stopped
container_name: xmr_wallet_rpc
entrypoint: monero-wallet-rpc --testnet --rpc-bind-ip=0.0.0.0 --disable-rpc-login --confirm-external-bind --rpc-bind-port=18082 --non-interactive --trusted-daemon --daemon-address=monerod:18081 --wallet-file=/wallet/wallet.keys --password-file=/wallet/password --tx-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/tx?cryptoCode=xmr&hash=%s"
ports:
- "18082:18082"
volumes:
- "./monero_wallet:/wallet"
depends_on:
- monerod
volumes:
monero_data:

View File

@@ -9,11 +9,11 @@ services:
build:
context: ..
dockerfile: BTCPayServer.Tests/Dockerfile
args:
CONFIGURATION_NAME: Release
environment:
TESTS_BTCRPCCONNECTION: server=http://bitcoind:43782;ceiwHEbqWI83:DwubwWsoo3
TESTS_LTCRPCCONNECTION: server=http://litecoind:43782;ceiwHEbqWI83:DwubwWsoo3
TESTS_BTCNBXPLORERURL: http://nbxplorer:32838/
TESTS_LTCNBXPLORERURL: http://nbxplorer:32838/
TESTS_DB: "Postgres"
TESTS_POSTGRES: User ID=postgres;Host=postgres;Port=5432;Database=btcpayserver
TESTS_HOSTNAME: tests
@@ -78,7 +78,7 @@ services:
- customer_lnd
- merchant_lnd
nbxplorer:
image: nicolasdorier/nbxplorer:2.1.35
image: nicolasdorier/nbxplorer:2.1.37
restart: unless-stopped
ports:
- "32838:32838"
@@ -86,19 +86,11 @@ services:
- "32838"
environment:
NBXPLORER_NETWORK: regtest
NBXPLORER_CHAINS: "btc,ltc,lbtc"
NBXPLORER_CHAINS: "btc"
NBXPLORER_BTCRPCURL: http://bitcoind:43782/
NBXPLORER_BTCNODEENDPOINT: bitcoind:39388
NBXPLORER_BTCRPCUSER: ceiwHEbqWI83
NBXPLORER_BTCRPCPASSWORD: DwubwWsoo3
NBXPLORER_LTCRPCURL: http://litecoind:43782/
NBXPLORER_LTCNODEENDPOINT: litecoind:39388
NBXPLORER_LTCRPCUSER: ceiwHEbqWI83
NBXPLORER_LTCRPCPASSWORD: DwubwWsoo3
NBXPLORER_LBTCRPCURL: "http://elementsd-liquid:19332/"
NBXPLORER_LBTCNODEENDPOINT: "elementsd-liquid:19444"
NBXPLORER_LBTCRPCUSER: "liquid"
NBXPLORER_LBTCRPCPASSWORD: "liquid"
NBXPLORER_BIND: 0.0.0.0:32838
NBXPLORER_MINGAPSIZE: 5
NBXPLORER_MAXGAPSIZE: 10
@@ -106,8 +98,6 @@ services:
NBXPLORER_NOAUTH: 1
links:
- bitcoind
- litecoind
- elementsd-liquid
bitcoind:
@@ -209,51 +199,6 @@ services:
links:
- bitcoind
litecoind:
restart: unless-stopped
image: nicolasdorier/docker-litecoin:0.16.3
environment:
BITCOIN_EXTRA_ARGS: |-
rpcuser=ceiwHEbqWI83
rpcpassword=DwubwWsoo3
regtest=1
rpcport=43782
port=39388
whitelist=0.0.0.0/0
ports:
- "43783:43782"
expose:
- "43782" # RPC
- "39388" # P2P
elementsd-liquid:
restart: always
container_name: btcpayserver_elementsd_liquid
image: btcpayserver/elements:0.18.1.7
environment:
ELEMENTS_CHAIN: elementsregtest
ELEMENTS_EXTRA_ARGS: |
mainchainrpcport=43782
mainchainrpchost=bitcoind
mainchainrpcuser=liquid
mainchainrpcpassword=liquid
rpcport=19332
rpcbind=0.0.0.0:19332
rpcauth=liquid:c8bf1a8961d97f224cb21224aaa8235d$$402f4a8907683d057b8c58a42940b6e54d1638322a42986ae28ebb844e603ae6
port=19444
whitelist=0.0.0.0/0
validatepegin=0
initialfreecoins=210000000000000
con_dyna_deploy_start=99999999999
expose:
- "19332"
- "19444"
ports:
- "19332:19332"
- "19444:19444"
volumes:
- "elementsd_liquid_datadir:/data"
postgres:
image: postgres:9.6.5
ports:
@@ -336,7 +281,6 @@ services:
- "torrcdir:/usr/local/etc/tor"
- "tor_servicesdir:/var/lib/tor/hidden_services"
volumes:
sshd_datadir:
bitcoin_datadir:

View File

@@ -6,4 +6,4 @@ if [ ! -z "$TEST_FILTERS" ]; then
FILTERS="--filter $TEST_FILTERS"
fi
dotnet test $FILTERS --no-build -v n < /dev/null
dotnet test -c ${CONFIGURATION_NAME} $FILTERS --no-build -v n < /dev/null

View File

@@ -1,10 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" />
<PropertyGroup Condition="'$(Configuration)' == 'Debug' And '$(RazorCompileOnBuild)' != 'true'">
<RazorCompileOnBuild>false</RazorCompileOnBuild>
<DefineConstants>$(DefineConstants);RAZOR_RUNTIME_COMPILE</DefineConstants>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
</PropertyGroup>
@@ -21,6 +18,10 @@
<None Remove="Build\**" />
<None Remove="wwwroot\bundles\jqueryvalidate\**" />
<None Remove="wwwroot\vendor\jquery-nice-select\**" />
<Content Update="Views\Shared\NBXSyncSummary.cshtml">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Remove="Currencies.txt" />
@@ -28,12 +29,28 @@
<ItemGroup>
<EmbeddedResource Include="bundleconfig.json" />
</ItemGroup>
<ItemGroup Condition="'$(Altcoins)' == 'true'">
<PackageReference Include="Nethereum.ABI" Version="3.8.0" />
<PackageReference Include="Nethereum.HdWallet" Version="3.8.0" />
<PackageReference Include="Nethereum.StandardTokenEIP20" Version="3.8.0" />
<PackageReference Include="Nethereum.Web3" Version="3.8.0" />
</ItemGroup>
<ItemGroup Condition="'$(Altcoins)' != 'true'">
<Content Remove="Services\Altcoins\**\*" />
<Content Remove="Views\EthereumLikeStore\**\*" />
<Content Remove="Views\MoneroLikeStore\**\*" />
<Content Remove="Views\Shared\Ethereum\**\*" />
<Content Remove="Views\Shared\Monero\**\*" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BTCPayServer.Hwi" Version="1.1.3" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.2.0" />
<PackageReference Include="BuildBundlerMinifier" Version="3.2.435" />
<PackageReference Include="BundlerMinifier.Core" Version="3.2.435" />
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
<PackageReference Include="CsvHelper" Version="15.0.5" />
<PackageReference Include="HtmlSanitizer" Version="4.0.217" />
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.9.8">

View File

@@ -10,6 +10,7 @@ using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using NBitcoin;
using Serilog.Events;
using TwentyTwenty.Storage;
namespace BTCPayServer.Configuration
{
@@ -90,11 +91,14 @@ namespace BTCPayServer.Configuration
var networkProvider = new BTCPayNetworkProvider(NetworkType);
var filtered = networkProvider.Filter(supportedChains.ToArray());
var elementsBased = filtered.GetAll().OfType<ElementsBTCPayNetwork>();
var parentChains = elementsBased.Select(network => network.NetworkCryptoCode.ToUpperInvariant()).Distinct();
var allSubChains = networkProvider.GetAll().OfType<ElementsBTCPayNetwork>()
.Where(network => parentChains.Contains(network.NetworkCryptoCode)).Select(network => network.CryptoCode.ToUpperInvariant());
supportedChains.AddRange(allSubChains);
#if ALTCOINS
supportedChains.AddRange(filtered.GetAllElementsSubChains());
#endif
#if !ALTCOINS
var onlyBTC = supportedChains.Count == 1 && supportedChains.First() == "BTC";
if (!onlyBTC)
throw new ConfigException($"This build of BTCPay Server does not support altcoins");
#endif
NetworkProvider = networkProvider.Filter(supportedChains.ToArray());
foreach (var chain in supportedChains)
{
@@ -172,6 +176,7 @@ namespace BTCPayServer.Configuration
SocksEndpoint = endpoint;
}
UpdateUrl = conf.GetOrDefault<Uri>("updateurl", null);
var sshSettings = ParseSSHConfiguration(conf);
if ((!string.IsNullOrEmpty(sshSettings.Password) || !string.IsNullOrEmpty(sshSettings.KeyFile)) && !string.IsNullOrEmpty(sshSettings.Server))
@@ -297,5 +302,6 @@ namespace BTCPayServer.Configuration
set;
}
public string TorrcFile { get; set; }
public Uri UpdateUrl { get; set; }
}
}

View File

@@ -38,6 +38,7 @@ namespace BTCPayServer.Configuration
app.Option("--sshtrustedfingerprints", "SSH Host public key fingerprint or sha256 (default: empty, it will allow untrusted connections)", CommandOptionType.SingleValue);
app.Option("--torrcfile", "Path to torrc file containing hidden services directories (default: empty)", CommandOptionType.SingleValue);
app.Option("--socksendpoint", "Socks endpoint to connect to onion urls (default: empty)", CommandOptionType.SingleValue);
app.Option("--updateurl", $"Url used for once a day new release version check. Check performed only if value is not empty (default: empty)", CommandOptionType.SingleValue);
app.Option("--debuglog", "A rolling log file for debug messages.", CommandOptionType.SingleValue);
app.Option("--debugloglevel", "The severity you log (default:information)", CommandOptionType.SingleValue);
app.Option("--disable-registration", "Disables new user registrations (default:true)", CommandOptionType.SingleValue);

View File

@@ -0,0 +1,10 @@
namespace BTCPayServer.Contracts
{
public interface ISyncSummaryProvider
{
bool AllAvailable();
string Partial { get; }
}
}

View File

@@ -443,13 +443,8 @@ namespace BTCPayServer.Controllers
var settings = await _SettingsRepository.GetSettingAsync<ThemeSettings>();
settings.FirstRun = false;
await _SettingsRepository.UpdateSetting<ThemeSettings>(settings);
if (_Options.DisableRegistration)
{
// Once the admin user has been created lock subsequent user registrations (needs to be disabled for unit tests that require multiple users).
Logs.PayServer.LogInformation("First admin created, disabling subscription (disable-registration is set to true)");
policies.LockSubscription = true;
await _SettingsRepository.UpdateSetting(policies);
}
await _SettingsRepository.FirstAdminRegistered(policies, _Options.UpdateUrl != null, _Options.DisableRegistration);
RegisteredAdmin = true;
}
@@ -626,7 +621,7 @@ namespace BTCPayServer.Controllers
private bool CanLoginOrRegister()
{
return _btcPayServerEnvironment.IsDevelopping || _btcPayServerEnvironment.IsSecure;
return _btcPayServerEnvironment.IsDeveloping || _btcPayServerEnvironment.IsSecure;
}
private void SetInsecureFlags()

View File

@@ -47,9 +47,42 @@ namespace BTCPayServer.Controllers
public string CreatedAppId { get; set; }
public async Task<IActionResult> ListApps()
public async Task<IActionResult> ListApps(
string sortOrder = null,
string sortOrderColumn = null
)
{
var apps = await _AppService.GetAllApps(GetUserId());
if (sortOrder != null && sortOrderColumn != null)
{
apps = apps.OrderByDescending(app =>
{
switch (sortOrderColumn)
{
case nameof(app.AppName):
return app.AppName;
case nameof(app.StoreName):
return app.StoreName;
case nameof(app.AppType):
return app.AppType;
default:
return app.Id;
}
}).ToArray();
switch (sortOrder)
{
case "desc":
ViewData[$"{sortOrderColumn}SortOrder"] = "asc";
break;
case "asc":
apps = apps.Reverse().ToArray();
ViewData[$"{sortOrderColumn}SortOrder"] = "desc";
break;
}
}
return View(new ListAppsViewModel()
{
Apps = apps

View File

@@ -288,7 +288,7 @@ namespace BTCPayServer.Controllers.GreenField
protected bool CanUseInternalLightning(bool doingAdminThings)
{
return (_btcPayServerEnvironment.IsDevelopping || User.IsInRole(Roles.ServerAdmin) ||
return (_btcPayServerEnvironment.IsDeveloping || User.IsInRole(Roles.ServerAdmin) ||
(_cssThemeManager.AllowLightningInternalNodeForAll && !doingAdminThings));
}

View File

@@ -148,13 +148,7 @@ namespace BTCPayServer.Controllers.GreenField
await _userManager.AddToRoleAsync(user, Roles.ServerAdmin);
if (!anyAdmin)
{
if (_options.DisableRegistration)
{
// automatically lock subscriptions now that we have our first admin
Logs.PayServer.LogInformation("First admin created, disabling subscription (disable-registration is set to true)");
policies.LockSubscription = true;
await _settingsRepository.UpdateSetting(policies);
}
await _settingsRepository.FirstAdminRegistered(policies, _options.UpdateUrl != null, _options.DisableRegistration);
}
}
_eventAggregator.Publish(new UserRegisteredEvent() { RequestUri = Request.GetAbsoluteRootUri(), User = user, Admin = request.IsAdministrator is true });

View File

@@ -29,7 +29,6 @@ using Microsoft.EntityFrameworkCore;
using NBitcoin;
using NBitpayClient;
using NBXplorer;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using StoreData = BTCPayServer.Data.StoreData;
@@ -598,55 +597,23 @@ namespace BTCPayServer.Controllers
return Ok("{}");
}
public class InvoicePreference
{
public int? TimezoneOffset { get; set; }
public string SearchTerm { get; set; }
}
[HttpGet]
[Route("invoices")]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
[BitpayAPIConstraint(false)]
public async Task<IActionResult> ListInvoices(string searchTerm = null, int skip = 0, int count = 50, int? timezoneOffset = null)
public async Task<IActionResult> ListInvoices(InvoicesModel model = null)
{
// If the user enter an empty searchTerm, then the variable will be null and not empty string
// but we want searchTerm to be null only if the user is browsing the page via some link
// NOT if the user entered some empty search
searchTerm = searchTerm is string ? searchTerm :
this.Request.Query.ContainsKey(nameof(searchTerm)) ? string.Empty :
null;
if (searchTerm is null)
{
if (this.Request.Cookies.TryGetValue("ListInvoicePreferences", out var str))
{
var preferences = JsonConvert.DeserializeObject<InvoicePreference>(str);
searchTerm = preferences.SearchTerm;
timezoneOffset = timezoneOffset is int v ? v : preferences.TimezoneOffset;
}
}
else
{
var preferences = new InvoicePreference();
preferences.SearchTerm = searchTerm;
preferences.TimezoneOffset = timezoneOffset;
this.Response.Cookies.Append("ListInvoicePreferences", JsonConvert.SerializeObject(preferences));
}
var fs = new SearchString(searchTerm);
model = this.ParseListQuery(model ?? new InvoicesModel());
var fs = new SearchString(model.SearchTerm);
var storeIds = fs.GetFilterArray("storeid") != null ? fs.GetFilterArray("storeid") : new List<string>().ToArray();
var model = new InvoicesModel
{
SearchTerm = searchTerm,
Skip = skip,
Count = count,
StoreIds = storeIds,
TimezoneOffset = timezoneOffset
};
InvoiceQuery invoiceQuery = GetInvoiceQuery(searchTerm, timezoneOffset ?? 0);
model.StoreIds = storeIds;
InvoiceQuery invoiceQuery = GetInvoiceQuery(model.SearchTerm, model.TimezoneOffset ?? 0);
var counting = _InvoiceRepository.GetInvoicesTotal(invoiceQuery);
invoiceQuery.Count = count;
invoiceQuery.Skip = skip;
invoiceQuery.Count = model.Count;
invoiceQuery.Skip = model.Skip;
var list = await _InvoiceRepository.GetInvoices(invoiceQuery);
foreach (var invoice in list)

View File

@@ -61,23 +61,22 @@ namespace BTCPayServer.Controllers
[HttpGet]
[Route("")]
[BitpayAPIConstraint(false)]
public async Task<IActionResult> GetPaymentRequests(int skip = 0, int count = 50, bool includeArchived = false)
public async Task<IActionResult> GetPaymentRequests(ListPaymentRequestsViewModel model = null)
{
model = this.ParseListQuery(model ?? new ListPaymentRequestsViewModel());
var includeArchived = new SearchString(model.SearchTerm).GetFilterBool("includearchived") == true;
var result = await _PaymentRequestRepository.FindPaymentRequests(new PaymentRequestQuery()
{
UserId = GetUserId(),
Skip = skip,
Count = count,
Skip = model.Skip,
Count = model.Count,
IncludeArchived = includeArchived
});
return View(new ListPaymentRequestsViewModel()
{
IncludeArchived = includeArchived,
Skip = skip,
Count = count,
Total = result.Total,
Items = result.Items.Select(data => new ViewPaymentRequestViewModel(data)).ToList()
});
model.Total = result.Total;
model.Items = result.Items.Select(data => new ViewPaymentRequestViewModel(data)).ToList();
return View(model);
}
[HttpGet]

View File

@@ -172,7 +172,7 @@ namespace BTCPayServer.Controllers
private bool CanUseInternalLightning()
{
return (_BTCPayEnv.IsDevelopping || User.IsInRole(Roles.ServerAdmin) || _CssThemeManager.AllowLightningInternalNodeForAll);
return (_BTCPayEnv.IsDeveloping || User.IsInRole(Roles.ServerAdmin) || _CssThemeManager.AllowLightningInternalNodeForAll);
}
}
}

View File

@@ -520,7 +520,9 @@ namespace BTCPayServer.Controllers
Value = value,
WalletId = new WalletId(store.Id, paymentMethodId.CryptoCode),
Enabled = !excludeFilters.Match(paymentMethodId) && strategy != null,
#if ALTCOINS
Collapsed = network is ElementsBTCPayNetwork elementsBTCPayNetwork && elementsBTCPayNetwork.NetworkCryptoCode != elementsBTCPayNetwork.CryptoCode && string.IsNullOrEmpty(value)
#endif
});
break;
case LightningPaymentType _:

View File

@@ -259,7 +259,11 @@ namespace BTCPayServer.Controllers
[Route("{walletId}/transactions")]
public async Task<IActionResult> WalletTransactions(
[ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId, string labelFilter = null)
WalletId walletId,
string labelFilter = null,
int skip = 0,
int count = 50
)
{
DerivationSchemeSettings paymentMethod = GetDerivationSchemeSettings(walletId);
if (paymentMethod == null)
@@ -271,7 +275,12 @@ namespace BTCPayServer.Controllers
var transactions = await wallet.FetchTransactions(paymentMethod.AccountDerivation);
var walletBlob = await walletBlobAsync;
var walletTransactionsInfo = await walletTransactionsInfoAsync;
var model = new ListTransactionsViewModel();
var model = new ListTransactionsViewModel
{
Skip = skip,
Count = count,
Total = 0
};
if (transactions == null)
{
TempData.SetStatusMessageModel(new StatusMessageModel()
@@ -309,7 +318,8 @@ namespace BTCPayServer.Controllers
model.Transactions.Add(vm);
}
model.Transactions = model.Transactions.OrderByDescending(t => t.Timestamp).ToList();
model.Total = model.Transactions.Count;
model.Transactions = model.Transactions.OrderByDescending(t => t.Timestamp).Skip(skip).Take(count).ToList();
}
return View(model);

View File

@@ -149,9 +149,9 @@ namespace BTCPayServer.Data
}
public class PayoutBlob
{
[JsonConverter(typeof(DecimalStringJsonConverter))]
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Amount { get; set; }
[JsonConverter(typeof(DecimalStringJsonConverter))]
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal? CryptoAmount { get; set; }
public int MinimumConfirmation { get; set; } = 1;
public IClaimDestination Destination { get; set; }
@@ -190,9 +190,9 @@ namespace BTCPayServer.Data
public string Name { get; set; }
public string Currency { get; set; }
public int Divisibility { get; set; }
[JsonConverter(typeof(DecimalStringJsonConverter))]
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal Limit { get; set; }
[JsonConverter(typeof(DecimalStringJsonConverter))]
[JsonConverter(typeof(NumericStringJsonConverter))]
public decimal MinimumClaim { get; set; }
public PullPaymentView View { get; set; } = new PullPaymentView();
[JsonConverter(typeof(TimeSpanJsonConverter))]

View File

@@ -64,7 +64,6 @@ namespace BTCPayServer.Data
{
if (storeData == null)
throw new ArgumentNullException(nameof(storeData));
networks = networks.UnfilteredNetworks;
#pragma warning disable CS0618
bool btcReturned = false;

View File

@@ -4,6 +4,7 @@ using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.WebSockets;
using System.Reflection;
using System.Security.Claims;
using System.Text;
using System.Threading;
@@ -449,5 +450,21 @@ namespace BTCPayServer
};
return controller.View("PostRedirect", redirectVm);
}
public static string ToSql<TEntity>(this IQueryable<TEntity> query) where TEntity : class
{
var enumerator = query.Provider.Execute<IEnumerable<TEntity>>(query.Expression).GetEnumerator();
var relationalCommandCache = enumerator.Private("_relationalCommandCache");
var selectExpression = relationalCommandCache.Private<Microsoft.EntityFrameworkCore.Query.SqlExpressions.SelectExpression>("_selectExpression");
var factory = relationalCommandCache.Private<Microsoft.EntityFrameworkCore.Query.IQuerySqlGeneratorFactory>("_querySqlGeneratorFactory");
var sqlGenerator = factory.Create();
var command = sqlGenerator.GetCommand(selectExpression);
string sql = command.CommandText;
return sql;
}
private static object Private(this object obj, string privateField) => obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);
private static T Private<T>(this object obj, string privateField) => (T)obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BTCPayServer.Configuration;
using BTCPayServer.Logging;
using BTCPayServer.Services;
using Microsoft.Extensions.Logging;
namespace BTCPayServer
{
// All logic that would otherwise be duplicated across solution goes into this utility class
// ~If~ Once this starts growing out of control, begin extracting action logic classes out of here
// Also some of logic in here may be result of parallel development of Greenfield API
// It's much better that we extract those common methods then copy paste and maintain same code across codebase
internal static class ActionLogicExtensions
{
internal static async Task FirstAdminRegistered(this SettingsRepository settingsRepository, PoliciesSettings policies,
bool updateCheck, bool disableRegistrations)
{
if (updateCheck)
{
Logs.PayServer.LogInformation("First admin created, enabling checks for new versions");
policies.CheckForNewVersions = updateCheck;
}
if (disableRegistrations)
{
// Once the admin user has been created lock subsequent user registrations (needs to be disabled for unit tests that require multiple users).
Logs.PayServer.LogInformation("First admin created, disabling subscription (disable-registration is set to true)");
policies.LockSubscription = true;
}
if (updateCheck || disableRegistrations)
await settingsRepository.UpdateSetting(policies);
}
}
}

View File

@@ -0,0 +1,93 @@
using System;
using System.Reflection;
using BTCPayServer.Models;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Models.PaymentRequestViewModels;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace BTCPayServer
{
// Classes here remember users preferences on certain pages and store them in unified blob cookie "UserPreferCookie"
public static class ControllerBaseExtension
{
public static T ParseListQuery<T>(this ControllerBase ctrl, T model) where T : BasePagingViewModel
{
PropertyInfo prop;
if (model is InvoicesModel)
prop = typeof(UserPrefsCookie).GetProperty(nameof(UserPrefsCookie.InvoicesQuery));
else if (model is ListPaymentRequestsViewModel)
prop = typeof(UserPrefsCookie).GetProperty(nameof(UserPrefsCookie.PaymentRequestsQuery));
else
throw new Exception("Unsupported BasePagingViewModel for cookie user preferences saving");
return ProcessParse(ctrl, model, prop);
}
private static T ProcessParse<T>(ControllerBase ctrl, T model, PropertyInfo prop) where T : BasePagingViewModel
{
var prefCookie = parsePrefCookie(ctrl);
// If the user enter an empty searchTerm, then the variable will be null and not empty string
// but we want searchTerm to be null only if the user is browsing the page via some link
// NOT if the user entered some empty search
var searchTerm = model.SearchTerm;
searchTerm = searchTerm is string ? searchTerm :
ctrl.Request.Query.ContainsKey(nameof(searchTerm)) ? string.Empty :
null;
if (searchTerm is null)
{
var section = prop.GetValue(prefCookie) as ListQueryDataHolder;
if (section != null && !String.IsNullOrEmpty(section.SearchTerm))
{
model.SearchTerm = section.SearchTerm;
model.TimezoneOffset = section.TimezoneOffset ?? 0;
}
}
else
{
prop.SetValue(prefCookie, new ListQueryDataHolder(model.SearchTerm, model.TimezoneOffset));
ctrl.Response.Cookies.Append(nameof(UserPrefsCookie), JsonConvert.SerializeObject(prefCookie));
}
return model;
}
private static UserPrefsCookie parsePrefCookie(ControllerBase ctrl)
{
var prefCookie = new UserPrefsCookie();
ctrl.Request.Cookies.TryGetValue(nameof(UserPrefsCookie), out var strPrefCookie);
if (!String.IsNullOrEmpty(strPrefCookie))
{
try
{
prefCookie = JsonConvert.DeserializeObject<UserPrefsCookie>(strPrefCookie);
}
catch { /* ignore cookie deserialization failures */ }
}
return prefCookie;
}
class UserPrefsCookie
{
public ListQueryDataHolder InvoicesQuery { get; set; }
public ListQueryDataHolder PaymentRequestsQuery { get; set; }
}
class ListQueryDataHolder
{
public ListQueryDataHolder() { }
public ListQueryDataHolder(string searchTerm, int? timezoneOffset)
{
SearchTerm = searchTerm;
TimezoneOffset = timezoneOffset;
}
public int? TimezoneOffset { get; set; }
public string SearchTerm { get; set; }
}
}
}

View File

@@ -16,6 +16,7 @@ namespace BTCPayServer
return money.ToDecimal(MoneyUnit.BTC);
case MoneyBag mb:
return mb.Select(money => money.GetValue(network)).Sum();
#if ALTCOINS
case AssetMoney assetMoney:
if (network is ElementsBTCPayNetwork elementsBTCPayNetwork)
{
@@ -24,6 +25,7 @@ namespace BTCPayServer
: 0;
}
throw new NotSupportedException("IMoney type not supported");
#endif
default:
throw new NotSupportedException("IMoney type not supported");
}

View File

@@ -10,12 +10,11 @@ namespace BTCPayServer.HostedServices
{
public abstract class BaseAsyncService : IHostedService
{
private CancellationTokenSource _Cts;
private CancellationTokenSource _Cts = new CancellationTokenSource();
protected Task[] _Tasks;
public virtual Task StartAsync(CancellationToken cancellationToken)
{
_Cts = new CancellationTokenSource();
_Tasks = InitializeTasks();
return Task.CompletedTask;
}

View File

@@ -0,0 +1,121 @@
using System;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Configuration;
using BTCPayServer.Logging;
using BTCPayServer.Services;
using BTCPayServer.Services.Notifications;
using BTCPayServer.Services.Notifications.Blobs;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.HostedServices
{
public class NewVersionCheckerHostedService : BaseAsyncService
{
private readonly SettingsRepository _settingsRepository;
private readonly BTCPayServerEnvironment _env;
private readonly NotificationSender _notificationSender;
private readonly IVersionFetcher _versionFetcher;
public NewVersionCheckerHostedService(SettingsRepository settingsRepository, BTCPayServerEnvironment env,
NotificationSender notificationSender, IVersionFetcher versionFetcher)
{
_settingsRepository = settingsRepository;
_env = env;
_notificationSender = notificationSender;
_versionFetcher = versionFetcher;
}
internal override Task[] InitializeTasks()
{
return new Task[] { CreateLoopTask(LoopVersionCheck) };
}
protected async Task LoopVersionCheck()
{
try
{
await ProcessVersionCheck();
}
catch (Exception ex)
{
Logs.Events.LogError(ex, "Error while performing new version check");
}
await Task.Delay(TimeSpan.FromDays(1), Cancellation);
}
public async Task ProcessVersionCheck()
{
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
if (policies.CheckForNewVersions)
{
var tag = await _versionFetcher.Fetch(Cancellation);
if (tag != null && tag != _env.Version)
{
var dh = await _settingsRepository.GetSettingAsync<NewVersionCheckerDataHolder>() ?? new NewVersionCheckerDataHolder();
if (dh.LastVersion != tag)
{
await _notificationSender.SendNotification(new AdminScope(), new NewVersionNotification(tag));
dh.LastVersion = tag;
await _settingsRepository.UpdateSetting(dh);
}
}
}
}
}
public class NewVersionCheckerDataHolder
{
public string LastVersion { get; set; }
}
public interface IVersionFetcher
{
Task<string> Fetch(CancellationToken cancellation);
}
public class GithubVersionFetcher : IVersionFetcher
{
private readonly HttpClient _httpClient;
private readonly Uri _updateurl;
public GithubVersionFetcher(IHttpClientFactory httpClientFactory, BTCPayServerOptions options)
{
_httpClient = httpClientFactory.CreateClient(nameof(GithubVersionFetcher));
_httpClient.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
_httpClient.DefaultRequestHeaders.Add("User-Agent", "BTCPayServer/NewVersionChecker");
_updateurl = options.UpdateUrl;
}
private static readonly Regex _releaseVersionTag = new Regex("^(v[1-9]+(\\.[0-9]+)*(-[0-9]+)?)$");
public async Task<string> Fetch(CancellationToken cancellation)
{
if (_updateurl == null)
return null;
using (var resp = await _httpClient.GetAsync(_updateurl, cancellation))
{
var strResp = await resp.Content.ReadAsStringAsync();
if (resp.IsSuccessStatusCode)
{
var jobj = JObject.Parse(strResp);
var tag = jobj["tag_name"].ToString();
var isReleaseVersionTag = _releaseVersionTag.IsMatch(tag);
return isReleaseVersionTag ? tag : null;
}
else
{
Logs.Events.LogWarning($"Unsuccessful status code returned during new version check. " +
$"Url: {_updateurl}, HTTP Code: {resp.StatusCode}, Response Body: {strResp}");
}
}
return null;
}
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.IO;
using System.Threading;
using BTCPayServer.Configuration;
using BTCPayServer.Contracts;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.HostedServices;
@@ -16,7 +17,6 @@ using BTCPayServer.Security;
using BTCPayServer.Security.Bitpay;
using BTCPayServer.Security.GreenField;
using BTCPayServer.Services;
using BTCPayServer.Services.Altcoins.Monero;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Fees;
using BTCPayServer.Services.Invoices;
@@ -47,7 +47,9 @@ using NBXplorer.DerivationStrategy;
using Newtonsoft.Json;
using NicolasDorier.RateLimits;
using Serilog;
#if ALTCOINS
using BTCPayServer.Services.Altcoins.Monero;
#endif
namespace BTCPayServer.Hosting
{
public static class BTCPayServerServices
@@ -75,7 +77,9 @@ namespace BTCPayServer.Hosting
services.RegisterJsonConverter(n => new ClaimDestinationJsonConverter(n));
services.AddPayJoinServices();
#if ALTCOINS
services.AddMoneroLike();
#endif
services.TryAddSingleton<SettingsRepository>();
services.TryAddSingleton<LabelFactory>();
services.TryAddSingleton<TorServices>();
@@ -168,6 +172,7 @@ namespace BTCPayServer.Hosting
htmlSanitizer.RemovingStyle += (sender, args) => { args.Cancel = true; };
htmlSanitizer.AllowedAttributes.Add("class");
htmlSanitizer.AllowedTags.Add("iframe");
htmlSanitizer.AllowedTags.Add("style");
htmlSanitizer.AllowedTags.Remove("img");
htmlSanitizer.AllowedAttributes.Add("webkitallowfullscreen");
htmlSanitizer.AllowedAttributes.Add("allowfullscreen");
@@ -177,6 +182,7 @@ namespace BTCPayServer.Hosting
services.TryAddSingleton<LightningConfigurationProvider>();
services.TryAddSingleton<LanguageService>();
services.TryAddSingleton<NBXplorerDashboard>();
services.TryAddSingleton<ISyncSummaryProvider, NBXSyncSummaryProvider>();
services.TryAddSingleton<StoreRepository>();
services.TryAddSingleton<PaymentRequestRepository>();
services.TryAddSingleton<BTCPayWalletProvider>();
@@ -233,7 +239,11 @@ namespace BTCPayServer.Hosting
services.AddSingleton<IBackgroundJobClient, BackgroundJobClient>();
services.AddScoped<IAuthorizationHandler, CookieAuthorizationHandler>();
services.AddScoped<IAuthorizationHandler, BitpayAuthorizationHandler>();
services.AddSingleton<IVersionFetcher, GithubVersionFetcher>();
services.AddSingleton<IHostedService, NewVersionCheckerHostedService>();
services.AddSingleton<INotificationHandler, NewVersionNotification.Handler>();
services.AddSingleton<INotificationHandler, InvoiceEventNotification.Handler>();
services.AddSingleton<INotificationHandler, PayoutNotification.Handler>();
@@ -278,7 +288,7 @@ namespace BTCPayServer.Hosting
{
var btcPayEnv = provider.GetService<BTCPayServerEnvironment>();
var rateLimits = new RateLimitService();
if (btcPayEnv.IsDevelopping)
if (btcPayEnv.IsDeveloping)
{
rateLimits.SetZone($"zone={ZoneLimits.Login} rate=1000r/min burst=100 nodelay");
rateLimits.SetZone($"zone={ZoneLimits.Register} rate=1000r/min burst=100 nodelay");

View File

@@ -0,0 +1,14 @@
using System.ComponentModel.DataAnnotations;
namespace BTCPayServer.Models
{
public abstract class BasePagingViewModel
{
public int Skip { get; set; } = 0;
public int Count { get; set; } = 50;
public int Total { get; set; }
[DisplayFormat(ConvertEmptyStringToNull = false)]
public string SearchTerm { get; set; }
public int? TimezoneOffset { get; set; }
}
}

View File

@@ -4,13 +4,8 @@ using BTCPayServer.Services.Invoices;
namespace BTCPayServer.Models.InvoicingModels
{
public class InvoicesModel
public class InvoicesModel : BasePagingViewModel
{
public int Skip { get; set; }
public int Count { get; set; }
public int Total { get; set; }
public string SearchTerm { get; set; }
public int? TimezoneOffset { get; set; }
public List<InvoiceModel> Invoices { get; set; } = new List<InvoiceModel>();
public string[] StoreIds { get; set; }
}

View File

@@ -8,15 +8,10 @@ using PaymentRequestData = BTCPayServer.Data.PaymentRequestData;
namespace BTCPayServer.Models.PaymentRequestViewModels
{
public class ListPaymentRequestsViewModel
public class ListPaymentRequestsViewModel : BasePagingViewModel
{
public int Skip { get; set; }
public int Count { get; set; }
public List<ViewPaymentRequestViewModel> Items { get; set; }
public int Total { get; set; }
public bool IncludeArchived { get; set; }
}
public class UpdatePaymentRequestViewModel

View File

@@ -11,6 +11,8 @@ namespace BTCPayServer.Models.StoreViewModels
public DerivationSchemeViewModel()
{
}
[Display(Name = "Derivation scheme")]
public string DerivationScheme
{
get; set;
@@ -31,7 +33,7 @@ namespace BTCPayServer.Models.StoreViewModels
public KeyPath RootKeyPath { get; set; }
[Display(Name = "Electrum/Hardware Wallet File")]
[Display(Name = "Wallet File")]
public IFormFile WalletFile { get; set; }
public string Config { get; set; }
public string Source { get; set; }

View File

@@ -19,5 +19,8 @@ namespace BTCPayServer.Models.WalletViewModels
}
public HashSet<Label> Labels { get; set; } = new HashSet<Label>();
public List<TransactionViewModel> Transactions { get; set; } = new List<TransactionViewModel>();
public int Skip { get; set; }
public int Count { get; set; }
public int Total { get; set; }
}
}

View File

@@ -1,4 +1,5 @@
using System;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using NBXplorer.Models;
@@ -12,11 +13,14 @@ namespace BTCPayServer.Models.WalletViewModels
public bool Ok => IsServerAdmin && IsSupportedByCurrency && IsFullySync;
[Range(1000, 10_000)]
[DisplayName("Batch size")]
public int BatchSize { get; set; } = 3000;
[Range(0, 10_000_000)]
[DisplayName("Starting index")]
public int StartingIndex { get; set; } = 0;
[Range(100, 100000)]
[DisplayName("Gap limit")]
public int GapLimit { get; set; } = 10000;
public int? Progress { get; set; }

View File

@@ -26,6 +26,7 @@ namespace BTCPayServer.Models.WalletViewModels
_FileName = value;
}
}
[Display(Name = "PSBT content")]
public string PSBT { get; set; }
public List<string> Errors { get; set; } = new List<string>();

View File

@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
namespace BTCPayServer.Models.WalletViewModels
@@ -6,6 +7,7 @@ namespace BTCPayServer.Models.WalletViewModels
public class WalletSettingsViewModel
{
public string Label { get; set; }
[DisplayName("Derivation scheme")]
public string DerivationScheme { get; set; }
public string DerivationSchemeInput { get; set; }
[Display(Name = "Is signing key")]
@@ -20,9 +22,12 @@ namespace BTCPayServer.Models.WalletViewModels
public class WalletSettingsAccountKeyViewModel
{
[DisplayName("Account key")]
public string AccountKey { get; set; }
[DisplayName("Master fingerprint")]
[Validation.HDFingerPrintValidator]
public string MasterFingerprint { get; set; }
[DisplayName("Account key path")]
[Validation.KeyPathValidator]
public string AccountKeyPath { get; set; }
}

View File

@@ -502,7 +502,7 @@ namespace BTCPayServer.Payments.PayJoin
{
var o = new JObject();
o.Add(new JProperty("errorCode", PayjoinReceiverHelper.GetErrorCode(error)));
if (string.IsNullOrEmpty(debug) || !_env.IsDevelopping)
if (string.IsNullOrEmpty(debug) || !_env.IsDeveloping)
{
o.Add(new JProperty("message", PayjoinReceiverHelper.GetMessage(error)));
}

View File

@@ -2,7 +2,9 @@ using System;
using System.Globalization;
using System.Linq;
using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices;
using NBitcoin;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Payments
@@ -64,6 +66,19 @@ namespace BTCPayServer.Payments
txId = txId.Split('-').First();
return string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, txId);
}
public override string GetPaymentLink(BTCPayNetworkBase network, IPaymentMethodDetails paymentMethodDetails,
Money cryptoInfoDue, string serverUri)
{
var bip21 = ((BTCPayNetwork)network).GenerateBIP21(paymentMethodDetails.GetPaymentDestination(), cryptoInfoDue);
if ((paymentMethodDetails as BitcoinLikeOnChainPaymentMethod)?.PayjoinEnabled is true)
{
bip21 += $"&{PayjoinClient.BIP21EndpointKey}={serverUri.WithTrailingSlash()}{network.CryptoCode}/{PayjoinClient.BIP21EndpointKey}";
}
return bip21;
}
public override string InvoiceViewPaymentPartialName { get; } = "ViewBitcoinLikePaymentData";
}
}

View File

@@ -1,5 +1,7 @@
using System;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Services.Invoices;
using NBitcoin;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -8,13 +10,14 @@ namespace BTCPayServer.Payments
public class LightningPaymentType : PaymentType
{
public static LightningPaymentType Instance { get; } = new LightningPaymentType();
private LightningPaymentType()
{
}
public override string ToPrettyString() => "Off-Chain";
public override string GetId() => "LightningLike";
public override CryptoPaymentData DeserializePaymentData(BTCPayNetworkBase network, string str)
{
return ((BTCPayNetwork)network).ToObject<LightningLikePaymentData>(str);
@@ -35,7 +38,8 @@ namespace BTCPayServer.Payments
return JsonConvert.SerializeObject(details);
}
public override ISupportedPaymentMethod DeserializeSupportedPaymentMethod(BTCPayNetworkBase network, JToken value)
public override ISupportedPaymentMethod DeserializeSupportedPaymentMethod(BTCPayNetworkBase network,
JToken value)
{
return JsonConvert.DeserializeObject<LightningSupportedPaymentMethod>(value.ToString());
}
@@ -44,6 +48,14 @@ namespace BTCPayServer.Payments
{
return null;
}
public override string GetPaymentLink(BTCPayNetworkBase network, IPaymentMethodDetails paymentMethodDetails,
Money cryptoInfoDue, string serverUri)
{
return
$"lightning:{paymentMethodDetails.GetPaymentDestination().ToUpperInvariant().Replace("LIGHTNING:", "", StringComparison.InvariantCultureIgnoreCase)}";
}
public override string InvoiceViewPaymentPartialName { get; } = "ViewLightningLikePaymentData";
}
}

View File

@@ -1,6 +1,9 @@
using System;
#if ALTCOINS
using BTCPayServer.Services.Altcoins.Monero.Payments;
#endif
using BTCPayServer.Services.Invoices;
using NBitcoin;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Payments
@@ -31,9 +34,11 @@ namespace BTCPayServer.Payments
case "offchain":
type = PaymentTypes.LightningLike;
break;
#if ALTCOINS
case "monerolike":
type = MoneroPaymentType.Instance;
break;
#endif
default:
type = null;
return false;
@@ -63,6 +68,8 @@ namespace BTCPayServer.Payments
public abstract string SerializePaymentMethodDetails(BTCPayNetworkBase network, IPaymentMethodDetails details);
public abstract ISupportedPaymentMethod DeserializeSupportedPaymentMethod(BTCPayNetworkBase network, JToken value);
public abstract string GetTransactionLink(BTCPayNetworkBase network, string txId);
public abstract string GetPaymentLink(BTCPayNetworkBase network, IPaymentMethodDetails paymentMethodDetails,
Money cryptoInfoDue, string serverUri);
public abstract string InvoiceViewPaymentPartialName { get; }
}
}

View File

@@ -1,13 +1,12 @@
{
"profiles": {
"Docker-Regtest": {
"Bitcoin": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"BTCPAY_NETWORK": "regtest",
"BTCPAY_LAUNCHSETTINGS": "true",
"BTCPAY_BUNDLEJSCSS": "false",
"BTCPAY_LTCEXPLORERURL": "http://127.0.0.1:32838/",
"BTCPAY_BTCLIGHTNING": "type=clightning;server=tcp://127.0.0.1:30993",
"BTCPAY_BTCEXTERNALLNDGRPC": "type=lnd-grpc;server=https://lnd:lnd@127.0.0.1:53280/;allowinsecure=true",
"BTCPAY_BTCEXTERNALLNDREST": "type=lnd-rest;server=https://lnd:lnd@127.0.0.1:53280/lnd-rest/btc/;allowinsecure=true;macaroonfilepath=D:\\admin.macaroon",
@@ -16,15 +15,47 @@
"BTCPAY_ALLOW-ADMIN-REGISTRATION": "true",
"BTCPAY_DISABLE-REGISTRATION": "false",
"ASPNETCORE_ENVIRONMENT": "Development",
"BTCPAY_CHAINS": "btc,ltc",
"BTCPAY_CHAINS": "btc",
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver",
"BTCPAY_DEBUGLOG": "debug.log",
"BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
"BTCPAY_SOCKSENDPOINT": "localhost:9050",
"BTCPAY_UPDATEURL": ""
},
"applicationUrl": "http://127.0.0.1:14142/"
},
"Bitcoin-HTTPS": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"BTCPAY_NETWORK": "regtest",
"BTCPAY_LAUNCHSETTINGS": "true",
"BTCPAY_PORT": "14142",
"BTCPAY_HttpsUseDefaultCertificate": "true",
"BTCPAY_BUNDLEJSCSS": "false",
"BTCPAY_BTCLIGHTNING": "type=clightning;server=tcp://127.0.0.1:30993",
"BTCPAY_BTCEXTERNALLNDGRPC": "type=lnd-grpc;server=https://lnd:lnd@127.0.0.1:53280/;allowinsecure=true",
"BTCPAY_BTCEXTERNALLNDREST": "type=lnd-rest;server=https://lnd:lnd@127.0.0.1:53280/lnd-rest/btc/;allowinsecure=true",
"BTCPAY_BTCEXTERNALLNDSEEDBACKUP": "../BTCPayServer.Tests/TestData/LndSeedBackup/walletunlock.json",
"BTCPAY_BTCEXTERNALSPARK": "server=/spark/btc/;cookiefile=fake",
"BTCPAY_BTCEXTERNALCHARGE": "server=https://127.0.0.1:53280/mycharge/btc/;cookiefilepath=fake",
"BTCPAY_EXTERNALCONFIGURATOR": "passwordfile=testpwd;server=/configurator",
"BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/",
"BTCPAY_ALLOW-ADMIN-REGISTRATION": "true",
"BTCPAY_DISABLE-REGISTRATION": "false",
"ASPNETCORE_ENVIRONMENT": "Development",
"BTCPAY_CHAINS": "btc",
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver",
"BTCPAY_EXTERNALSERVICES": "totoservice:totolink;",
"BTCPAY_SSHCONNECTION": "root@127.0.0.1:21622",
"BTCPAY_SSHPASSWORD": "opD3i2282D",
"BTCPAY_DEBUGLOG": "debug.log",
"BTCPAY_TORRCFILE": "../BTCPayServer.Tests/TestData/Tor/torrc",
"BTCPAY_SOCKSENDPOINT": "localhost:9050"
},
"applicationUrl": "http://127.0.0.1:14142/"
"applicationUrl": "https://localhost:14142/"
},
"Docker-Regtest-https": {
"Altcoins-HTTPS": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {

View File

@@ -58,6 +58,16 @@ namespace BTCPayServer
return null;
var val = Filters[key].First();
// handle special string values
if (val == "-24h")
return DateTimeOffset.UtcNow.AddHours(-24).AddMinutes(timezoneOffset);
else if (val == "-3d")
return DateTimeOffset.UtcNow.AddDays(-3).AddMinutes(timezoneOffset);
else if (val == "-7d")
return DateTimeOffset.UtcNow.AddDays(-7).AddMinutes(timezoneOffset);
// default parsing logic
var success = DateTimeOffset.TryParse(val, null as IFormatProvider, DateTimeStyles.AssumeUniversal, out var r);
if (success)
{

View File

@@ -1,3 +1,4 @@
#if ALTCOINS
using System;
using System.Collections.Generic;
@@ -16,3 +17,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.Configuration
public string WalletDirectory { get; set; }
}
}
#endif

View File

@@ -1,3 +1,4 @@
#if ALTCOINS
using System;
using System.Linq;
using BTCPayServer.Configuration;
@@ -23,6 +24,7 @@ namespace BTCPayServer.Services.Altcoins.Monero
serviceCollection.AddSingleton<MoneroLikePaymentMethodHandler>();
serviceCollection.AddSingleton<IPaymentMethodHandler>(provider => provider.GetService<MoneroLikePaymentMethodHandler>());
serviceCollection.AddSingleton<IStoreNavExtension, MoneroStoreNavExtension>();
serviceCollection.AddSingleton<ISyncSummaryProvider, MoneroSyncSummaryProvider>();
return serviceCollection;
}
@@ -67,3 +69,4 @@ namespace BTCPayServer.Services.Altcoins.Monero
}
}
}
#endif

View File

@@ -1,3 +1,4 @@
#if ALTCOINS
using BTCPayServer.Contracts;
namespace BTCPayServer.Services.Altcoins.Monero
@@ -7,3 +8,4 @@ namespace BTCPayServer.Services.Altcoins.Monero
public string Partial { get; } = "Monero/StoreNavMoneroExtension";
}
}
#endif

View File

@@ -1,3 +1,4 @@
#if ALTCOINS
using BTCPayServer.Payments;
namespace BTCPayServer.Services.Altcoins.Monero.Payments
@@ -34,3 +35,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
public decimal NextNetworkFee { get; set; }
}
}
#endif

View File

@@ -1,3 +1,4 @@
#if ALTCOINS
using BTCPayServer.Client.Models;
using BTCPayServer.Payments;
using BTCPayServer.Services.Altcoins.Monero.Utils;
@@ -65,3 +66,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
}
}
}
#endif

View File

@@ -1,3 +1,4 @@
#if ALTCOINS
using System;
using System.Collections.Generic;
using System.Globalization;
@@ -75,20 +76,15 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
public override void PreparePaymentModel(PaymentModel model, InvoiceResponse invoiceResponse, StoreBlob storeBlob)
{
var paymentMethodId = new PaymentMethodId(model.CryptoCode, PaymentType);
var client = _moneroRpcProvider.WalletRpcClients[model.CryptoCode];
var cryptoInfo = invoiceResponse.CryptoInfo.First(o => o.GetpaymentMethodId() == paymentMethodId);
var network = _networkProvider.GetNetwork<MoneroLikeSpecificBtcPayNetwork>(model.CryptoCode);
model.IsLightning = false;
model.PaymentMethodName = GetPaymentMethodName(network);
model.CryptoImage = GetCryptoImage(network);
model.InvoiceBitcoinUrl = client.SendCommandAsync<MakeUriRequest, MakeUriResponse>("make_uri", new MakeUriRequest()
model.InvoiceBitcoinUrl = MoneroPaymentType.Instance.GetPaymentLink(network, new MoneroLikeOnChainPaymentMethodDetails()
{
Address = cryptoInfo.Address,
Amount = MoneroMoney.Convert(decimal.Parse(cryptoInfo.Due, CultureInfo.InvariantCulture))
}).GetAwaiter()
.GetResult().Uri;
DepositAddress = cryptoInfo.Address
}, cryptoInfo.Due, null);
model.InvoiceBitcoinUrlQR = model.InvoiceBitcoinUrl;
}
public override string GetCryptoImage(PaymentMethodId paymentMethodId)
@@ -128,3 +124,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
}
}
}
#endif

View File

@@ -1,6 +1,8 @@
#if ALTCOINS
using System.Globalization;
using BTCPayServer.Payments;
using BTCPayServer.Services.Invoices;
using NBitcoin;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -44,6 +46,13 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
return string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, txId);
}
public override string GetPaymentLink(BTCPayNetworkBase network, IPaymentMethodDetails paymentMethodDetails, Money cryptoInfoDue, string serverUri)
{
return
$"{(network as MoneroLikeSpecificBtcPayNetwork).UriScheme}:{paymentMethodDetails.GetPaymentDestination()}?tx_amount={cryptoInfoDue.ToDecimal(MoneyUnit.BTC)}";
}
public override string InvoiceViewPaymentPartialName { get; } = "Monero/ViewMoneroLikePaymentData";
}
}
#endif

View File

@@ -1,3 +1,4 @@
#if ALTCOINS
using BTCPayServer.Payments;
namespace BTCPayServer.Services.Altcoins.Monero.Payments
@@ -10,3 +11,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.Payments
public PaymentMethodId PaymentId => new PaymentMethodId(CryptoCode, MoneroPaymentType.Instance);
}
}
#endif

View File

@@ -1,3 +1,4 @@
#if ALTCOINS
using BTCPayServer.Filters;
using Microsoft.AspNetCore.Mvc;
@@ -36,3 +37,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.RPC
}
}
#endif

View File

@@ -1,3 +1,4 @@
#if ALTCOINS
namespace BTCPayServer.Services.Altcoins.Monero.RPC
{
public class MoneroEvent
@@ -13,3 +14,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.RPC
}
}
}
#endif

View File

@@ -1,3 +1,4 @@
#if ALTCOINS
using System;
using System.Threading;
using System.Threading.Tasks;
@@ -64,3 +65,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services
}
}
}
#endif

View File

@@ -1,3 +1,4 @@
#if ALTCOINS
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -374,3 +375,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services
}
}
}
#endif

View File

@@ -1,3 +1,4 @@
#if ALTCOINS
using System;
using System.Collections.Concurrent;
using System.Collections.Immutable;
@@ -63,7 +64,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services
await daemonRpcClient.SendCommandAsync<JsonRpcClient.NoRequestModel, SyncInfoResponse>("sync_info",
JsonRpcClient.NoRequestModel.Instance);
summary.TargetHeight = daemonResult.TargetHeight ?? daemonResult.Height;
summary.Synced = daemonResult.Height >= summary.TargetHeight && (summary.TargetHeight > 0 || _btcPayServerEnvironment.IsDevelopping);
summary.Synced = daemonResult.Height >= summary.TargetHeight && (summary.TargetHeight > 0 || _btcPayServerEnvironment.IsDeveloping);
summary.CurrentHeight = daemonResult.Height;
summary.UpdatedAt = DateTime.Now;
summary.DaemonAvailable = true;
@@ -117,3 +118,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.Services
}
}
}
#endif

View File

@@ -0,0 +1,24 @@
#if ALTCOINS
using System.Linq;
using BTCPayServer.Contracts;
namespace BTCPayServer.Services.Altcoins.Monero.Services
{
public class MoneroSyncSummaryProvider : ISyncSummaryProvider
{
private readonly MoneroRPCProvider _moneroRpcProvider;
public MoneroSyncSummaryProvider(MoneroRPCProvider moneroRpcProvider)
{
_moneroRpcProvider = moneroRpcProvider;
}
public bool AllAvailable()
{
return _moneroRpcProvider.Summaries.All(pair => pair.Value.WalletAvailable);
}
public string Partial { get; } = "Monero/MoneroSyncSummary";
}
}
#endif

View File

@@ -1,3 +1,4 @@
#if ALTCOINS
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
@@ -302,3 +303,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
}
}
}
#endif

View File

@@ -1,3 +1,4 @@
#if ALTCOINS
using System;
namespace BTCPayServer.Services.Altcoins.Monero.UI
@@ -13,3 +14,4 @@ namespace BTCPayServer.Services.Altcoins.Monero.UI
public string TransactionLink { get; set; }
}
}
#endif

View File

@@ -26,7 +26,7 @@ namespace BTCPayServer.Services
{
public BTCPayNetworkJsonSerializerSettings(BTCPayNetworkProvider networkProvider, IEnumerable<IJsonConverterRegistration> jsonSerializers)
{
foreach (var network in networkProvider.UnfilteredNetworks.GetAll().OfType<BTCPayNetwork>())
foreach (var network in networkProvider.GetAll().OfType<BTCPayNetwork>())
{
var serializer = new JsonSerializerSettings();
foreach (var jsonSerializer in jsonSerializers)

View File

@@ -22,6 +22,12 @@ namespace BTCPayServer.Services
#else
Build = "Release";
#endif
#if ALTCOINS
AltcoinsVersion = true;
#else
AltcoinsVersion = false;
#endif
Environment = env;
NetworkType = provider.NetworkType;
this.torServices = torServices;
@@ -46,8 +52,9 @@ namespace BTCPayServer.Services
{
get; set;
}
public bool AltcoinsVersion { get; set; }
public bool IsDevelopping
public bool IsDeveloping
{
get
{
@@ -72,6 +79,8 @@ namespace BTCPayServer.Services
{
StringBuilder txt = new StringBuilder();
txt.Append($"@Copyright BTCPayServer v{Version}");
if (AltcoinsVersion)
txt.Append($" (altcoins)");
if (!Environment.IsProduction() || !Build.Equals("Release", StringComparison.OrdinalIgnoreCase))
{
txt.Append($" Environment: {Environment.EnvironmentName} Build: {Build}");

View File

@@ -1,143 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
// Ref: https://www.codeproject.com/Articles/566656/CSV-Serializer-for-NET
namespace BTCPayServer.Services.Invoices.Export
{
/// <summary>
/// Serialize and Deserialize Lists of any object type to CSV.
/// </summary>
public class CsvSerializer<T> where T : class, new()
{
private readonly List<PropertyInfo> _properties;
public bool IgnoreEmptyLines { get; set; } = true;
public bool IgnoreReferenceTypesExceptString { get; set; } = true;
public string NewlineReplacement { get; set; } = ((char)0x254).ToString(CultureInfo.InvariantCulture);
public char Separator { get; set; } = ',';
public string RowNumberColumnTitle { get; set; } = "RowNumber";
public bool UseLineNumbers { get; set; } = false;
public bool UseEofLiteral { get; set; } = false;
/// <summary>
/// Csv Serializer
/// Initialize by selected properties from the type to be de/serialized
/// </summary>
public CsvSerializer()
{
var type = typeof(T);
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance
| BindingFlags.GetProperty | BindingFlags.SetProperty);
var q = properties.AsQueryable();
if (IgnoreReferenceTypesExceptString)
{
q = q.Where(a => a.PropertyType.IsValueType || a.PropertyType.Name == "String");
}
var r = from a in q
where a.GetCustomAttribute<CsvIgnoreAttribute>() == null
select a;
_properties = r.ToList();
}
/// <summary>
/// Serialize
/// </summary>
/// <param name="stream">stream</param>
/// <param name="data">data</param>
public string Serialize(IList<T> data)
{
var sb = new StringBuilder();
var values = new List<string>();
sb.AppendLine(GetHeader());
var row = 1;
foreach (var item in data)
{
values.Clear();
if (UseLineNumbers)
{
values.Add(row++.ToString(CultureInfo.InvariantCulture));
}
foreach (var p in _properties)
{
var raw = p.GetValue(item);
var value = raw == null ? "" :
raw.ToString()
.Replace("\"", "``", StringComparison.OrdinalIgnoreCase)
.Replace(Environment.NewLine, NewlineReplacement, StringComparison.OrdinalIgnoreCase);
value = String.Format(CultureInfo.InvariantCulture, "\"{0}\"", value);
values.Add(value);
}
sb.AppendLine(String.Join(Separator.ToString(CultureInfo.InvariantCulture), values.ToArray()));
}
if (UseEofLiteral)
{
values.Clear();
if (UseLineNumbers)
{
values.Add(row++.ToString(CultureInfo.InvariantCulture));
}
values.Add("EOF");
sb.AppendLine(string.Join(Separator.ToString(CultureInfo.InvariantCulture), values.ToArray()));
}
return sb.ToString();
}
/// <summary>
/// Get Header
/// </summary>
/// <returns></returns>
private string GetHeader()
{
var header = _properties.Select(a => a.Name);
if (UseLineNumbers)
{
header = new string[] { RowNumberColumnTitle }.Union(header);
}
return string.Join(Separator.ToString(CultureInfo.InvariantCulture), header.ToArray());
}
}
public class CsvIgnoreAttribute : Attribute { }
public class InvalidCsvFormatException : Exception
{
/// <summary>
/// Invalid Csv Format Exception
/// </summary>
/// <param name="message">message</param>
public InvalidCsvFormatException(string message)
: base(message)
{
}
public InvalidCsvFormatException(string message, Exception ex)
: base(message, ex)
{
}
}
}

View File

@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using BTCPayServer.Services.Rates;
using CsvHelper.Configuration;
using Newtonsoft.Json;
namespace BTCPayServer.Services.Invoices.Export
@@ -42,10 +44,15 @@ namespace BTCPayServer.Services.Invoices.Export
private string processCsv(List<ExportInvoiceHolder> invoices)
{
var serializer = new CsvSerializer<ExportInvoiceHolder>();
var csv = serializer.Serialize(invoices);
return csv;
using StringWriter writer = new StringWriter();
using var csvWriter = new CsvHelper.CsvWriter(writer, new CsvConfiguration(CultureInfo.InvariantCulture), true);
csvWriter.WriteHeader<ExportInvoiceHolder>();
foreach (var invoice in invoices)
{
csvWriter.WriteRecord(invoice);
}
csvWriter.Flush();
return writer.ToString();
}
private IEnumerable<ExportInvoiceHolder> convertFromDb(InvoiceEntity invoice)

View File

@@ -456,7 +456,8 @@ namespace BTCPayServer.Services.Invoices
{
cryptoInfo.PaymentUrls = new InvoicePaymentUrls()
{
BOLT11 = $"lightning:{cryptoInfo.Address}"
BOLT11 = paymentId.PaymentType.GetPaymentLink(info.Network, details, cryptoInfo.Due,
ServerUrl)
};
}
else if (paymentId.PaymentType == PaymentTypes.BTCLike)
@@ -466,15 +467,10 @@ namespace BTCPayServer.Services.Invoices
minerInfo.SatoshiPerBytes = ((BitcoinLikeOnChainPaymentMethod)details).FeeRate
.GetFee(1).Satoshi;
dto.MinerFees.TryAdd(cryptoInfo.CryptoCode, minerInfo);
var bip21 = ((BTCPayNetwork)info.Network).GenerateBIP21(cryptoInfo.Address, cryptoInfo.Due);
if ((details as BitcoinLikeOnChainPaymentMethod)?.PayjoinEnabled is true)
cryptoInfo.PaymentUrls = new InvoicePaymentUrls()
{
bip21 += $"&{PayjoinClient.BIP21EndpointKey}={ServerUrl.WithTrailingSlash()}{cryptoCode}/{PayjoinClient.BIP21EndpointKey}";
}
cryptoInfo.PaymentUrls = new NBitpayClient.InvoicePaymentUrls()
{
BIP21 = bip21,
BIP21 = paymentId.PaymentType.GetPaymentLink(info.Network, details, cryptoInfo.Due,
ServerUrl)
};
#pragma warning disable 618
@@ -558,7 +554,12 @@ namespace BTCPayServer.Services.Invoices
r.CryptoCode = paymentMethodId.CryptoCode;
r.PaymentType = paymentMethodId.PaymentType.ToString();
r.ParentEntity = this;
r.Network = Networks?.UnfilteredNetworks.GetNetwork<BTCPayNetworkBase>(r.CryptoCode);
if (Networks != null)
{
r.Network = Networks.GetNetwork<BTCPayNetworkBase>(r.CryptoCode);
if (r.Network is null)
continue;
}
paymentMethods.Add(r);
}
}

View File

@@ -45,7 +45,7 @@ retry:
catch when (retryCount++ < 5) { goto retry; }
_IndexerThread = new CustomThreadPool(1, "Invoice Indexer");
_ContextFactory = contextFactory;
_Networks = networks.UnfilteredNetworks;
_Networks = networks;
}
public InvoiceEntity CreateNewInvoice()
@@ -527,7 +527,9 @@ retry:
private IQueryable<Data.InvoiceData> GetInvoiceQuery(ApplicationDbContext context, InvoiceQuery queryObject)
{
IQueryable<Data.InvoiceData> query = context.Invoices;
IQueryable<Data.InvoiceData> query = queryObject.UserId is null
? context.Invoices
: context.UserStore.Where(u => u.ApplicationUserId == queryObject.UserId).SelectMany(c => c.StoreData.Invoices);
if (!queryObject.IncludeArchived)
{
@@ -546,11 +548,6 @@ retry:
query = query.Where(i => stores.Contains(i.StoreDataId));
}
if (queryObject.UserId != null)
{
query = query.Where(i => i.StoreData.UserStores.Any(u => u.ApplicationUserId == queryObject.UserId));
}
if (!string.IsNullOrEmpty(queryObject.TextSearch))
{
var ids = new HashSet<string>(SearchInvoice(queryObject.TextSearch)).ToArray();
@@ -605,7 +602,6 @@ retry:
if (queryObject.Count != null)
query = query.Take(queryObject.Count.Value);
return query;
}
@@ -628,7 +624,6 @@ retry:
query = query.Include(o => o.HistoricalAddressInvoices).Include(o => o.AddressInvoices);
if (queryObject.IncludeEvents)
query = query.Include(o => o.Events);
var data = await query.ToArrayAsync().ConfigureAwait(false);
return data.Select(ToEntity).ToArray();
}

View File

@@ -0,0 +1,22 @@
using BTCPayServer.Contracts;
using BTCPayServer.HostedServices;
namespace BTCPayServer.Services
{
public class NBXSyncSummaryProvider : ISyncSummaryProvider
{
private readonly NBXplorerDashboard _nbXplorerDashboard;
public NBXSyncSummaryProvider(NBXplorerDashboard nbXplorerDashboard)
{
_nbXplorerDashboard = nbXplorerDashboard;
}
public bool AllAvailable()
{
return _nbXplorerDashboard.IsFullySynched();
}
public string Partial { get; } = "NBXSyncSummary";
}
}

View File

@@ -24,6 +24,8 @@ namespace BTCPayServer.Services
public bool AllowHotWalletForAll { get; set; }
[Display(Name = "Allow non-admins to import their hot wallets to the node wallet")]
public bool AllowHotWalletRPCImportForAll { get; set; }
[Display(Name = "Check releases on GitHub and alert when new BTCPayServer version is available")]
public bool CheckForNewVersions { get; set; }
[Display(Name = "Display app on website root")]
public string RootAppId { get; set; }

Some files were not shown because too many files have changed in this diff Show More