Compare commits

..

1 Commits

Author SHA1 Message Date
334abdc9d1 bump 2019-02-27 20:31:00 +09:00
778 changed files with 38090 additions and 58183 deletions

View File

@ -1,60 +1,40 @@
version: 2
jobs:
fast_tests:
machine:
enabled: true
steps:
build:
machine:
docker_layer_caching: true
steps:
- checkout
- run:
command: |
cd .circleci && ./run-tests.sh "Fast=Fast"
selenium_tests:
machine:
enabled: true
steps:
- checkout
- run:
command: |
cd .circleci && ./run-tests.sh "Selenium=Selenium"
integration_tests:
machine:
enabled: true
steps:
- checkout
- run:
command: |
cd .circleci && ./run-tests.sh "Integration=Integration"
external_tests:
machine:
enabled: true
steps:
- checkout
- run:
command: |
if [ "$CIRCLE_PROJECT_USERNAME" == "btcpayserver" ] && [ "$CIRCLE_PROJECT_REPONAME" == "btcpayserver" ]; then
cd .circleci && ./run-tests.sh "ExternalIntegration=ExternalIntegration"
else
echo "Skipping running ExternalIntegration tests outside of context of main user and repository that have access to secrets"
fi
test:
machine:
docker_layer_caching: true
steps:
- checkout
- run:
command: |
cd BTCPayServer.Tests
docker-compose down --v
docker-compose build
docker-compose run tests
# publish jobs require $DOCKERHUB_REPO, $DOCKERHUB_USER, $DOCKERHUB_PASS defined
amd64:
publish_docker_linuxamd64:
machine:
enabled: true
docker_layer_caching: true
steps:
- checkout
- run:
command: |
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 -t $DOCKERHUB_REPO:$LATEST_TAG-amd64 -f Dockerfile.linuxamd64 .
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-amd64
arm32v7:
publish_docker_linuxarm:
machine:
enabled: true
docker_layer_caching: true
steps:
- checkout
- run:
@ -62,25 +42,11 @@ jobs:
sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset
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 -t $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 -f Dockerfile.linuxarm32v7 .
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm32v7
arm64v8:
machine:
enabled: true
steps:
- checkout
- run:
command: |
sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
#
sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-arm64v8 -f arm64v8.Dockerfile .
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm64v8
multiarch:
publish_docker_multiarch:
machine:
enabled: true
image: circleci/classic:201808-01
@ -94,54 +60,39 @@ jobs:
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
#
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
sudo docker manifest create --amend $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-amd64 $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 $DOCKERHUB_REPO:$LATEST_TAG-arm64v8
sudo docker manifest create --amend $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-amd64 $DOCKERHUB_REPO:$LATEST_TAG-arm32v7
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-amd64 --os linux --arch amd64
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
workflows:
version: 2
build_and_test:
jobs:
- fast_tests
- selenium_tests
- integration_tests
- external_tests:
filters:
branches:
only: master
- test
publish:
jobs:
- amd64:
- publish_docker_linuxamd64:
filters:
# ignore any commit on any branch by default
branches:
ignore: /.*/
# only act on version tags v1.0.0.88
# OR feature tags like vlndseedbackup
# only act on version tags
tags:
only: /(v[1-9]+(\.[0-9]+)*)|(v[a-z]+)/
- arm32v7:
only: /v[1-9]+(\.[0-9]+)*/
- publish_docker_linuxarm:
filters:
branches:
ignore: /.*/
tags:
only: /(v[1-9]+(\.[0-9]+)*)|(v[a-z]+)/
- arm64v8:
filters:
branches:
ignore: /.*/
tags:
only: /(v[1-9]+(\.[0-9]+)*)|(v[a-z]+)/
- multiarch:
only: /v[1-9]+(\.[0-9]+)*/
- publish_docker_multiarch:
requires:
- amd64
- arm32v7
- arm64v8
- publish_docker_linuxamd64
- publish_docker_linuxarm
filters:
branches:
ignore: /.*/
tags:
only: /(v[1-9]+(\.[0-9]+)*)|(v[a-z]+)/
only: /v[1-9]+(\.[0-9]+)*/

View File

@ -1,9 +0,0 @@
#!/bin/sh
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

2
.github/FUNDING.yml vendored
View File

@ -1,2 +0,0 @@
# These are supported funding model platforms
custom: https://foundation.btcpayserver.org

View File

@ -1,34 +0,0 @@
---
name: Report a problem
about: File a technical problem or report a bug
---
**Describe the problem/bug**
A clear and concise description of what the bug is.
**Your environment**
* Version of BTCPay Server:
* Deployment method:
* Other relevant environment details:
**Logs (if applicable)**
Basic logs can be found in Server Settings > Logs.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Actual behavior**
Tell us what happens instead
**Screenshots/Links**
If applicable, add screenshots or links to help explain your problem.
**Additional context**
Add any other context about the problem here.

View File

@ -1,20 +0,0 @@
---
name: Feature request
about: Ideas and feature requests
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Provide examples**
If applicable provide examples, wireframes, sketches or images to better explain your idea.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@ -1,44 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitBitcoin()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("BTC");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Bitcoin",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/tx/{0}" : "https://blockstream.info/testnet/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "bitcoin",
CryptoImagePath = "imlegacy/bitcoin.svg",
LightningImagePath = "imlegacy/bitcoin-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("0'") : new KeyPath("1'"),
SupportRBF = true,
//https://github.com/spesmilo/electrum/blob/11733d6bc271646a00b69ff07657119598874da4/electrum/constants.py
ElectrumMapping = NetworkType == NetworkType.Mainnet
? new Dictionary<uint, DerivationType>()
{
{0x0488b21eU, DerivationType.Legacy }, // xpub
{0x049d7cb2U, DerivationType.SegwitP2SH }, // ypub
{0x4b24746U, DerivationType.Segwit }, //zpub
}
: new Dictionary<uint, DerivationType>()
{
{0x043587cfU, DerivationType.Legacy},
{0x044a5262U, DerivationType.SegwitP2SH},
{0x045f1cf6U, DerivationType.Segwit}
}
});
}
}
}

View File

@ -1,50 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitLitecoin()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("LTC");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Litecoin",
BlockExplorerLink = NetworkType == NetworkType.Mainnet
? "https://live.blockcypher.com/ltc/tx/{0}/"
: "http://explorer.litecointools.com/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "litecoin",
DefaultRateRules = new[]
{
"LTC_X = LTC_BTC * BTC_X",
"LTC_BTC = coingecko(LTC_BTC)"
},
CryptoImagePath = "imlegacy/litecoin.svg",
LightningImagePath = "imlegacy/litecoin-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("2'") : new KeyPath("1'"),
//https://github.com/pooler/electrum-ltc/blob/0d6989a9d2fb2edbea421c116e49d1015c7c5a91/electrum_ltc/constants.py
ElectrumMapping = NetworkType == NetworkType.Mainnet
? new Dictionary<uint, DerivationType>()
{
{0x0488b21eU, DerivationType.Legacy },
{0x049d7cb2U, DerivationType.SegwitP2SH },
{0x04b24746U, DerivationType.Segwit },
}
: new Dictionary<uint, DerivationType>()
{
{0x043587cfU, DerivationType.Legacy },
{0x044a5262U, DerivationType.SegwitP2SH },
{0x045f1cf6U, DerivationType.Segwit }
}
});
}
}
}

View File

@ -1,54 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using NBitcoin.Altcoins;
using NBitcoin.Altcoins.Elements;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitLiquid()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("LBTC");
Add(new ElementsBTCPayNetwork()
{
AssetId = NetworkType == NetworkType.Mainnet ? ElementsParams<Liquid>.PeggedAssetId: ElementsParams<Liquid.LiquidRegtest>.PeggedAssetId,
CryptoCode = "LBTC",
NetworkCryptoCode = "LBTC",
DisplayName = "Liquid Bitcoin",
DefaultRateRules = new[]
{
"LBTC_X = LBTC_BTC * BTC_X",
"LBTC_BTC = 1",
},
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "liquidnetwork",
CryptoImagePath = "imlegacy/liquid.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
SupportRBF = true,
//https://github.com/spesmilo/electrum/blob/11733d6bc271646a00b69ff07657119598874da4/electrum/constants.py
ElectrumMapping = NetworkType == NetworkType.Mainnet
? new Dictionary<uint, DerivationType>()
{
{0x0488b21eU, DerivationType.Legacy }, // xpub
{0x049d7cb2U, DerivationType.SegwitP2SH }, // ypub
{0x4b24746U, DerivationType.Segwit }, //zpub
}
: new Dictionary<uint, DerivationType>()
{
{0x043587cfU, DerivationType.Legacy},
{0x044a5262U, DerivationType.SegwitP2SH},
{0x045f1cf6U, DerivationType.Segwit}
},
});
}
}
}

View File

@ -1,53 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitLiquidAssets()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("LBTC");
Add(new ElementsBTCPayNetwork()
{
CryptoCode = "USDt",
NetworkCryptoCode = "LBTC",
DefaultRateRules = new[]
{
"USDT_UST = 1",
"USDT_X = USDT_BTC * BTC_X",
"USDT_BTC = bitfinex(UST_BTC)",
},
AssetId = new uint256("ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2"),
DisplayName = "Liquid Tether",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "liquidnetwork",
CryptoImagePath = "imlegacy/liquid-tether.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
SupportRBF = true,
//https://github.com/spesmilo/electrum/blob/11733d6bc271646a00b69ff07657119598874da4/electrum/constants.py
ElectrumMapping = NetworkType == NetworkType.Mainnet
? new Dictionary<uint, DerivationType>()
{
{0x0488b21eU, DerivationType.Legacy }, // xpub
{0x049d7cb2U, DerivationType.SegwitP2SH }, // ypub
{0x4b24746U, DerivationType.Segwit }, //zpub
}
: new Dictionary<uint, DerivationType>()
{
{0x043587cfU, DerivationType.Legacy},
{0x044a5262U, DerivationType.SegwitP2SH},
{0x045f1cf6U, DerivationType.Segwit}
}
});
}
}
}

View File

@ -1,31 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using NBitcoin;
using NBXplorer;
using NBXplorer.Models;
namespace BTCPayServer
{
public class ElementsBTCPayNetwork:BTCPayNetwork
{
public string NetworkCryptoCode { get; set; }
public uint256 AssetId { get; set; }
public override bool ReadonlyWallet { get; set; } = true;
public int Divisibility { get; set; } = 8;
public override IEnumerable<(MatchedOutput matchedOutput, OutPoint outPoint)> GetValidOutputs(NewTransactionEvent evtOutputs)
{
return evtOutputs.Outputs.Where(output =>
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId).Select(output =>
{
var outpoint = new OutPoint(evtOutputs.TransactionData.TransactionHash, output.Index);
return (output, outpoint);
});
}
public override string GenerateBIP21(string cryptoInfoAddress, string cryptoInfoDue)
{
return $"{base.GenerateBIP21(cryptoInfoAddress, cryptoInfoDue)}&assetid={AssetId}";
}
}
}

View File

@ -1,26 +0,0 @@
using NBitcoin;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitMonero()
{
Add(new MoneroLikeSpecificBtcPayNetwork()
{
CryptoCode = "XMR",
DisplayName = "Monero",
BlockExplorerLink =
NetworkType == NetworkType.Mainnet
? "https://www.exploremonero.com/transaction/{0}"
: "https://testnet.xmrchain.net/tx/{0}",
DefaultRateRules = new[]
{
"XMR_X = XMR_BTC * BTC_X",
"XMR_BTC = kraken(XMR_BTC)"
},
CryptoImagePath = "/imlegacy/monero.svg"
});
}
}
}

View File

@ -1,7 +0,0 @@
namespace BTCPayServer
{
public class MoneroLikeSpecificBtcPayNetwork : BTCPayNetworkBase
{
public int MaxTrackedConfirmation = 10;
}
}

View File

@ -1,121 +0,0 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
namespace BTCPayServer.Services.Altcoins.Monero.RPC
{
public class JsonRpcClient
{
private readonly Uri _address;
private readonly string _username;
private readonly string _password;
private readonly HttpClient _httpClient;
public JsonRpcClient(Uri address, string username, string password, HttpClient client = null)
{
_address = address;
_username = username;
_password = password;
_httpClient = client ?? new HttpClient();
}
public async Task<TResponse> SendCommandAsync<TRequest, TResponse>(string method, TRequest data,
CancellationToken cts = default(CancellationToken))
{
var jsonSerializer = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
var httpRequest = new HttpRequestMessage()
{
Method = HttpMethod.Post,
RequestUri = new Uri(_address, "json_rpc"),
Content = new StringContent(
JsonConvert.SerializeObject(new JsonRpcCommand<TRequest>(method, data), jsonSerializer),
Encoding.UTF8, "application/json")
};
httpRequest.Headers.Accept.Clear();
httpRequest.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(Encoding.Default.GetBytes($"{_username}:{_password}")));
var rawResult = await _httpClient.SendAsync(httpRequest, cts);
var rawJson = await rawResult.Content.ReadAsStringAsync();
rawResult.EnsureSuccessStatusCode();
JsonRpcResult<TResponse> response;
try
{
response = JsonConvert.DeserializeObject<JsonRpcResult<TResponse>>(rawJson, jsonSerializer);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine(rawJson);
throw;
}
if (response.Error != null)
{
throw new JsonRpcApiException()
{
Error = response.Error
};
}
return response.Result;
}
public class NoRequestModel
{
public static NoRequestModel Instance = new NoRequestModel();
}
internal class JsonRpcApiException : Exception
{
public JsonRpcResultError Error { get; set; }
public override string Message => Error?.Message;
}
public class JsonRpcResultError
{
[JsonProperty("code")] public int Code { get; set; }
[JsonProperty("message")] public string Message { get; set; }
[JsonProperty("data")] dynamic Data { get; set; }
}
internal class JsonRpcResult<T>
{
[JsonProperty("result")] public T Result { get; set; }
[JsonProperty("error")] public JsonRpcResultError Error { get; set; }
[JsonProperty("id")] public string Id { get; set; }
}
internal class JsonRpcCommand<T>
{
[JsonProperty("jsonRpc")] public string JsonRpc { get; set; } = "2.0";
[JsonProperty("id")] public string Id { get; set; } = Guid.NewGuid().ToString();
[JsonProperty("method")] public string Method { get; set; }
[JsonProperty("params")] public T Parameters { get; set; }
public JsonRpcCommand()
{
}
public JsonRpcCommand(string method, T parameters)
{
Method = method;
Parameters = parameters;
}
}
}
}

View File

@ -1,9 +0,0 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class CreateAccountRequest
{
[JsonProperty("label")] public string Label { get; set; }
}
}

View File

@ -1,10 +0,0 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class CreateAccountResponse
{
[JsonProperty("account_index")] public long AccountIndex { get; set; }
[JsonProperty("address")] public string Address { get; set; }
}
}

View File

@ -1,10 +0,0 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class CreateAddressRequest
{
[JsonProperty("account_index")] public long AccountIndex { get; set; }
[JsonProperty("label")] public string Label { get; set; }
}
}

View File

@ -1,10 +0,0 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class CreateAddressResponse
{
[JsonProperty("address")] public string Address { get; set; }
[JsonProperty("address_index")] public long AddressIndex { get; set; }
}
}

View File

@ -1,9 +0,0 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class GetAccountsRequest
{
[JsonProperty("tag")] public string Tag { get; set; }
}
}

View File

@ -1,14 +0,0 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class GetAccountsResponse
{
[JsonProperty("subaddress_accounts")] public List<SubaddressAccount> SubaddressAccounts { get; set; }
[JsonProperty("total_balance")] public decimal TotalBalance { get; set; }
[JsonProperty("total_unlocked_balance")]
public decimal TotalUnlockedBalance { get; set; }
}
}

View File

@ -1,9 +0,0 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public class GetFeeEstimateRequest
{
[JsonProperty("grace_blocks")] public int? GraceBlocks { get; set; }
}
}

View File

@ -1,11 +0,0 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public class GetFeeEstimateResponse
{
[JsonProperty("fee")] public long Fee { get; set; }
[JsonProperty("status")] public string Status { get; set; }
[JsonProperty("untrusted")] public bool Untrusted { get; set; }
}
}

View File

@ -1,9 +0,0 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class GetHeightResponse
{
[JsonProperty("height")] public long Height { get; set; }
}
}

View File

@ -1,11 +0,0 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public class GetTransferByTransactionIdRequest
{
[JsonProperty("txid")] public string TransactionId { get; set; }
[JsonProperty("account_index")] public long AccountIndex { get; set; }
}
}

View File

@ -1,32 +0,0 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class GetTransferByTransactionIdResponse
{
[JsonProperty("transfer")] public TransferItem Transfer { get; set; }
[JsonProperty("transfers")] public IEnumerable<TransferItem> Transfers { get; set; }
public partial class TransferItem
{
[JsonProperty("address")] public string Address { get; set; }
[JsonProperty("amount")] public long Amount { get; set; }
[JsonProperty("confirmations")] public long Confirmations { get; set; }
[JsonProperty("double_spend_seen")] public bool DoubleSpendSeen { get; set; }
[JsonProperty("fee")] public long Fee { get; set; }
[JsonProperty("height")] public long Height { get; set; }
[JsonProperty("note")] public string Note { get; set; }
[JsonProperty("payment_id")] public string PaymentId { get; set; }
[JsonProperty("subaddr_index")] public SubaddrIndex SubaddrIndex { get; set; }
[JsonProperty("suggested_confirmations_threshold")]
public long SuggestedConfirmationsThreshold { get; set; }
[JsonProperty("timestamp")] public long Timestamp { get; set; }
[JsonProperty("txid")] public string Txid { get; set; }
[JsonProperty("type")] public string Type { get; set; }
[JsonProperty("unlock_time")] public long UnlockTime { get; set; }
}
}
}

View File

@ -1,19 +0,0 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class GetTransfersRequest
{
[JsonProperty("in")] public bool In { get; set; }
[JsonProperty("out")] public bool Out { get; set; }
[JsonProperty("pending")] public bool Pending { get; set; }
[JsonProperty("failed")] public bool Failed { get; set; }
[JsonProperty("pool")] public bool Pool { get; set; }
[JsonProperty("filter_by_height ")] public bool FilterByHeight { get; set; }
[JsonProperty("min_height")] public long MinHeight { get; set; }
[JsonProperty("max_height")] public long MaxHeight { get; set; }
[JsonProperty("account_index")] public long AccountIndex { get; set; }
[JsonProperty("subaddr_indices")] public List<long> SubaddrIndices { get; set; }
}
}

View File

@ -1,36 +0,0 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class GetTransfersResponse
{
[JsonProperty("in")] public List<GetTransfersResponseItem> In { get; set; }
[JsonProperty("out")] public List<GetTransfersResponseItem> Out { get; set; }
[JsonProperty("pending")] public List<GetTransfersResponseItem> Pending { get; set; }
[JsonProperty("failed")] public List<GetTransfersResponseItem> Failed { get; set; }
[JsonProperty("pool")] public List<GetTransfersResponseItem> Pool { get; set; }
public partial class GetTransfersResponseItem
{
[JsonProperty("address")] public string Address { get; set; }
[JsonProperty("amount")] public long Amount { get; set; }
[JsonProperty("confirmations")] public long Confirmations { get; set; }
[JsonProperty("double_spend_seen")] public bool DoubleSpendSeen { get; set; }
[JsonProperty("fee")] public long Fee { get; set; }
[JsonProperty("height")] public long Height { get; set; }
[JsonProperty("note")] public string Note { get; set; }
[JsonProperty("payment_id")] public string PaymentId { get; set; }
[JsonProperty("subaddr_index")] public SubaddrIndex SubaddrIndex { get; set; }
[JsonProperty("suggested_confirmations_threshold")]
public long SuggestedConfirmationsThreshold { get; set; }
[JsonProperty("timestamp")] public long Timestamp { get; set; }
[JsonProperty("txid")] public string Txid { get; set; }
[JsonProperty("type")] public string Type { get; set; }
[JsonProperty("unlock_time")] public long UnlockTime { get; set; }
}
}
}

View File

@ -1,33 +0,0 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class Info
{
[JsonProperty("address")] public string Address { get; set; }
[JsonProperty("avg_download")] public long AvgDownload { get; set; }
[JsonProperty("avg_upload")] public long AvgUpload { get; set; }
[JsonProperty("connection_id")] public string ConnectionId { get; set; }
[JsonProperty("current_download")] public long CurrentDownload { get; set; }
[JsonProperty("current_upload")] public long CurrentUpload { get; set; }
[JsonProperty("height")] public long Height { get; set; }
[JsonProperty("host")] public string Host { get; set; }
[JsonProperty("incoming")] public bool Incoming { get; set; }
[JsonProperty("ip")] public string Ip { get; set; }
[JsonProperty("live_time")] public long LiveTime { get; set; }
[JsonProperty("local_ip")] public bool LocalIp { get; set; }
[JsonProperty("localhost")] public bool Localhost { get; set; }
[JsonProperty("peer_id")] public string PeerId { get; set; }
[JsonProperty("port")]
[JsonConverter(typeof(ParseStringConverter))]
public long Port { get; set; }
[JsonProperty("recv_count")] public long RecvCount { get; set; }
[JsonProperty("recv_idle_time")] public long RecvIdleTime { get; set; }
[JsonProperty("send_count")] public long SendCount { get; set; }
[JsonProperty("send_idle_time")] public long SendIdleTime { get; set; }
[JsonProperty("state")] public string State { get; set; }
[JsonProperty("support_flags")] public long SupportFlags { get; set; }
}
}

View File

@ -1,13 +0,0 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class MakeUriRequest
{
[JsonProperty("address")] public string Address { get; set; }
[JsonProperty("amount")] public long Amount { get; set; }
[JsonProperty("payment_id")] public string PaymentId { get; set; }
[JsonProperty("tx_description")] public string TxDescription { get; set; }
[JsonProperty("recipient_name")] public string RecipientName { get; set; }
}
}

View File

@ -1,9 +0,0 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class MakeUriResponse
{
[JsonProperty("uri")] public string Uri { get; set; }
}
}

View File

@ -1,39 +0,0 @@
using System;
using System.Globalization;
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
internal class ParseStringConverter : JsonConverter
{
public override bool CanConvert(Type t) => t == typeof(long) || t == typeof(long?);
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) return null;
var value = serializer.Deserialize<string>(reader);
long l;
if (Int64.TryParse(value, out l))
{
return l;
}
throw new Exception("Cannot unmarshal type long");
}
public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer)
{
if (untypedValue == null)
{
serializer.Serialize(writer, null);
return;
}
var value = (long)untypedValue;
serializer.Serialize(writer, value.ToString(CultureInfo.InvariantCulture));
return;
}
public static readonly ParseStringConverter Singleton = new ParseStringConverter();
}
}

View File

@ -1,9 +0,0 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class Peer
{
[JsonProperty("info")] public Info Info { get; set; }
}
}

View File

@ -1,10 +0,0 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class SubaddrIndex
{
[JsonProperty("major")] public long Major { get; set; }
[JsonProperty("minor")] public long Minor { get; set; }
}
}

View File

@ -1,14 +0,0 @@
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class SubaddressAccount
{
[JsonProperty("account_index")] public long AccountIndex { get; set; }
[JsonProperty("balance")] public decimal Balance { get; set; }
[JsonProperty("base_address")] public string BaseAddress { get; set; }
[JsonProperty("label")] public string Label { get; set; }
[JsonProperty("tag")] public string Tag { get; set; }
[JsonProperty("unlocked_balance")] public decimal UnlockedBalance { get; set; }
}
}

View File

@ -1,13 +0,0 @@
using System.Collections.Generic;
using Newtonsoft.Json;
namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
{
public partial class SyncInfoResponse
{
[JsonProperty("height")] public long Height { get; set; }
[JsonProperty("peers")] public List<Peer> Peers { get; set; }
[JsonProperty("status")] public string Status { get; set; }
[JsonProperty("target_height")] public long? TargetHeight { get; set; }
}
}

View File

@ -1,20 +0,0 @@
using System.Globalization;
namespace BTCPayServer.Services.Altcoins.Monero.Utils
{
public class MoneroMoney
{
public static decimal Convert(long piconero)
{
var amt = piconero.ToString(CultureInfo.InvariantCulture).PadLeft(12, '0');
amt = amt.Length == 12 ? $"0.{amt}" : amt.Insert(amt.Length - 12, ".");
return decimal.Parse(amt, CultureInfo.InvariantCulture);
}
public static long Convert(decimal monero)
{
return System.Convert.ToInt64(monero * 1000000000000);
}
}
}

View File

@ -1,154 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using NBitcoin;
using NBXplorer;
using NBXplorer.Models;
using Newtonsoft.Json;
namespace BTCPayServer
{
public enum DerivationType
{
Legacy,
SegwitP2SH,
Segwit
}
public class BTCPayDefaultSettings
{
static BTCPayDefaultSettings()
{
_Settings = new Dictionary<NetworkType, BTCPayDefaultSettings>();
foreach (var chainType in new[] { NetworkType.Mainnet, NetworkType.Testnet, NetworkType.Regtest })
{
var settings = new BTCPayDefaultSettings();
_Settings.Add(chainType, settings);
settings.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", NBXplorerDefaultSettings.GetFolderName(chainType));
settings.DefaultConfigurationFile = Path.Combine(settings.DefaultDataDirectory, "settings.config");
settings.DefaultPort = (chainType == NetworkType.Mainnet ? 23000 :
chainType == NetworkType.Regtest ? 23002 :
chainType == NetworkType.Testnet ? 23001 : throw new NotSupportedException(chainType.ToString()));
}
}
static Dictionary<NetworkType, BTCPayDefaultSettings> _Settings;
public static BTCPayDefaultSettings GetDefaultSettings(NetworkType chainType)
{
return _Settings[chainType];
}
public string DefaultDataDirectory { get; set; }
public string DefaultConfigurationFile { get; set; }
public int DefaultPort { get; set; }
}
public class BTCPayNetwork:BTCPayNetworkBase
{
public Network NBitcoinNetwork { get { return NBXplorerNetwork?.NBitcoinNetwork; } }
public NBXplorer.NBXplorerNetwork NBXplorerNetwork { get; set; }
public bool SupportRBF { get; internal set; }
public string LightningImagePath { get; set; }
public BTCPayDefaultSettings DefaultSettings { get; set; }
public KeyPath CoinType { get; internal set; }
public Dictionary<uint, DerivationType> ElectrumMapping = new Dictionary<uint, DerivationType>();
public virtual bool WalletSupported { get; set; } = true;
public virtual bool ReadonlyWallet{ get; set; } = false;
public int MaxTrackedConfirmation { get; internal set; } = 6;
public string UriScheme { get; internal set; }
public KeyPath GetRootKeyPath(DerivationType type)
{
KeyPath baseKey;
if (!NBitcoinNetwork.Consensus.SupportSegwit)
{
baseKey = new KeyPath("44'");
}
else
{
switch (type)
{
case DerivationType.Legacy:
baseKey = new KeyPath("44'");
break;
case DerivationType.SegwitP2SH:
baseKey = new KeyPath("49'");
break;
case DerivationType.Segwit:
baseKey = new KeyPath("84'");
break;
default:
throw new ArgumentOutOfRangeException(nameof(type), type, null);
}
}
return baseKey
.Derive(CoinType);
}
public KeyPath GetRootKeyPath()
{
return new KeyPath(NBitcoinNetwork.Consensus.SupportSegwit ? "49'" : "44'")
.Derive(CoinType);
}
public override T ToObject<T>(string json)
{
return NBXplorerNetwork.Serializer.ToObject<T>(json);
}
public override string ToString<T>(T obj)
{
return NBXplorerNetwork.Serializer.ToString(obj);
}
public virtual IEnumerable<(MatchedOutput matchedOutput, OutPoint outPoint)> GetValidOutputs(NewTransactionEvent evtOutputs)
{
return evtOutputs.Outputs.Select(output =>
{
var outpoint = new OutPoint(evtOutputs.TransactionData.TransactionHash, output.Index);
return (output, outpoint);
});
}
public virtual string GenerateBIP21(string cryptoInfoAddress, string cryptoInfoDue)
{
return $"{UriScheme}:{cryptoInfoAddress}?amount={cryptoInfoDue}";
}
}
public abstract class BTCPayNetworkBase
{
public string CryptoCode { get; internal set; }
public string BlockExplorerLink { get; internal set; }
public string DisplayName { get; set; }
[Obsolete("Should not be needed")]
public bool IsBTC
{
get
{
return CryptoCode == "BTC";
}
}
public string CryptoImagePath { get; set; }
public string[] DefaultRateRules { get; internal set; } = Array.Empty<string>();
public override string ToString()
{
return CryptoCode;
}
public virtual T ToObject<T>(string json)
{
return NBitcoin.JsonConverters.Serializer.ToObject<T>(json, null);
}
public virtual string ToString<T>(T obj)
{
return NBitcoin.JsonConverters.Serializer.ToString(obj, null);
}
}
}

View File

@ -1,9 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" />
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="NBXplorer.Client" Version="3.0.2" />
</ItemGroup>
</Project>

View File

@ -1,17 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BTCPayServer
{
public static class UtilitiesExtensions
{
public static void AddRange<T>(this HashSet<T> hashSet, IEnumerable<T> items)
{
foreach (var item in items)
{
hashSet.Add(item);
}
}
}
}

View File

@ -1,236 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Microsoft.Extensions.Logging.Console.Internal
{
/// <summary>
/// For non-Windows platform consoles which understand the ANSI escape code sequences to represent color
/// </summary>
internal class AnsiLogConsole : IConsole
{
private readonly StringBuilder _outputBuilder;
private readonly IAnsiSystemConsole _systemConsole;
public AnsiLogConsole(IAnsiSystemConsole systemConsole)
{
_outputBuilder = new StringBuilder();
_systemConsole = systemConsole;
}
public void Write(string message, ConsoleColor? background, ConsoleColor? foreground)
{
// Order: backgroundcolor, foregroundcolor, Message, reset foregroundcolor, reset backgroundcolor
if (background.HasValue)
{
_outputBuilder.Append(GetBackgroundColorEscapeCode(background.Value));
}
if (foreground.HasValue)
{
_outputBuilder.Append(GetForegroundColorEscapeCode(foreground.Value));
}
_outputBuilder.Append(message);
if (foreground.HasValue)
{
_outputBuilder.Append("\x1B[39m\x1B[22m"); // reset to default foreground color
}
if (background.HasValue)
{
_outputBuilder.Append("\x1B[49m"); // reset to the background color
}
}
public void WriteLine(string message, ConsoleColor? background, ConsoleColor? foreground)
{
Write(message, background, foreground);
_outputBuilder.AppendLine();
}
public void Flush()
{
_systemConsole.Write(_outputBuilder.ToString());
_outputBuilder.Clear();
}
private static string GetForegroundColorEscapeCode(ConsoleColor color)
{
switch (color)
{
case ConsoleColor.Black:
return "\x1B[30m";
case ConsoleColor.DarkRed:
return "\x1B[31m";
case ConsoleColor.DarkGreen:
return "\x1B[32m";
case ConsoleColor.DarkYellow:
return "\x1B[33m";
case ConsoleColor.DarkBlue:
return "\x1B[34m";
case ConsoleColor.DarkMagenta:
return "\x1B[35m";
case ConsoleColor.DarkCyan:
return "\x1B[36m";
case ConsoleColor.Gray:
return "\x1B[37m";
case ConsoleColor.Red:
return "\x1B[1m\x1B[31m";
case ConsoleColor.Green:
return "\x1B[1m\x1B[32m";
case ConsoleColor.Yellow:
return "\x1B[1m\x1B[33m";
case ConsoleColor.Blue:
return "\x1B[1m\x1B[34m";
case ConsoleColor.Magenta:
return "\x1B[1m\x1B[35m";
case ConsoleColor.Cyan:
return "\x1B[1m\x1B[36m";
case ConsoleColor.White:
return "\x1B[1m\x1B[37m";
default:
return "\x1B[39m\x1B[22m"; // default foreground color
}
}
private static string GetBackgroundColorEscapeCode(ConsoleColor color)
{
switch (color)
{
case ConsoleColor.Black:
return "\x1B[40m";
case ConsoleColor.Red:
return "\x1B[41m";
case ConsoleColor.Green:
return "\x1B[42m";
case ConsoleColor.Yellow:
return "\x1B[43m";
case ConsoleColor.Blue:
return "\x1B[44m";
case ConsoleColor.Magenta:
return "\x1B[45m";
case ConsoleColor.Cyan:
return "\x1B[46m";
case ConsoleColor.White:
return "\x1B[47m";
default:
return "\x1B[49m"; // Use default background color
}
}
}
internal class AnsiSystemConsole : IAnsiSystemConsole
{
private readonly TextWriter _textWriter;
/// <inheritdoc />
public AnsiSystemConsole(bool stdErr = false)
{
_textWriter = stdErr ? System.Console.Error : System.Console.Out;
}
public void Write(string message)
{
_textWriter.Write(message);
}
}
}
namespace Microsoft.Extensions.Logging.Console
{
internal interface IAnsiSystemConsole
{
void Write(string message);
}
public interface IConsole
{
void Write(string message, ConsoleColor? background, ConsoleColor? foreground);
void WriteLine(string message, ConsoleColor? background, ConsoleColor? foreground);
void Flush();
}
internal class WindowsLogConsole : IConsole
{
private readonly TextWriter _textWriter;
/// <inheritdoc />
public WindowsLogConsole(bool stdErr = false)
{
_textWriter = stdErr ? System.Console.Error : System.Console.Out;
}
private bool SetColor(ConsoleColor? background, ConsoleColor? foreground)
{
if (background.HasValue)
{
System.Console.BackgroundColor = background.Value;
}
if (foreground.HasValue)
{
System.Console.ForegroundColor = foreground.Value;
}
return background.HasValue || foreground.HasValue;
}
private void ResetColor()
{
System.Console.ResetColor();
}
public void Write(string message, ConsoleColor? background, ConsoleColor? foreground)
{
var colorChanged = SetColor(background, foreground);
_textWriter.Write(message);
if (colorChanged)
{
ResetColor();
}
}
public void WriteLine(string message, ConsoleColor? background, ConsoleColor? foreground)
{
var colorChanged = SetColor(background, foreground);
_textWriter.WriteLine(message);
if (colorChanged)
{
ResetColor();
}
}
public void Flush()
{
// No action required as for every write, data is sent directly to the console
// output stream
}
}
}
namespace Microsoft.Extensions.Logging.Abstractions.Internal
{
/// <summary>
/// An empty scope without any logic
/// </summary>
internal class NullScope : IDisposable
{
public static NullScope Instance { get; } = new NullScope();
private NullScope()
{
}
/// <inheritdoc />
public void Dispose()
{
}
}
}

View File

@ -1,12 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" />
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.0" />
<PackageReference Include="OpenIddict.EntityFrameworkCore" Version="3.0.0-alpha1.20058.15" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.0" />
</ItemGroup>
</Project>

View File

@ -1,36 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Data
{
public class AddressInvoiceData
{
/// <summary>
/// Some crypto currencies share same address prefix
/// For not having exceptions thrown by two address on different network, we suffix by "#CRYPTOCODE"
/// </summary>
[Obsolete("Use GetHash instead")]
public string Address
{
get; set;
}
public InvoiceData InvoiceData
{
get; set;
}
public string InvoiceDataId
{
get; set;
}
public DateTimeOffset? CreatedTime
{
get; set;
}
}
}

View File

@ -1,6 +0,0 @@
using OpenIddict.EntityFrameworkCore.Models;
namespace BTCPayServer.Data
{
public class BTCPayOpenIdAuthorization : OpenIddictAuthorization<string, BTCPayOpenIdClient, BTCPayOpenIdToken> { }
}

View File

@ -1,10 +0,0 @@
using OpenIddict.EntityFrameworkCore.Models;
namespace BTCPayServer.Data
{
public class BTCPayOpenIdClient: OpenIddictApplication<string, BTCPayOpenIdAuthorization, BTCPayOpenIdToken>
{
public string ApplicationUserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
}
}

View File

@ -1,6 +0,0 @@
using OpenIddict.EntityFrameworkCore.Models;
namespace BTCPayServer.Data
{
public class BTCPayOpenIdToken : OpenIddictToken<string, BTCPayOpenIdClient, BTCPayOpenIdAuthorization> { }
}

View File

@ -1,45 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BTCPayServer.Data
{
public class PaymentRequestData
{
public string Id { get; set; }
public DateTimeOffset Created
{
get; set;
}
public string StoreDataId { get; set; }
public StoreData StoreData { get; set; }
public PaymentRequestStatus Status { get; set; }
public byte[] Blob { get; set; }
public class PaymentRequestBlob
{
public decimal Amount { get; set; }
public string Currency { get; set; }
public DateTime? ExpiryDate { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Email { get; set; }
public string EmbeddedCSS { get; set; }
public string CustomCSSLink { get; set; }
public bool AllowCustomPaymentAmounts { get; set; }
}
public enum PaymentRequestStatus
{
Pending = 0,
Completed = 1,
Expired = 2
}
}
}

View File

@ -1,98 +0,0 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using Newtonsoft.Json.Linq;
using System.Security.Claims;
namespace BTCPayServer.Data
{
public enum SpeedPolicy
{
HighSpeed = 0,
MediumSpeed = 1,
LowSpeed = 2,
LowMediumSpeed = 3
}
public class StoreData
{
public string Id
{
get;
set;
}
public List<UserStore> UserStores
{
get; set;
}
public List<AppData> Apps
{
get; set;
}
public List<PaymentRequestData> PaymentRequests
{
get; set;
}
public List<InvoiceData> Invoices { get; set; }
[Obsolete("Use GetDerivationStrategies instead")]
public string DerivationStrategy
{
get; set;
}
[Obsolete("Use GetDerivationStrategies instead")]
public string DerivationStrategies
{
get;
set;
}
public string StoreName
{
get; set;
}
public SpeedPolicy SpeedPolicy
{
get; set;
}
public string StoreWebsite
{
get; set;
}
public byte[] StoreCertificate
{
get; set;
}
[NotMapped]
public string Role
{
get; set;
}
public byte[] StoreBlob
{
get;
set;
}
[Obsolete("Use GetDefaultPaymentId instead")]
public string DefaultCrypto { get; set; }
public List<PairedSINData> PairedSINs { get; set; }
public IEnumerable<APIKeyData> APIKeys { get; set; }
}
public enum NetworkFeeMode
{
MultiplePaymentsOnly,
Always,
Never
}
}

View File

@ -1,20 +0,0 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;
namespace BTCPayServer.Data
{
public class StoredFile
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string Id { get; set; }
public string FileName { get; set; }
public string StorageFileName { get; set; }
public DateTime Timestamp { get; set; }
public string ApplicationUserId { get; set; }
public ApplicationUser ApplicationUser
{
get; set;
}
}
}

View File

@ -1,23 +0,0 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace BTCPayServer.Data
{
public class U2FDevice
{
public string Id { get; set; }
public string Name { get; set; }
[Required] public byte[] KeyHandle { get; set; }
[Required] public byte[] PublicKey { get; set; }
[Required] public byte[] AttestationCert { get; set; }
[Required] public int Counter { get; set; }
public string ApplicationUserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
}
}

View File

@ -1,84 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace BTCPayServer.Data
{
public class WalletData
{
[System.ComponentModel.DataAnnotations.Key]
public string Id { get; set; }
public List<WalletTransactionData> WalletTransactions { get; set; }
public byte[] Blob { get; set; }
}
public class Label
{
public Label(string value, string color)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
if (color == null)
throw new ArgumentNullException(nameof(color));
Value = value;
Color = color;
}
public string Value { get; }
public string Color { get; }
public override bool Equals(object obj)
{
Label item = obj as Label;
if (item == null)
return false;
return Value.Equals(item.Value, StringComparison.OrdinalIgnoreCase);
}
public static bool operator ==(Label a, Label b)
{
if (System.Object.ReferenceEquals(a, b))
return true;
if (((object)a == null) || ((object)b == null))
return false;
return a.Value == b.Value;
}
public static bool operator !=(Label a, Label b)
{
return !(a == b);
}
public override int GetHashCode()
{
return Value.GetHashCode(StringComparison.OrdinalIgnoreCase);
}
}
public class WalletBlobInfo
{
public Dictionary<string, string> LabelColors { get; set; } = new Dictionary<string, string>();
public IEnumerable<Label> GetLabels(WalletTransactionInfo transactionInfo)
{
foreach (var label in transactionInfo.Labels)
{
if (LabelColors.TryGetValue(label, out var color))
{
yield return new Label(label, color);
}
}
}
public IEnumerable<Label> GetLabels()
{
foreach (var kv in LabelColors)
{
yield return new Label(kv.Key, kv.Value);
}
}
}
}

View File

@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace BTCPayServer.Data
{
public class WalletTransactionData
{
public string WalletDataId { get; set; }
public WalletData WalletData { get; set; }
public string TransactionId { get; set; }
public string Labels { get; set; }
public byte[] Blob { get; set; }
}
public class WalletTransactionInfo
{
public string Comment { get; set; } = string.Empty;
[JsonIgnore]
public HashSet<string> Labels { get; set; } = new HashSet<string>();
}
}

View File

@ -1,172 +0,0 @@
using System;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20190225091644_AddOpenIddict")]
public partial class AddOpenIddict : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
migrationBuilder.CreateTable(
name: "OpenIddictApplications",
columns: table => new
{
ClientId = table.Column<string>(maxLength: 100, nullable: false),
ClientSecret = table.Column<string>(nullable: true),
ConcurrencyToken = table.Column<string>(maxLength: 50, nullable: true),
ConsentType = table.Column<string>(nullable: true),
DisplayName = table.Column<string>(nullable: true),
Id = table.Column<string>(nullable: false, maxLength: maxLength),
Permissions = table.Column<string>(nullable: true),
PostLogoutRedirectUris = table.Column<string>(nullable: true),
Properties = table.Column<string>(nullable: true),
RedirectUris = table.Column<string>(nullable: true),
Type = table.Column<string>(maxLength: 25, nullable: false),
ApplicationUserId = table.Column<string>(nullable: true, maxLength: maxLength)
},
constraints: table =>
{
table.PrimaryKey("PK_OpenIddictApplications", x => x.Id);
table.ForeignKey(
name: "FK_OpenIddictApplications_AspNetUsers_ApplicationUserId",
column: x => x.ApplicationUserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "OpenIddictScopes",
columns: table => new
{
ConcurrencyToken = table.Column<string>(maxLength: 50, nullable: true),
Description = table.Column<string>(nullable: true),
DisplayName = table.Column<string>(nullable: true),
Id = table.Column<string>(nullable: false, maxLength: maxLength),
Name = table.Column<string>(maxLength: 200, nullable: false),
Properties = table.Column<string>(nullable: true),
Resources = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_OpenIddictScopes", x => x.Id);
});
migrationBuilder.CreateTable(
name: "OpenIddictAuthorizations",
columns: table => new
{
ApplicationId = table.Column<string>(nullable: true, maxLength: maxLength),
ConcurrencyToken = table.Column<string>(maxLength: 50, nullable: true),
Id = table.Column<string>(nullable: false, maxLength: maxLength),
Properties = table.Column<string>(nullable: true),
Scopes = table.Column<string>(nullable: true),
Status = table.Column<string>(maxLength: 25, nullable: false),
Subject = table.Column<string>(maxLength: 450, nullable: false),
Type = table.Column<string>(maxLength: 25, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_OpenIddictAuthorizations", x => x.Id);
table.ForeignKey(
name: "FK_OpenIddictAuthorizations_OpenIddictApplications_ApplicationId",
column: x => x.ApplicationId,
principalTable: "OpenIddictApplications",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "OpenIddictTokens",
columns: table => new
{
ApplicationId = table.Column<string>(nullable: true, maxLength: maxLength),
AuthorizationId = table.Column<string>(nullable: true, maxLength: maxLength),
ConcurrencyToken = table.Column<string>(maxLength: 50, nullable: true),
CreationDate = table.Column<DateTimeOffset>(nullable: true),
ExpirationDate = table.Column<DateTimeOffset>(nullable: true),
Id = table.Column<string>(nullable: false, maxLength: maxLength),
Payload = table.Column<string>(nullable: true),
Properties = table.Column<string>(nullable: true),
ReferenceId = table.Column<string>(maxLength: 100, nullable: true),
Status = table.Column<string>(maxLength: 25, nullable: false),
Subject = table.Column<string>(maxLength: 450, nullable: false),
Type = table.Column<string>(maxLength: 25, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_OpenIddictTokens", x => x.Id);
table.ForeignKey(
name: "FK_OpenIddictTokens_OpenIddictApplications_ApplicationId",
column: x => x.ApplicationId,
principalTable: "OpenIddictApplications",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_OpenIddictTokens_OpenIddictAuthorizations_AuthorizationId",
column: x => x.AuthorizationId,
principalTable: "OpenIddictAuthorizations",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_OpenIddictApplications_ApplicationUserId",
table: "OpenIddictApplications",
column: "ApplicationUserId");
migrationBuilder.CreateIndex(
name: "IX_OpenIddictApplications_ClientId",
table: "OpenIddictApplications",
column: "ClientId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_OpenIddictAuthorizations_ApplicationId_Status_Subject_Type",
table: "OpenIddictAuthorizations",
columns: new[] { "ApplicationId", "Status", "Subject", "Type" });
migrationBuilder.CreateIndex(
name: "IX_OpenIddictScopes_Name",
table: "OpenIddictScopes",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_OpenIddictTokens_AuthorizationId",
table: "OpenIddictTokens",
column: "AuthorizationId");
migrationBuilder.CreateIndex(
name: "IX_OpenIddictTokens_ReferenceId",
table: "OpenIddictTokens",
column: "ReferenceId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_OpenIddictTokens_ApplicationId_Status_Subject_Type",
table: "OpenIddictTokens",
columns: new[] { "ApplicationId", "Status", "Subject", "Type" });
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "OpenIddictScopes");
migrationBuilder.DropTable(
name: "OpenIddictTokens");
migrationBuilder.DropTable(
name: "OpenIddictAuthorizations");
migrationBuilder.DropTable(
name: "OpenIddictApplications");
}
}
}

View File

@ -1,48 +0,0 @@
using System;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20190324141717_AddFiles")]
public partial class AddFiles : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
migrationBuilder.CreateTable(
name: "Files",
columns: table => new
{
Id = table.Column<string>(nullable: false, maxLength: maxLength),
FileName = table.Column<string>(nullable: true),
StorageFileName = table.Column<string>(nullable: true),
Timestamp = table.Column<DateTime>(nullable: false),
ApplicationUserId = table.Column<string>(nullable: true, maxLength: maxLength)
},
constraints: table =>
{
table.PrimaryKey("PK_Files", x => x.Id);
table.ForeignKey(
name: "FK_Files_AspNetUsers_ApplicationUserId",
column: x => x.ApplicationUserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_Files_ApplicationUserId",
table: "Files",
column: "ApplicationUserId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Files");
}
}
}

View File

@ -1,65 +0,0 @@
using System;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20190425081749_AddU2fDevices")]
public partial class AddU2fDevices : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
if (this.SupportDropColumn(migrationBuilder.ActiveProvider))
{
migrationBuilder.DropColumn(
name: "Facade",
table: "PairedSINData");
}
migrationBuilder.CreateTable(
name: "U2FDevices",
columns: table => new
{
Id = table.Column<string>(nullable: false, maxLength: maxLength),
Name = table.Column<string>(nullable: true),
KeyHandle = table.Column<byte[]>(nullable: false),
PublicKey = table.Column<byte[]>(nullable: false),
AttestationCert = table.Column<byte[]>(nullable: false),
Counter = table.Column<int>(nullable: false),
ApplicationUserId = table.Column<string>(nullable: true, maxLength: maxLength)
},
constraints: table =>
{
table.PrimaryKey("PK_U2FDevices", x => x.Id);
table.ForeignKey(
name: "FK_U2FDevices_AspNetUsers_ApplicationUserId",
column: x => x.ApplicationUserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_U2FDevices_ApplicationUserId",
table: "U2FDevices",
column: "ApplicationUserId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "U2FDevices");
//if it did not support dropping it, then it is still here and re-adding it would throw
if (this.SupportDropColumn(migrationBuilder.ActiveProvider))
{
migrationBuilder.AddColumn<string>(
name: "Facade",
table: "PairedSINData",
nullable: true);
}
}
}
}

View File

@ -1,29 +0,0 @@
using System;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20190701082105_sort_paymentrequests")]
public partial class sort_paymentrequests : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
migrationBuilder.AddColumn<DateTimeOffset>(
name: "Created",
table: "PaymentRequests",
nullable: false,
defaultValue: new DateTimeOffset(new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0)));
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Created",
table: "PaymentRequests");
}
}
}

View File

@ -1,57 +0,0 @@
using System;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20190802142637_WalletData")]
public partial class WalletData : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
migrationBuilder.CreateTable(
name: "Wallets",
columns: table => new
{
Id = table.Column<string>(nullable: false, maxLength: maxLength),
Blob = table.Column<byte[]>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Wallets", x => x.Id);
});
migrationBuilder.CreateTable(
name: "WalletTransactions",
columns: table => new
{
WalletDataId = table.Column<string>(nullable: false, maxLength: maxLength),
TransactionId = table.Column<string>(nullable: false, maxLength: maxLength),
Labels = table.Column<string>(nullable: true),
Blob = table.Column<byte[]>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_WalletTransactions", x => new { x.WalletDataId, x.TransactionId });
table.ForeignKey(
name: "FK_WalletTransactions_Wallets_WalletDataId",
column: x => x.WalletDataId,
principalTable: "Wallets",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "WalletTransactions");
migrationBuilder.DropTable(
name: "Wallets");
}
}
}

View File

@ -1,244 +0,0 @@
using System;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20200110064617_OpenIddictUpdate")]
public partial class OpenIddictUpdate : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
if (!migrationBuilder.IsSqlite())
{
migrationBuilder.AlterColumn<string>(
name: "Subject",
table: "OpenIddictTokens",
maxLength: 450,
nullable: true,
oldClrType: typeof(string),
oldMaxLength: 450);
migrationBuilder.AlterColumn<string>(
name: "Subject",
table: "OpenIddictAuthorizations",
maxLength: 450,
nullable: true,
oldClrType: typeof(string),
oldMaxLength: 450);
}
else
{
ReplaceOldTable(migrationBuilder, s =>
{
migrationBuilder.CreateTable(
name: s,
columns: table => new
{
ApplicationId = table.Column<string>(nullable: true, maxLength: null),
AuthorizationId = table.Column<string>(nullable: true, maxLength: null),
ConcurrencyToken = table.Column<string>(maxLength: 50, nullable: true),
CreationDate = table.Column<DateTimeOffset>(nullable: true),
ExpirationDate = table.Column<DateTimeOffset>(nullable: true),
Id = table.Column<string>(nullable: false, maxLength: null),
Payload = table.Column<string>(nullable: true),
Properties = table.Column<string>(nullable: true),
ReferenceId = table.Column<string>(maxLength: 100, nullable: true),
Status = table.Column<string>(maxLength: 25, nullable: false),
Subject = table.Column<string>(maxLength: 450, nullable: true),
Type = table.Column<string>(maxLength: 25, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_OpenIddictTokens", x => x.Id);
table.ForeignKey(
name: "FK_OpenIddictTokens_OpenIddictApplications_ApplicationId",
column: x => x.ApplicationId,
principalTable: "OpenIddictApplications",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_OpenIddictTokens_OpenIddictAuthorizations_AuthorizationId",
column: x => x.AuthorizationId,
principalTable: "OpenIddictAuthorizations",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
}, "OpenIddictTokens");
ReplaceOldTable(migrationBuilder, s =>
{
migrationBuilder.CreateTable(
name: s,
columns: table => new
{
ApplicationId = table.Column<string>(nullable: true, maxLength: null),
ConcurrencyToken = table.Column<string>(maxLength: 50, nullable: true),
Id = table.Column<string>(nullable: false, maxLength: null),
Properties = table.Column<string>(nullable: true),
Scopes = table.Column<string>(nullable: true),
Status = table.Column<string>(maxLength: 25, nullable: false),
Subject = table.Column<string>(maxLength: 450, nullable: true),
Type = table.Column<string>(maxLength: 25, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_OpenIddictAuthorizations", x => x.Id);
table.ForeignKey(
name: "FK_OpenIddictAuthorizations_OpenIddictApplications_ApplicationId",
column: x => x.ApplicationId,
principalTable: "OpenIddictApplications",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
}, "OpenIddictAuthorizations");
}
migrationBuilder.AddColumn<string>(
name: "Requirements",
table: "OpenIddictApplications",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
if (!migrationBuilder.IsSqlite())
{
migrationBuilder.DropColumn(
name: "Requirements",
table: "OpenIddictApplications");
migrationBuilder.AlterColumn<string>(
name: "Subject",
table: "OpenIddictTokens",
maxLength: 450,
nullable: false,
oldClrType: typeof(string),
oldMaxLength: 450,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "Subject",
table: "OpenIddictAuthorizations",
maxLength: 450,
nullable: false,
oldClrType: typeof(string),
oldMaxLength: 450,
oldNullable: true);
}
else
{
ReplaceOldTable(migrationBuilder, s =>
{
migrationBuilder.CreateTable(
name: s,
columns: table => new
{
ApplicationId = table.Column<string>(nullable: true, maxLength: null),
AuthorizationId = table.Column<string>(nullable: true, maxLength: null),
ConcurrencyToken = table.Column<string>(maxLength: 50, nullable: true),
CreationDate = table.Column<DateTimeOffset>(nullable: true),
ExpirationDate = table.Column<DateTimeOffset>(nullable: true),
Id = table.Column<string>(nullable: false, maxLength: null),
Payload = table.Column<string>(nullable: true),
Properties = table.Column<string>(nullable: true),
ReferenceId = table.Column<string>(maxLength: 100, nullable: true),
Status = table.Column<string>(maxLength: 25, nullable: false),
Subject = table.Column<string>(maxLength: 450, nullable: false),
Type = table.Column<string>(maxLength: 25, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_OpenIddictTokens", x => x.Id);
table.ForeignKey(
name: "FK_OpenIddictTokens_OpenIddictApplications_ApplicationId",
column: x => x.ApplicationId,
principalTable: "OpenIddictApplications",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_OpenIddictTokens_OpenIddictAuthorizations_AuthorizationId",
column: x => x.AuthorizationId,
principalTable: "OpenIddictAuthorizations",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
}, "OpenIddictTokens", "WHERE Subject IS NOT NULL");
ReplaceOldTable(migrationBuilder, s =>
{
migrationBuilder.CreateTable(
name: s,
columns: table => new
{
ApplicationId = table.Column<string>(nullable: true, maxLength: null),
ConcurrencyToken = table.Column<string>(maxLength: 50, nullable: true),
Id = table.Column<string>(nullable: false, maxLength: null),
Properties = table.Column<string>(nullable: true),
Scopes = table.Column<string>(nullable: true),
Status = table.Column<string>(maxLength: 25, nullable: false),
Subject = table.Column<string>(maxLength: 450, nullable: false),
Type = table.Column<string>(maxLength: 25, nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_OpenIddictAuthorizations", x => x.Id);
table.ForeignKey(
name: "FK_OpenIddictAuthorizations_OpenIddictApplications_ApplicationId",
column: x => x.ApplicationId,
principalTable: "OpenIddictApplications",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
}, "OpenIddictAuthorizations", "WHERE Subject IS NOT NULL");
ReplaceOldTable(migrationBuilder, s =>
{
migrationBuilder.CreateTable(
name: s,
columns: table => new
{
ClientId = table.Column<string>(maxLength: 100, nullable: false),
ClientSecret = table.Column<string>(nullable: true),
ConcurrencyToken = table.Column<string>(maxLength: 50, nullable: true),
ConsentType = table.Column<string>(nullable: true),
DisplayName = table.Column<string>(nullable: true),
Id = table.Column<string>(nullable: false, maxLength: null),
Permissions = table.Column<string>(nullable: true),
PostLogoutRedirectUris = table.Column<string>(nullable: true),
Properties = table.Column<string>(nullable: true),
RedirectUris = table.Column<string>(nullable: true),
Type = table.Column<string>(maxLength: 25, nullable: false),
ApplicationUserId = table.Column<string>(nullable: true, maxLength: null)
},
constraints: table =>
{
table.PrimaryKey("PK_OpenIddictApplications", x => x.Id);
table.ForeignKey(
name: "FK_OpenIddictApplications_AspNetUsers_ApplicationUserId",
column: x => x.ApplicationUserId,
principalTable: "AspNetUsers",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
}, "OpenIddictApplications", "",
"ClientId, ClientSecret, ConcurrencyToken, ConsentType, DisplayName, Id, Permissions, PostLogoutRedirectUris, Properties, RedirectUris, Type, ApplicationUserId");
}
}
private void ReplaceOldTable(MigrationBuilder migrationBuilder, Action<string> createTable, string tableName,
string whereClause = "", string columns = "*")
{
createTable.Invoke($"New_{tableName}");
migrationBuilder.Sql(
$"INSERT INTO New_{tableName} {(columns == "*" ? string.Empty : $"({columns})")}SELECT {columns} FROM {tableName} {whereClause};");
migrationBuilder.Sql("PRAGMA foreign_keys=\"0\"", true);
migrationBuilder.Sql($"DROP TABLE {tableName}", true);
migrationBuilder.Sql($"ALTER TABLE New_{tableName} RENAME TO {tableName}", true);
migrationBuilder.Sql("PRAGMA foreign_keys=\"1\"", true);
}
}
}

View File

@ -1,28 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace BTCPayServer.Migrations
{
public static class MigrationsExtensions
{
public static bool SupportDropColumn(this Microsoft.EntityFrameworkCore.Migrations.Migration migration, string activeProvider)
{
return activeProvider != "Microsoft.EntityFrameworkCore.Sqlite";
}
public static bool SupportDropForeignKey(this Microsoft.EntityFrameworkCore.Migrations.Migration migration, string activeProvider)
{
return activeProvider != "Microsoft.EntityFrameworkCore.Sqlite";
}
public static bool SupportDropForeignKey(this DatabaseFacade facade)
{
return facade.ProviderName != "Microsoft.EntityFrameworkCore.Sqlite";
}
public static bool IsMySql(this Microsoft.EntityFrameworkCore.Migrations.Migration migration, string activeProvider)
{
return activeProvider == "Pomelo.EntityFrameworkCore.MySql";
}
}
}

View File

@ -1,29 +0,0 @@
namespace BTCPayServer.Rating
{
public enum RateSource
{
Coingecko,
Direct
}
public class AvailableRateProvider
{
public string Name { get; }
public string Url { get; }
public string Id { get; }
public string SourceId { get; }
public RateSource Source { get; }
public AvailableRateProvider(string id, string name, string url) : this(id, id, name, url, RateSource.Direct)
{
}
public AvailableRateProvider(string id, string sourceId, string name, string url, RateSource source)
{
Id = id;
SourceId = sourceId;
Name = name;
Url = url;
Source = source;
}
}
}

View File

@ -1,21 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" />
<ItemGroup>
<Folder Include="Providers\" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.4.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.6.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BTCPayServer.Common\BTCPayServer.Common.csproj" />
</ItemGroup>
</Project>

View File

@ -1,276 +0,0 @@
using System;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using BTCPayServer.Rating;
using System.Threading;
using Microsoft.Extensions.Logging.Abstractions;
using BTCPayServer.Logging;
using Newtonsoft.Json;
using System.Reflection;
using System.Globalization;
namespace BTCPayServer.Services.Rates
{
public class BackgroundFetcherState
{
public string ExchangeName { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset? LastRequested { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset? LastUpdated { get; set; }
[JsonProperty(ItemConverterType = typeof(BackgroundFetcherRateJsonConverter))]
public List<BackgroundFetcherRate> Rates { get; set; }
}
public class BackgroundFetcherRate
{
public CurrencyPair Pair { get; set; }
public BidAsk BidAsk { get; set; }
}
//This make the json more compact
class BackgroundFetcherRateJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(BackgroundFetcherRate).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
var value = (string)reader.Value;
var parts = value.Split('|');
return new BackgroundFetcherRate()
{
Pair = CurrencyPair.Parse(parts[0]),
BidAsk = new BidAsk(decimal.Parse(parts[1], CultureInfo.InvariantCulture), decimal.Parse(parts[2], CultureInfo.InvariantCulture))
};
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
var rate = (BackgroundFetcherRate)value;
writer.WriteValue($"{rate.Pair}|{rate.BidAsk.Bid.ToString(CultureInfo.InvariantCulture)}|{rate.BidAsk.Ask.ToString(CultureInfo.InvariantCulture)}");
}
}
/// <summary>
/// This class is a decorator which handle caching and pre-emptive query to the underlying rate provider
/// </summary>
public class BackgroundFetcherRateProvider : IRateProvider
{
public class LatestFetch
{
public PairRate[] Latest;
public DateTimeOffset NextRefresh;
public TimeSpan Backoff = TimeSpan.FromSeconds(5.0);
public DateTimeOffset Updated;
public DateTimeOffset Expiration;
public Exception Exception;
internal PairRate[] GetResult()
{
if (Expiration <= DateTimeOffset.UtcNow)
{
if (Exception != null)
{
ExceptionDispatchInfo.Capture(Exception).Throw();
}
else
{
throw new InvalidOperationException($"The rate has expired");
}
}
return Latest;
}
}
IRateProvider _Inner;
public IRateProvider Inner => _Inner;
public BackgroundFetcherRateProvider(string exchangeName, IRateProvider inner)
{
if (inner == null)
throw new ArgumentNullException(nameof(inner));
if (exchangeName == null)
throw new ArgumentNullException(nameof(exchangeName));
_Inner = inner;
ExchangeName = exchangeName;
}
public BackgroundFetcherState GetState()
{
var state = new BackgroundFetcherState()
{
ExchangeName = ExchangeName,
LastRequested = LastRequested
};
if (_Latest is LatestFetch fetch)
{
state.LastUpdated = fetch.Updated;
state.Rates = fetch.Latest
.Select(r => new BackgroundFetcherRate()
{
Pair = r.CurrencyPair,
BidAsk = r.BidAsk
}).ToList();
}
return state;
}
public void LoadState(BackgroundFetcherState state)
{
if (ExchangeName != state.ExchangeName)
throw new InvalidOperationException("The state does not belong to this fetcher");
if (state.LastRequested is DateTimeOffset lastRequested)
this.LastRequested = state.LastRequested;
if (state.LastUpdated is DateTimeOffset updated && state.Rates is List<BackgroundFetcherRate> rates)
{
var fetch = new LatestFetch()
{
Latest = rates.Select(r => new PairRate(r.Pair, r.BidAsk)).ToArray(),
Updated = updated,
NextRefresh = updated + RefreshRate,
Expiration = updated + ValidatyTime
};
_Latest = fetch;
}
}
TimeSpan _RefreshRate = TimeSpan.FromSeconds(30);
/// <summary>
/// The timespan after which <see cref="UpdateIfNecessary(CancellationToken)"/> will get the rates from the underlying rate provider
/// </summary>
public TimeSpan RefreshRate
{
get
{
return _RefreshRate;
}
set
{
var diff = value - _RefreshRate;
var latest = _Latest;
if (latest != null)
latest.NextRefresh += diff;
_RefreshRate = value;
}
}
TimeSpan _ValidatyTime = TimeSpan.FromMinutes(10);
/// <summary>
/// The timespan after which calls to <see cref="GetRatesAsync(CancellationToken)"/> will query underlying provider if the rate has not been updated
/// </summary>
public TimeSpan ValidatyTime
{
get
{
return _ValidatyTime;
}
set
{
var diff = value - _ValidatyTime;
var latest = _Latest;
if (latest != null)
latest.Expiration += diff;
_ValidatyTime = value;
}
}
public DateTimeOffset NextUpdate
{
get
{
var latest = _Latest;
if (latest == null)
return DateTimeOffset.UtcNow;
return latest.NextRefresh;
}
}
public bool DoNotAutoFetchIfExpired { get; set; }
readonly static TimeSpan MaxBackoff = TimeSpan.FromMinutes(5.0);
public async Task<LatestFetch> UpdateIfNecessary(CancellationToken cancellationToken)
{
if (NextUpdate <= DateTimeOffset.UtcNow)
{
try
{
await Fetch(cancellationToken);
}
catch { } // Exception is inside _Latest
return _Latest;
}
return _Latest;
}
LatestFetch _Latest;
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
LastRequested = DateTimeOffset.UtcNow;
var latest = _Latest;
if (!DoNotAutoFetchIfExpired && latest != null && latest.Expiration <= DateTimeOffset.UtcNow + TimeSpan.FromSeconds(1.0))
{
latest = null;
}
return (latest ?? (await Fetch(cancellationToken))).GetResult();
}
/// <summary>
/// The last time this rate provider has been used
/// </summary>
public DateTimeOffset? LastRequested { get; set; }
public string ExchangeName { get; }
public DateTimeOffset? Expiration
{
get
{
if (_Latest is LatestFetch f)
{
return f.Expiration;
}
return null;
}
}
private async Task<LatestFetch> Fetch(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var previous = _Latest;
var fetch = new LatestFetch();
try
{
var rates = await _Inner.GetRatesAsync(cancellationToken);
fetch.Latest = rates;
fetch.Updated = DateTimeOffset.UtcNow;
fetch.Expiration = fetch.Updated + ValidatyTime;
fetch.NextRefresh = fetch.Updated + RefreshRate;
}
catch (Exception ex)
{
if (previous != null)
{
fetch.Latest = previous.Latest;
fetch.Expiration = previous.Expiration;
fetch.Backoff = previous.Backoff * 2;
if (fetch.Backoff > MaxBackoff)
fetch.Backoff = MaxBackoff;
}
else
{
fetch.Expiration = DateTimeOffset.UtcNow;
}
fetch.NextRefresh = DateTimeOffset.UtcNow + fetch.Backoff;
fetch.Exception = ex;
}
_Latest = fetch;
fetch.GetResult(); // Will throw if not valid
return fetch;
}
public void InvalidateCache()
{
_Latest = null;
}
}
}

View File

@ -1,38 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Rating;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Services.Rates
{
public class BitbankRateProvider : IRateProvider
{
private readonly HttpClient _httpClient;
public BitbankRateProvider(HttpClient httpClient)
{
_httpClient = httpClient ?? new HttpClient();
}
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync("https://public.bitbank.cc/prices", cancellationToken);
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
return ((jobj["data"] as JObject) ?? new JObject())
.Properties()
.Select(p => new PairRate(CurrencyPair.Parse(p.Name), CreateBidAsk(p)))
.ToArray();
}
private static BidAsk CreateBidAsk(JProperty p)
{
var buy = p.Value["buy"].Value<decimal>();
var sell = p.Value["sell"].Value<decimal>();
// Bug from their API (https://github.com/btcpayserver/btcpayserver/issues/741)
return buy < sell ? new BidAsk(buy, sell) : new BidAsk(sell, buy);
}
}
}

View File

@ -1,32 +0,0 @@
using System.Linq;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using BTCPayServer.Rating;
using System.Threading;
using System.Net.Http;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Services.Rates
{
public class BitpayRateProvider : IRateProvider
{
private readonly HttpClient _httpClient;
public BitpayRateProvider(HttpClient httpClient)
{
_httpClient = httpClient ?? new HttpClient();
}
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync("https://bitpay.com/rates", cancellationToken);
var jarray = (JArray)(await response.Content.ReadAsAsync<JObject>(cancellationToken))["data"];
return jarray
.Children<JObject>()
.Select(jobj => new PairRate(new CurrencyPair("BTC", jobj["code"].Value<string>()), new BidAsk(jobj["rate"].Value<decimal>())))
.Where(o => o.CurrencyPair.Right != "BTC")
.ToArray();
}
}
}

View File

@ -1,26 +0,0 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Rating;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Services.Rates
{
public class ByllsRateProvider : IRateProvider
{
private readonly HttpClient _httpClient;
public ByllsRateProvider(HttpClient httpClient)
{
_httpClient = httpClient ?? new HttpClient();
}
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync("https://bylls.com/api/price?from_currency=BTC&to_currency=CAD", cancellationToken);
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
var value = jobj["public_price"]["to_price"].Value<decimal>();
return new[] { new PairRate(new CurrencyPair("BTC", "CAD"), new BidAsk(value)) };
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,183 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Rating;
using ExchangeSharp;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Newtonsoft.Json.Linq;
using MemoryCache = Microsoft.Extensions.Caching.Memory.MemoryCache;
namespace BTCPayServer.Services.Rates
{
public class RateProviderFactory
{
class WrapperRateProvider : IRateProvider
{
private readonly IRateProvider _inner;
public Exception Exception { get; private set; }
public TimeSpan Latency { get; set; }
public WrapperRateProvider(IRateProvider inner)
{
_inner = inner;
}
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
DateTimeOffset now = DateTimeOffset.UtcNow;
try
{
return await _inner.GetRatesAsync(cancellationToken);
}
catch (Exception ex)
{
Exception = ex;
return Array.Empty<PairRate>();
}
finally
{
Latency = DateTimeOffset.UtcNow - now;
}
}
}
public class QueryRateResult
{
public TimeSpan Latency { get; set; }
public PairRate[] PairRates { get; set; }
public ExchangeException Exception { get; internal set; }
public string Exchange { get; internal set; }
}
public RateProviderFactory(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
InitExchanges();
}
private readonly IHttpClientFactory _httpClientFactory;
private readonly Dictionary<string, IRateProvider> _DirectProviders = new Dictionary<string, IRateProvider>();
public Dictionary<string, IRateProvider> Providers
{
get
{
return _DirectProviders;
}
}
internal IEnumerable<AvailableRateProvider> GetDirectlySupportedExchanges()
{
yield return new AvailableRateProvider("binance", "Binance", "https://api.binance.com/api/v1/ticker/24hr");
yield return new AvailableRateProvider("bittrex", "Bittrex", "https://bittrex.com/api/v1.1/public/getmarketsummaries");
yield return new AvailableRateProvider("poloniex", "Poloniex", "https://poloniex.com/public?command=returnTicker");
yield return new AvailableRateProvider("hitbtc", "HitBTC", "https://api.hitbtc.com/api/2/public/ticker");
yield return new AvailableRateProvider("ndax", "NDAX", "https://ndax.io/api/returnTicker");
yield return new AvailableRateProvider("coingecko", "CoinGecko", "https://api.coingecko.com/api/v3/exchange_rates");
yield return new AvailableRateProvider("kraken", "Kraken", "https://api.kraken.com/0/public/Ticker?pair=ATOMETH,ATOMEUR,ATOMUSD,ATOMXBT,BATETH,BATEUR,BATUSD,BATXBT,BCHEUR,BCHUSD,BCHXBT,DAIEUR,DAIUSD,DAIUSDT,DASHEUR,DASHUSD,DASHXBT,EOSETH,EOSXBT,ETHCHF,ETHDAI,ETHUSDC,ETHUSDT,GNOETH,GNOXBT,ICXETH,ICXEUR,ICXUSD,ICXXBT,LINKETH,LINKEUR,LINKUSD,LINKXBT,LSKETH,LSKEUR,LSKUSD,LSKXBT,NANOETH,NANOEUR,NANOUSD,NANOXBT,OMGETH,OMGEUR,OMGUSD,OMGXBT,PAXGETH,PAXGEUR,PAXGUSD,PAXGXBT,SCETH,SCEUR,SCUSD,SCXBT,USDCEUR,USDCUSD,USDCUSDT,USDTCAD,USDTEUR,USDTGBP,USDTZUSD,WAVESETH,WAVESEUR,WAVESUSD,WAVESXBT,XBTCHF,XBTDAI,XBTUSDC,XBTUSDT,XDGEUR,XDGUSD,XETCXETH,XETCXXBT,XETCZEUR,XETCZUSD,XETHXXBT,XETHZCAD,XETHZEUR,XETHZGBP,XETHZJPY,XETHZUSD,XLTCXXBT,XLTCZEUR,XLTCZUSD,XMLNXETH,XMLNXXBT,XMLNZEUR,XMLNZUSD,XREPXETH,XREPXXBT,XREPZEUR,XXBTZCAD,XXBTZEUR,XXBTZGBP,XXBTZJPY,XXBTZUSD,XXDGXXBT,XXLMXXBT,XXMRXXBT,XXMRZEUR,XXMRZUSD,XXRPXXBT,XXRPZEUR,XXRPZUSD,XZECXXBT,XZECZEUR,XZECZUSD");
yield return new AvailableRateProvider("bylls", "Bylls", "https://bylls.com/api/price?from_currency=BTC&to_currency=CAD");
yield return new AvailableRateProvider("bitbank", "Bitbank", "https://public.bitbank.cc/prices");
yield return new AvailableRateProvider("bitpay", "Bitpay", "https://bitpay.com/rates");
}
void InitExchanges()
{
// We need to be careful to only add exchanges which OnGetTickers implementation make only 1 request
Providers.Add("binance", new ExchangeSharpRateProvider(new ExchangeBinanceAPI(), true));
Providers.Add("bittrex", new ExchangeSharpRateProvider(new ExchangeBittrexAPI(), true));
Providers.Add("poloniex", new ExchangeSharpRateProvider(new ExchangePoloniexAPI(), true));
Providers.Add("hitbtc", new ExchangeSharpRateProvider(new ExchangeHitBTCAPI(), true));
Providers.Add("ndax", new ExchangeSharpRateProvider(new ExchangeNDAXAPI(), true));
// Handmade providers
Providers.Add("coingecko", new CoinGeckoRateProvider(_httpClientFactory));
Providers.Add("kraken", new KrakenExchangeRateProvider() { HttpClient = _httpClientFactory?.CreateClient("EXCHANGE_KRAKEN") });
Providers.Add("bylls", new ByllsRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BYLLS")));
Providers.Add("bitbank", new BitbankRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITBANK")));
Providers.Add("bitpay", new BitpayRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITPAY")));
// Backward compatibility: coinaverage should be using coingecko to prevent stores from breaking
Providers.Add("coinaverage", new CoinGeckoRateProvider(_httpClientFactory));
// Those exchanges make multiple requests when calling GetTickers so we remove them
//DirectProviders.Add("gemini", new ExchangeSharpRateProvider("gemini", new ExchangeGeminiAPI()));
//DirectProviders.Add("bitfinex", new ExchangeSharpRateProvider("bitfinex", new ExchangeBitfinexAPI()));
//DirectProviders.Add("okex", new ExchangeSharpRateProvider("okex", new ExchangeOkexAPI()));
//DirectProviders.Add("bitstamp", new ExchangeSharpRateProvider("bitstamp", new ExchangeBitstampAPI()));
foreach (var provider in Providers.ToArray())
{
var prov = new BackgroundFetcherRateProvider(provider.Key, Providers[provider.Key]);
prov.RefreshRate = TimeSpan.FromMinutes(1.0);
prov.ValidatyTime = TimeSpan.FromMinutes(5.0);
Providers[provider.Key] = prov;
}
foreach (var supportedExchange in GetCoinGeckoSupportedExchanges())
{
if (!Providers.ContainsKey(supportedExchange.Id) && supportedExchange.Id != CoinGeckoRateProvider.CoinGeckoName)
{
var coingecko = new CoinGeckoRateProvider(_httpClientFactory)
{
UnderlyingExchange = supportedExchange.Id
};
var bgFetcher = new BackgroundFetcherRateProvider(supportedExchange.Id, coingecko);
bgFetcher.RefreshRate = TimeSpan.FromMinutes(1.0);
bgFetcher.ValidatyTime = TimeSpan.FromMinutes(5.0);
Providers.Add(supportedExchange.Id, bgFetcher);
}
}
}
IEnumerable<AvailableRateProvider> _AvailableRateProviders = null;
public IEnumerable<AvailableRateProvider> GetSupportedExchanges()
{
if (_AvailableRateProviders == null)
{
var availableProviders = new Dictionary<string, AvailableRateProvider>();
foreach (var exchange in GetDirectlySupportedExchanges())
{
availableProviders.Add(exchange.Id, exchange);
}
foreach (var exchange in GetCoinGeckoSupportedExchanges())
{
availableProviders.TryAdd(exchange.Id, exchange);
}
_AvailableRateProviders = availableProviders.Values.OrderBy(o => o.Name).ToArray();
}
return _AvailableRateProviders;
}
internal IEnumerable<AvailableRateProvider> GetCoinGeckoSupportedExchanges()
{
return JArray.Parse(CoinGeckoRateProvider.SupportedExchanges).Select(token =>
new AvailableRateProvider(Normalize(token["id"].ToString().ToLowerInvariant()), token["id"].ToString().ToLowerInvariant(), token["name"].ToString(),
$"https://api.coingecko.com/api/v3/exchanges/{token["id"]}/tickers", RateSource.Coingecko))
.Concat(new[] { new AvailableRateProvider("gdax", "gdax", string.Empty, $"https://api.coingecko.com/api/v3/exchanges/gdax", RateSource.Coingecko) });
}
private string Normalize(string name)
{
if (name == "oasis_trade")
return "oasisdex";
if (name == "gdax")
return "coinbasepro";
return name;
}
public async Task<QueryRateResult> QueryRates(string exchangeName, CancellationToken cancellationToken)
{
Providers.TryGetValue(exchangeName, out var directProvider);
directProvider = directProvider ?? NullRateProvider.Instance;
var wrapper = new WrapperRateProvider(directProvider);
var value = await wrapper.GetRatesAsync(cancellationToken);
return new QueryRateResult()
{
Exchange = exchangeName,
Latency = wrapper.Latency,
PairRates = value,
Exception = wrapper.Exception != null ? new ExchangeException() { Exception = wrapper.Exception, ExchangeName = exchangeName } : null
};
}
}
}

View File

@ -1,427 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using System.Security.Claims;
using BTCPayServer.Tests.Logging;
using Xunit;
using Xunit.Abstractions;
using System.Net.Http;
using System.Net.Http.Headers;
using BTCPayServer.Data;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenIddict.Abstractions;
using OpenQA.Selenium;
using Microsoft.AspNetCore.Identity;
namespace BTCPayServer.Tests
{
public class AuthenticationTests
{
public const int TestTimeout = TestUtils.TestTimeout;
public AuthenticationTests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) {Name = "Tests"};
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task GetRedirectedToLoginPathOnChallenge()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var client = tester.PayTester.HttpClient;
//Wallets endpoint is protected
var response = await client.GetAsync("wallets");
var urlPath = response.RequestMessage.RequestUri.ToString()
.Replace(tester.PayTester.ServerUri.ToString(), "");
//Cookie Challenge redirects you to login page
Assert.StartsWith("Account/Login", urlPath, StringComparison.InvariantCultureIgnoreCase);
var queryString = response.RequestMessage.RequestUri.ParseQueryString();
Assert.NotNull(queryString["ReturnUrl"]);
Assert.Equal("/wallets", queryString["ReturnUrl"]);
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanGetOpenIdConfiguration()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
using (var response =
await tester.PayTester.HttpClient.GetAsync("/.well-known/openid-configuration"))
{
using (var streamToReadFrom = new StreamReader(await response.Content.ReadAsStreamAsync()))
{
var json = await streamToReadFrom.ReadToEndAsync();
Assert.NotNull(json);
JObject.Parse(json); // Should do more tests but good enough
}
}
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanUseNonInteractiveFlows()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
await user.MakeAdmin();
var token = await RegisterPasswordClientAndGetAccessToken(user, null, tester);
await TestApiAgainstAccessToken(token, tester, user);
token = await RegisterPasswordClientAndGetAccessToken(user, "secret", tester);
await TestApiAgainstAccessToken(token, tester, user);
token = await RegisterClientCredentialsFlowAndGetAccessToken(user, "secret", tester);
await TestApiAgainstAccessToken(token, tester, user);
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Selenium", "Selenium")]
public async Task CanUseImplicitFlow()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
var tester = s.Server;
var user = tester.NewAccount();
user.GrantAccess();
await user.MakeAdmin();
var id = Guid.NewGuid().ToString();
var redirecturi = new Uri("http://127.0.0.1/oidc-callback");
var openIdClient = await user.RegisterOpenIdClient(
new OpenIddictApplicationDescriptor()
{
ClientId = id,
DisplayName = id,
Permissions = {OpenIddictConstants.Permissions.GrantTypes.Implicit},
RedirectUris = {redirecturi},
});
var implicitAuthorizeUrl = new Uri(tester.PayTester.ServerUri,
$"connect/authorize?response_type=token&client_id={id}&redirect_uri={redirecturi.AbsoluteUri}&scope=openid server_management store_management&nonce={Guid.NewGuid().ToString()}");
s.Driver.Navigate().GoToUrl(implicitAuthorizeUrl);
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
s.Driver.FindElement(By.Id("consent-yes")).Click();
var url = s.Driver.Url;
var results = url.Split("#").Last().Split("&")
.ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
await TestApiAgainstAccessToken(results["access_token"], tester, user);
//in Implicit mode, you renew your token by hitting the same endpoint but adding prompt=none. If you are still logged in on the site, you will receive a fresh token.
var implicitAuthorizeUrlSilentModel = new Uri($"{implicitAuthorizeUrl.OriginalString}&prompt=none");
s.Driver.Navigate().GoToUrl(implicitAuthorizeUrlSilentModel);
url = s.Driver.Url;
results = url.Split("#").Last().Split("&").ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
await TestApiAgainstAccessToken(results["access_token"], tester, user);
var stores = await TestApiAgainstAccessToken<StoreData[]>(results["access_token"],
$"api/test/me/stores",
tester.PayTester.HttpClient);
Assert.NotEmpty(stores);
Assert.True(await TestApiAgainstAccessToken<bool>(results["access_token"],
$"api/test/me/stores/{stores[0].Id}/can-edit",
tester.PayTester.HttpClient));
//we dont ask for consent after acquiring it the first time for the same scopes.
LogoutFlow(tester, id, s);
s.Driver.Navigate().GoToUrl(implicitAuthorizeUrl);
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
s.Driver.AssertElementNotFound(By.Id("consent-yes"));
// Let's asks without scopes
LogoutFlow(tester, id, s);
id = Guid.NewGuid().ToString();
openIdClient = await user.RegisterOpenIdClient(
new OpenIddictApplicationDescriptor()
{
ClientId = id,
DisplayName = id,
Permissions = { OpenIddictConstants.Permissions.GrantTypes.Implicit },
RedirectUris = { redirecturi },
});
implicitAuthorizeUrl = new Uri(tester.PayTester.ServerUri,
$"connect/authorize?response_type=token&client_id={id}&redirect_uri={redirecturi.AbsoluteUri}&scope=openid&nonce={Guid.NewGuid().ToString()}");
s.Driver.Navigate().GoToUrl(implicitAuthorizeUrl);
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
s.Driver.FindElement(By.Id("consent-yes")).Click();
results = s.Driver.Url.Split("#").Last().Split("&")
.ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
{
await TestApiAgainstAccessToken<StoreData[]>(results["access_token"],
$"api/test/me/stores",
tester.PayTester.HttpClient);
});
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
{
await TestApiAgainstAccessToken<bool>(results["access_token"],
$"api/test/me/stores/{stores[0].Id}/can-edit",
tester.PayTester.HttpClient);
});
}
}
void LogoutFlow(ServerTester tester, string clientId, SeleniumTester seleniumTester)
{
var logoutUrl = new Uri(tester.PayTester.ServerUri,
$"connect/logout?response_type=token&client_id={clientId}");
seleniumTester.Driver.Navigate().GoToUrl(logoutUrl);
seleniumTester.GoToHome();
Assert.Throws<NoSuchElementException>(() => seleniumTester.Driver.FindElement(By.Id("Logout")));
}
[Fact(Timeout = TestTimeout)]
[Trait("Selenium", "Selenium")]
public async Task CanUseCodeFlow()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
var tester = s.Server;
var user = tester.NewAccount();
user.GrantAccess();
await user.MakeAdmin();
var id = Guid.NewGuid().ToString();
var redirecturi = new Uri("http://127.0.0.1/oidc-callback");
var secret = "secret";
var openIdClient = await user.RegisterOpenIdClient(
new OpenIddictApplicationDescriptor()
{
ClientId = id,
DisplayName = id,
Permissions =
{
OpenIddictConstants.Permissions.GrantTypes.AuthorizationCode,
OpenIddictConstants.Permissions.GrantTypes.RefreshToken
},
RedirectUris = {redirecturi}
}, secret);
var authorizeUrl = new Uri(tester.PayTester.ServerUri,
$"connect/authorize?response_type=code&client_id={id}&redirect_uri={redirecturi.AbsoluteUri}&scope=openid offline_access server_management store_management&state={Guid.NewGuid().ToString()}");
s.Driver.Navigate().GoToUrl(authorizeUrl);
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
s.Driver.FindElement(By.Id("consent-yes")).Click();
var url = s.Driver.Url;
var results = url.Split("?").Last().Split("&")
.ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
var httpClient = tester.PayTester.HttpClient;
var httpRequest = new HttpRequestMessage(HttpMethod.Post,
new Uri(tester.PayTester.ServerUri, "/connect/token"))
{
Content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("grant_type",
OpenIddictConstants.GrantTypes.AuthorizationCode),
new KeyValuePair<string, string>("client_id", openIdClient.ClientId),
new KeyValuePair<string, string>("client_secret", secret),
new KeyValuePair<string, string>("code", results["code"]),
new KeyValuePair<string, string>("redirect_uri", redirecturi.AbsoluteUri)
})
};
var response = await httpClient.SendAsync(httpRequest);
Assert.True(response.IsSuccessStatusCode);
string content = await response.Content.ReadAsStringAsync();
var result = System.Text.Json.JsonSerializer.Deserialize<OpenIddictResponse>(content);
await TestApiAgainstAccessToken(result.AccessToken, tester, user);
var refreshedAccessToken = await RefreshAnAccessToken(result.RefreshToken, httpClient, id, secret);
await TestApiAgainstAccessToken(refreshedAccessToken, tester, user);
LogoutFlow(tester, id, s);
s.Driver.Navigate().GoToUrl(authorizeUrl);
s.Login(user.RegisterDetails.Email, user.RegisterDetails.Password);
Assert.Throws<NoSuchElementException>(() => s.Driver.FindElement(By.Id("consent-yes")));
results = url.Split("?").Last().Split("&")
.ToDictionary(s1 => s1.Split("=")[0], s1 => s1.Split("=")[1]);
Assert.True(results.ContainsKey("code"));
}
}
private static async Task<string> RefreshAnAccessToken(string refreshToken, HttpClient client, string clientId,
string clientSecret = null)
{
var httpRequest = new HttpRequestMessage(HttpMethod.Post,
new Uri(client.BaseAddress, "/connect/token"))
{
Content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("grant_type",
OpenIddictConstants.GrantTypes.RefreshToken),
new KeyValuePair<string, string>("client_id", clientId),
new KeyValuePair<string, string>("client_secret", clientSecret),
new KeyValuePair<string, string>("refresh_token", refreshToken)
})
};
var response = await client.SendAsync(httpRequest);
Assert.True(response.IsSuccessStatusCode);
string content = await response.Content.ReadAsStringAsync();
var result = System.Text.Json.JsonSerializer.Deserialize<OpenIddictResponse>(content);
Assert.NotEmpty(result.AccessToken);
Assert.Null(result.Error);
return result.AccessToken;
}
private static async Task<string> RegisterClientCredentialsFlowAndGetAccessToken(TestAccount user,
string secret,
ServerTester tester)
{
var id = Guid.NewGuid().ToString();
var openIdClient = await user.RegisterOpenIdClient(
new OpenIddictApplicationDescriptor()
{
ClientId = id,
DisplayName = id,
Permissions = {OpenIddictConstants.Permissions.GrantTypes.ClientCredentials}
}, secret);
var httpClient = tester.PayTester.HttpClient;
var httpRequest = new HttpRequestMessage(HttpMethod.Post,
new Uri(tester.PayTester.ServerUri, "/connect/token"))
{
Content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("grant_type",
OpenIddictConstants.GrantTypes.ClientCredentials),
new KeyValuePair<string, string>("client_id", openIdClient.ClientId),
new KeyValuePair<string, string>("client_secret", secret),
new KeyValuePair<string, string>("scope", "server_management store_management")
})
};
var response = await httpClient.SendAsync(httpRequest);
Assert.True(response.IsSuccessStatusCode);
string content = await response.Content.ReadAsStringAsync();
var result = System.Text.Json.JsonSerializer.Deserialize<OpenIddictResponse>(content);
Assert.NotEmpty(result.AccessToken);
Assert.Null(result.Error);
return result.AccessToken;
}
private static async Task<string> RegisterPasswordClientAndGetAccessToken(TestAccount user, string secret,
ServerTester tester)
{
var id = Guid.NewGuid().ToString();
var openIdClient = await user.RegisterOpenIdClient(
new OpenIddictApplicationDescriptor()
{
ClientId = id,
DisplayName = id,
Permissions = {OpenIddictConstants.Permissions.GrantTypes.Password}
}, secret);
var httpClient = tester.PayTester.HttpClient;
var httpRequest = new HttpRequestMessage(HttpMethod.Post,
new Uri(tester.PayTester.ServerUri, "/connect/token"))
{
Content = new FormUrlEncodedContent(new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("grant_type", OpenIddictConstants.GrantTypes.Password),
new KeyValuePair<string, string>("username", user.RegisterDetails.Email),
new KeyValuePair<string, string>("password", user.RegisterDetails.Password),
new KeyValuePair<string, string>("client_id", openIdClient.ClientId),
new KeyValuePair<string, string>("client_secret", secret),
new KeyValuePair<string, string>("scope", "server_management store_management")
})
};
var response = await httpClient.SendAsync(httpRequest);
Assert.True(response.IsSuccessStatusCode);
string content = await response.Content.ReadAsStringAsync();
var result = System.Text.Json.JsonSerializer.Deserialize<OpenIddictResponse>(content);
Assert.NotEmpty(result.AccessToken);
Assert.Null(result.Error);
return result.AccessToken;
}
async Task TestApiAgainstAccessToken(string accessToken, ServerTester tester, TestAccount testAccount)
{
var resultUser =
await TestApiAgainstAccessToken<string>(accessToken, "api/test/me/id",
tester.PayTester.HttpClient);
Assert.Equal(testAccount.UserId, resultUser);
var secondUser = tester.NewAccount();
secondUser.GrantAccess();
var resultStores =
await TestApiAgainstAccessToken<StoreData[]>(accessToken, "api/test/me/stores",
tester.PayTester.HttpClient);
Assert.Contains(resultStores,
data => data.Id.Equals(testAccount.StoreId, StringComparison.InvariantCultureIgnoreCase));
Assert.DoesNotContain(resultStores,
data => data.Id.Equals(secondUser.StoreId, StringComparison.InvariantCultureIgnoreCase));
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
$"api/test/me/stores/{testAccount.StoreId}/can-edit",
tester.PayTester.HttpClient));
Assert.True(await TestApiAgainstAccessToken<bool>(accessToken,
$"api/test/me/is-admin",
tester.PayTester.HttpClient));
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
{
await TestApiAgainstAccessToken<bool>(accessToken, $"api/test/me/stores/{secondUser.StoreId}/can-edit",
tester.PayTester.HttpClient);
});
}
public async Task<T> TestApiAgainstAccessToken<T>(string accessToken, string url, HttpClient client)
{
var httpRequest = new HttpRequestMessage(HttpMethod.Get,
new Uri(client.BaseAddress, url));
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var result = await client.SendAsync(httpRequest);
result.EnsureSuccessStatusCode();
var rawJson = await result.Content.ReadAsStringAsync();
if (typeof(T).IsPrimitive || typeof(T) == typeof(string))
{
return (T)Convert.ChangeType(rawJson, typeof(T));
}
return JsonConvert.DeserializeObject<T>(rawJson);
}
}
}

View File

@ -1,31 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework Condition="'$(TargetFrameworkOverride)' != ''">$(TargetFrameworkOverride)</TargetFramework>
<TargetFramework>netcoreapp2.1</TargetFramework>
<IsPackable>false</IsPackable>
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
<LangVersion>8.0</LangVersion>
<LangVersion>7.2</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>
</PropertyGroup>
<!--https://devblogs.microsoft.com/aspnet/testing-asp-net-core-mvc-web-apps-in-memory/-->
<Target Name="CopyAditionalFiles" AfterTargets="Build" Condition="'$(TargetFramework)'!=''">
<ItemGroup>
<DepsFilePaths Include="$([System.IO.Path]::ChangeExtension('%(_ResolvedProjectReferencePaths.FullPath)', '.deps.json'))" />
</ItemGroup>
<Copy SourceFiles="%(DepsFilePaths.FullPath)" DestinationFolder="$(OutputPath)" Condition="Exists('%(DepsFilePaths.FullPath)')" />
</Target>
<PropertyGroup Condition="'$(CI_TESTS)' == 'true'">
<DefineConstants>$(DefineConstants);SHORT_TIMEOUT</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="78.0.3904.7000" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
@ -46,8 +31,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BTCPayServer.Rating\BTCPayServer.Rating.csproj" />
<ProjectReference Include="..\BTCPayServer\BTCPayServer.csproj" />
</ItemGroup>
</Project>

View File

@ -13,6 +13,7 @@ using BTCPayServer.Tests.Logging;
using BTCPayServer.Tests.Mocks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Routing;
@ -32,13 +33,8 @@ using System.Security.Claims;
using System.Security.Principal;
using System.Text;
using System.Threading;
using OpenIddict.Abstractions;
using Xunit;
using BTCPayServer.Services;
using System.Net.Http;
using Microsoft.AspNetCore.Hosting.Server.Features;
using System.Threading.Tasks;
using AuthenticationSchemes = BTCPayServer.Security.AuthenticationSchemes;
namespace BTCPayServer.Tests
{
@ -63,7 +59,6 @@ namespace BTCPayServer.Tests
}
public Uri LTCNBXplorerUri { get; set; }
public Uri LBTCNBXplorerUri { get; set; }
public Uri ServerUri
{
@ -94,10 +89,7 @@ namespace BTCPayServer.Tests
public bool MockRates { get; set; } = true;
public HashSet<string> Chains { get; set; } = new HashSet<string>(){"BTC"};
public bool UseLightning { get; set; }
public async Task StartAsync()
public void Start()
{
if (!Directory.Exists(_Directory))
Directory.CreateDirectory(_Directory);
@ -106,72 +98,37 @@ namespace BTCPayServer.Tests
if (!Directory.Exists(chainDirectory))
Directory.CreateDirectory(chainDirectory);
StringBuilder config = new StringBuilder();
config.AppendLine($"{chain.ToLowerInvariant()}=1");
if (InContainer)
{
config.AppendLine($"bind=0.0.0.0");
}
config.AppendLine($"port={Port}");
config.AppendLine($"chains={string.Join(',', Chains)}");
if (Chains.Contains("BTC", StringComparer.OrdinalIgnoreCase))
{
config.AppendLine($"btc.explorer.url={NBXplorerUri.AbsoluteUri}");
config.AppendLine($"btc.explorer.cookiefile=0");
}
config.AppendLine($"chains=btc,ltc");
if (UseLightning)
{
config.AppendLine($"btc.lightning={IntegratedLightning.AbsoluteUri}");
var localLndBackupFile = Path.Combine(_Directory, "walletunlock.json");
File.Copy(TestUtils.GetTestDataFullPath("LndSeedBackup/walletunlock.json"), localLndBackupFile, true);
config.AppendLine($"btc.external.lndseedbackup={localLndBackupFile}");
}
config.AppendLine($"btc.explorer.url={NBXplorerUri.AbsoluteUri}");
config.AppendLine($"btc.explorer.cookiefile=0");
if (Chains.Contains("LTC", StringComparer.OrdinalIgnoreCase))
{
config.AppendLine($"ltc.explorer.url={LTCNBXplorerUri.AbsoluteUri}");
config.AppendLine($"ltc.explorer.cookiefile=0");
}
if (Chains.Contains("LBTC", StringComparer.OrdinalIgnoreCase))
{
config.AppendLine($"lbtc.explorer.url={LBTCNBXplorerUri.AbsoluteUri}");
config.AppendLine($"lbtc.explorer.cookiefile=0");
}
config.AppendLine("allow-admin-registration=1");
config.AppendLine($"torrcfile={TestUtils.GetTestDataFullPath("Tor/torrc")}");
config.AppendLine($"debuglog=debug.log");
config.AppendLine($"ltc.explorer.url={LTCNBXplorerUri.AbsoluteUri}");
config.AppendLine($"ltc.explorer.cookiefile=0");
if (!string.IsNullOrEmpty(SSHPassword) && string.IsNullOrEmpty(SSHKeyFile))
config.AppendLine($"sshpassword={SSHPassword}");
if (!string.IsNullOrEmpty(SSHKeyFile))
config.AppendLine($"sshkeyfile={SSHKeyFile}");
if (!string.IsNullOrEmpty(SSHConnection))
config.AppendLine($"sshconnection={SSHConnection}");
config.AppendLine($"btc.lightning={IntegratedLightning.AbsoluteUri}");
if (TestDatabase == TestDatabases.MySQL && !String.IsNullOrEmpty(MySQL))
config.AppendLine($"mysql=" + MySQL);
else if (!String.IsNullOrEmpty(Postgres))
config.AppendLine($"postgres=" + Postgres);
var confPath = Path.Combine(chainDirectory, "settings.config");
await File.WriteAllTextAsync(confPath, config.ToString());
File.WriteAllText(confPath, config.ToString());
ServerUri = new Uri("http://" + HostName + ":" + Port + "/");
HttpClient = new HttpClient();
HttpClient.BaseAddress = ServerUri;
Environment.SetEnvironmentVariable("ASPNETCORE_ENVIRONMENT", "Development");
var conf = new DefaultConfiguration() { Logger = Logs.LogProvider.CreateLogger("Console") }.CreateConfiguration(new[] { "--datadir", _Directory, "--conf", confPath, "--disable-registration", "false" });
_Host = new WebHostBuilder()
.UseConfiguration(conf)
.UseContentRoot(FindBTCPayServerDirectory())
.UseWebRoot(Path.Combine(FindBTCPayServerDirectory(), "wwwroot"))
.ConfigureServices(s =>
{
s.AddLogging(l =>
{
l.AddFilter("System.Net.Http.HttpClient", LogLevel.Critical);
l.SetMinimumLevel(LogLevel.Information)
.AddFilter("Microsoft", LogLevel.Error)
.AddFilter("Hangfire", LogLevel.Error)
@ -181,18 +138,14 @@ namespace BTCPayServer.Tests
.UseKestrel()
.UseStartup<Startup>()
.Build();
await _Host.StartWithTasksAsync();
var urls = _Host.ServerFeatures.Get<IServerAddressesFeature>().Addresses;
foreach (var url in urls)
{
Logs.Tester.LogInformation("Listening on " + url);
}
Logs.Tester.LogInformation("Server URI " + ServerUri);
_Host.Start();
InvoiceRepository = (InvoiceRepository)_Host.Services.GetService(typeof(InvoiceRepository));
StoreRepository = (StoreRepository)_Host.Services.GetService(typeof(StoreRepository));
Networks = (BTCPayNetworkProvider)_Host.Services.GetService(typeof(BTCPayNetworkProvider));
var dashBoard = (NBXplorerDashboard)_Host.Services.GetService(typeof(NBXplorerDashboard));
while(!dashBoard.IsFullySynched())
{
Thread.Sleep(10);
}
if (MockRates)
{
@ -200,74 +153,61 @@ namespace BTCPayServer.Tests
rateProvider.Providers.Clear();
var coinAverageMock = new MockRateProvider();
coinAverageMock.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_USD"), new BidAsk(5000m)));
coinAverageMock.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_CAD"), new BidAsk(4500m)));
coinAverageMock.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_LTC"), new BidAsk(162m)));
coinAverageMock.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("LTC_USD"), new BidAsk(500m)));
rateProvider.Providers.Add("coingecko", coinAverageMock);
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
{
Exchange = "coinaverage",
CurrencyPair = CurrencyPair.Parse("BTC_USD"),
BidAsk = new BidAsk(5000m)
});
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
{
Exchange = "coinaverage",
CurrencyPair = CurrencyPair.Parse("BTC_CAD"),
BidAsk = new BidAsk(4500m)
});
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
{
Exchange = "coinaverage",
CurrencyPair = CurrencyPair.Parse("LTC_BTC"),
BidAsk = new BidAsk(0.001m)
});
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
{
Exchange = "coinaverage",
CurrencyPair = CurrencyPair.Parse("LTC_USD"),
BidAsk = new BidAsk(500m)
});
rateProvider.Providers.Add("coinaverage", coinAverageMock);
var bitflyerMock = new MockRateProvider();
bitflyerMock.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_JPY"), new BidAsk(700000m)));
bitflyerMock.ExchangeRates.Add(new Rating.ExchangeRate()
{
Exchange = "bitflyer",
CurrencyPair = CurrencyPair.Parse("BTC_JPY"),
BidAsk = new BidAsk(700000m)
});
rateProvider.Providers.Add("bitflyer", bitflyerMock);
var quadrigacx = new MockRateProvider();
quadrigacx.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_CAD"), new BidAsk(6000m)));
quadrigacx.ExchangeRates.Add(new Rating.ExchangeRate()
{
Exchange = "quadrigacx",
CurrencyPair = CurrencyPair.Parse("BTC_CAD"),
BidAsk = new BidAsk(6000m)
});
rateProvider.Providers.Add("quadrigacx", quadrigacx);
var bittrex = new MockRateProvider();
bittrex.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("DOGE_BTC"), new BidAsk(0.004m)));
bittrex.ExchangeRates.Add(new Rating.ExchangeRate()
{
Exchange = "bittrex",
CurrencyPair = CurrencyPair.Parse("DOGE_BTC"),
BidAsk = new BidAsk(0.004m)
});
rateProvider.Providers.Add("bittrex", bittrex);
var bitfinex = new MockRateProvider();
bitfinex.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("UST_BTC"), new BidAsk(0.000136m)));
rateProvider.Providers.Add("bitfinex", bitfinex);
}
Logs.Tester.LogInformation("Waiting site is operational...");
await WaitSiteIsOperational();
Logs.Tester.LogInformation("Site is now operational");
}
private async Task WaitSiteIsOperational()
{
using (var cts = new CancellationTokenSource(20_000))
{
var synching = WaitIsFullySynched(cts.Token);
var accessingHomepage = WaitCanAccessHomepage(cts.Token);
await Task.WhenAll(synching, accessingHomepage).ConfigureAwait(false);
}
}
private async Task WaitCanAccessHomepage(CancellationToken cancellationToken)
{
while (true)
{
var resp = await HttpClient.GetAsync("/", cancellationToken).ConfigureAwait(false);
if (resp.StatusCode == HttpStatusCode.OK)
break;
await Task.Delay(10, cancellationToken).ConfigureAwait(false);
}
}
private async Task WaitIsFullySynched(CancellationToken cancellationToken)
{
var dashBoard = GetService<NBXplorerDashboard>();
while (!dashBoard.IsFullySynched())
{
await Task.Delay(10, cancellationToken).ConfigureAwait(false);
}
}
private string FindBTCPayServerDirectory()
{
var solutionDirectory = TestUtils.TryGetSolutionDirectoryInfo(Directory.GetCurrentDirectory());
return Path.Combine(solutionDirectory.FullName, "BTCPayServer");
}
public HttpClient HttpClient { get; set; }
public string HostName
{
get;
@ -275,7 +215,6 @@ namespace BTCPayServer.Tests
}
public InvoiceRepository InvoiceRepository { get; private set; }
public StoreRepository StoreRepository { get; private set; }
public BTCPayNetworkProvider Networks { get; private set; }
public Uri IntegratedLightning { get; internal set; }
public bool InContainer { get; internal set; }
@ -284,12 +223,7 @@ namespace BTCPayServer.Tests
return _Host.Services.GetRequiredService<T>();
}
public IServiceProvider ServiceProvider => _Host.Services;
public string SSHPassword { get; internal set; }
public string SSHKeyFile { get; internal set; }
public string SSHConnection { get; set; }
public T GetController<T>(string userId = null, string storeId = null, bool isAdmin = false) where T : Controller
public T GetController<T>(string userId = null, string storeId = null, Claim[] additionalClaims = null) where T : Controller
{
var context = new DefaultHttpContext();
context.Request.Host = new HostString("127.0.0.1", Port);
@ -298,10 +232,10 @@ namespace BTCPayServer.Tests
if (userId != null)
{
List<Claim> claims = new List<Claim>();
claims.Add(new Claim(OpenIddictConstants.Claims.Subject, userId));
if (isAdmin)
claims.Add(new Claim(ClaimTypes.Role, Roles.ServerAdmin));
context.User = new ClaimsPrincipal(new ClaimsIdentity(claims.ToArray(), AuthenticationSchemes.Cookie));
claims.Add(new Claim(ClaimTypes.NameIdentifier, userId));
if (additionalClaims != null)
claims.AddRange(additionalClaims);
context.User = new ClaimsPrincipal(new ClaimsIdentity(claims.ToArray(), Policies.CookieAuthentication));
}
if (storeId != null)
{

View File

@ -21,26 +21,25 @@ namespace BTCPayServer.Tests
{
public class ChangellyTests
{
public const int TestTimeout = 60_000;
public ChangellyTests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) {Name = "Tests"};
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact(Timeout = TestTimeout)]
[Fact]
[Trait("Integration", "Integration")]
public async void CanSetChangellyPaymentMethod()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
tester.Start();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
var storeBlob = controller.CurrentStore.GetStoreBlob();
var storeBlob = controller.StoreData.GetStoreBlob();
Assert.Null(storeBlob.ChangellySettings);
var updateModel = new UpdateChangellySettingsViewModel()
@ -55,7 +54,7 @@ namespace BTCPayServer.Tests
await controller.UpdateChangellySettings(user.StoreId, updateModel, "save")).ActionName);
var store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
storeBlob = controller.CurrentStore.GetStoreBlob();
storeBlob = controller.StoreData.GetStoreBlob();
Assert.NotNull(storeBlob.ChangellySettings);
Assert.NotNull(storeBlob.ChangellySettings);
Assert.IsType<ChangellySettings>(storeBlob.ChangellySettings);
@ -69,13 +68,13 @@ namespace BTCPayServer.Tests
}
[Fact(Timeout = TestTimeout)]
[Fact]
[Trait("Integration", "Integration")]
public async Task CanToggleChangellyPaymentMethod()
public async void CanToggleChangellyPaymentMethod()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
tester.Start();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
@ -107,13 +106,13 @@ namespace BTCPayServer.Tests
}
}
[Fact(Timeout = TestTimeout)]
[Fact]
[Trait("Integration", "Integration")]
public async void CannotUseChangellyApiWithoutChangellyPaymentMethodSet()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
tester.Start();
var user = tester.NewAccount();
user.GrantAccess();
var changellyController =
@ -162,13 +161,13 @@ namespace BTCPayServer.Tests
};
}
[Fact(Timeout = TestTimeout)]
[Fact]
[Trait("Integration", "Integration")]
public async void CanGetCurrencyListFromChangelly()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
tester.Start();
var user = tester.NewAccount();
user.GrantAccess();
@ -195,13 +194,13 @@ namespace BTCPayServer.Tests
}
[Fact(Timeout = TestTimeout)]
[Fact]
[Trait("Integration", "Integration")]
public async void CanCalculateToAmountForChangelly()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
tester.Start();
var user = tester.NewAccount();
user.GrantAccess();
@ -219,13 +218,13 @@ namespace BTCPayServer.Tests
tester.NetworkProvider, fetcher);
changellyController.IsTest = true;
Assert.IsType<decimal>(Assert
.IsType<OkObjectResult>(await changellyController.CalculateAmount(user.StoreId, "ltc", "btc", 1.0m, default))
.IsType<OkObjectResult>(await changellyController.CalculateAmount(user.StoreId, "ltc", "btc", 1.0m))
.Value);
}
}
[Fact]
[Trait("Fast", "Fast")]
[Trait("Integration", "Integration")]
public void CanComputeBaseAmount()
{
Assert.Equal(1, ChangellyCalculationHelper.ComputeBaseAmount(1, 1));
@ -235,7 +234,7 @@ namespace BTCPayServer.Tests
}
[Fact]
[Trait("Fast", "Fast")]
[Trait("Integration", "Integration")]
public void CanComputeCorrectAmount()
{
Assert.Equal(1, ChangellyCalculationHelper.ComputeCorrectAmount(0.5m, 1, 2));

View File

@ -1,181 +0,0 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Lightning;
using BTCPayServer.Tests.Logging;
using BTCPayServer.Views.Stores;
using NBitpayClient;
using OpenQA.Selenium;
using OpenQA.Selenium.Interactions;
using OpenQA.Selenium.Support.UI;
using Xunit;
using Xunit.Abstractions;
namespace BTCPayServer.Tests
{
[Trait("Selenium", "Selenium")]
public class CheckoutUITests
{
public const int TestTimeout = TestUtils.TestTimeout;
public CheckoutUITests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact(Timeout = TestTimeout)]
public async Task CanHandleRefundEmailForm()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
var store = s.CreateNewStore();
s.AddDerivationScheme("BTC");
s.GoToStore(store.storeId, StoreNavPages.Checkout);
s.Driver.FindElement(By.Id("RequiresRefundEmail")).Click();
s.Driver.FindElement(By.Name("command")).ForceClick();
var emailAlreadyThereInvoiceId = s.CreateInvoice(store.storeName, 100, "USD", "a@g.com");
s.GoToInvoiceCheckout(emailAlreadyThereInvoiceId);
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.GoToHome();
var invoiceId = s.CreateInvoice(store.storeName);
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
s.Driver.AssertNoError();
s.Driver.Navigate().Back();
s.Driver.FindElement(By.ClassName("invoice-checkout-link")).Click();
Assert.NotEmpty(s.Driver.FindElements(By.Id("checkoutCtrl")));
Assert.True(s.Driver.FindElement(By.Id("emailAddressFormInput")).Displayed);
s.Driver.FindElement(By.Id("emailAddressFormInput")).SendKeys("xxx");
s.Driver.FindElement(By.Id("emailAddressForm")).FindElement(By.CssSelector("button.action-button"))
.Click();
var formInput = s.Driver.FindElement(By.Id("emailAddressFormInput"));
Assert.True(formInput.Displayed);
Assert.Contains("form-input-invalid", formInput.GetAttribute("class"));
formInput = s.Driver.FindElement(By.Id("emailAddressFormInput"));
formInput.SendKeys("@g.com");
var actionButton = s.Driver.FindElement(By.Id("emailAddressForm")).FindElement(By.CssSelector("button.action-button"));
actionButton.Click();
try // Sometimes the click only take the focus, without actually really clicking on it...
{
actionButton.Click();
}
catch { }
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
s.Driver.Navigate().Refresh();
s.Driver.AssertElementNotFound(By.Id("emailAddressFormInput"));
}
}
[Fact(Timeout = TestTimeout)]
public async Task CanUseLanguageDropdown()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
var store = s.CreateNewStore();
s.AddDerivationScheme("BTC");
var invoiceId = s.CreateInvoice(store.storeName);
s.GoToInvoiceCheckout(invoiceId);
Assert.True(s.Driver.FindElement(By.Id("DefaultLang")).FindElements(By.TagName("option")).Count > 1);
var payWithTextEnglish = s.Driver.FindElement(By.Id("pay-with-text")).Text;
var prettyDropdown = s.Driver.FindElement(By.Id("prettydropdown-DefaultLang"));
prettyDropdown.Click();
await Task.Delay(200);
prettyDropdown.FindElement(By.CssSelector("[data-value=\"da-DK\"]")).Click();
await Task.Delay(1000);
Assert.NotEqual(payWithTextEnglish, s.Driver.FindElement(By.Id("pay-with-text")).Text);
s.Driver.Navigate().GoToUrl(s.Driver.Url + "?lang=da-DK");
Assert.NotEqual(payWithTextEnglish, s.Driver.FindElement(By.Id("pay-with-text")).Text);
s.Driver.Quit();
}
}
[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.FindElement(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();
Thread.Sleep(1000);
currencyDropdownButton = s.Driver.FindElement(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();
Thread.Sleep(1000);
currencyDropdownButton = s.Driver.FindElement(By.ClassName("payment__currencies"));
Assert.Contains("Lightning", currencyDropdownButton.Text);
s.Driver.Quit();
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Lightning", "Lightning")]
public async Task CanUseLightningSatsFeature()
{
using (var s = SeleniumTester.Create())
{
s.Server.ActivateLightning();
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
var store = s.CreateNewStore();
s.AddInternalLightningNode("BTC");
s.GoToStore(store.storeId, StoreNavPages.Checkout);
s.SetCheckbox(s, "LightningAmountInSatoshi", true);
var command = s.Driver.FindElement(By.Name("command"));
command.ForceClick();
var invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com");
s.GoToInvoiceCheckout(invoiceId);
Assert.Contains("Sats", s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Text);
}
}
}
}

View File

@ -5,8 +5,6 @@ using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc;
using Xunit;
using Xunit.Abstractions;
using BTCPayServer.Data;
using System.Threading.Tasks;
namespace BTCPayServer.Tests
{
@ -20,17 +18,17 @@ namespace BTCPayServer.Tests
[Fact]
[Trait("Integration", "Integration")]
public async Task CanSetCoinSwitchPaymentMethod()
public async void CanSetCoinSwitchPaymentMethod()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
tester.Start();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
var storeBlob = controller.CurrentStore.GetStoreBlob();
var storeBlob = controller.StoreData.GetStoreBlob();
Assert.Null(storeBlob.CoinSwitchSettings);
var updateModel = new UpdateCoinSwitchSettingsViewModel()
@ -42,7 +40,7 @@ namespace BTCPayServer.Tests
await controller.UpdateCoinSwitchSettings(user.StoreId, updateModel, "save")).ActionName);
var store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
storeBlob = controller.CurrentStore.GetStoreBlob();
storeBlob = controller.StoreData.GetStoreBlob();
Assert.NotNull(storeBlob.CoinSwitchSettings);
Assert.NotNull(storeBlob.CoinSwitchSettings);
Assert.IsType<CoinSwitchSettings>(storeBlob.CoinSwitchSettings);
@ -54,11 +52,11 @@ namespace BTCPayServer.Tests
[Fact]
[Trait("Integration", "Integration")]
public async Task CanToggleCoinSwitchPaymentMethod()
public async void CanToggleCoinSwitchPaymentMethod()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
tester.Start();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);

View File

@ -35,13 +35,13 @@ namespace BTCPayServer.Tests
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact(Timeout = TestTimeout)]
[Fact]
[Trait("Integration", "Integration")]
public async Task CanCreateAndDeleteCrowdfundApp()
public void CanCreateAndDeleteCrowdfundApp()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
tester.Start();
var user = tester.NewAccount();
user.GrantAccess();
var user2 = tester.NewAccount();
@ -75,13 +75,13 @@ namespace BTCPayServer.Tests
[Fact(Timeout = TestTimeout)]
[Fact]
[Trait("Integration", "Integration")]
public async Task CanContributeOnlyWhenAllowed()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
tester.Start();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -100,7 +100,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.Enabled = false;
crowdfundViewModel.EndDate = null;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
var anonAppPubsController = tester.PayTester.GetController<AppsPublicController>();
var publicApps = user.GetController<AppsPublicController>();
@ -109,7 +109,7 @@ namespace BTCPayServer.Tests
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund()
{
Amount = new decimal(0.01)
}, default));
}));
Assert.IsType<NotFoundResult>(await anonAppPubsController.ViewCrowdfund(appId, string.Empty));
@ -118,7 +118,7 @@ namespace BTCPayServer.Tests
{
RedirectToCheckout = false,
Amount = new decimal(0.01)
}, default));
}));
Assert.IsType<ViewResult>(await publicApps.ViewCrowdfund(appId, string.Empty));
Assert.IsType<NotFoundResult>(await anonAppPubsController.ViewCrowdfund(appId, string.Empty));
@ -126,11 +126,11 @@ namespace BTCPayServer.Tests
crowdfundViewModel.StartDate= DateTime.Today.AddDays(2);
crowdfundViewModel.Enabled = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund()
{
Amount = new decimal(0.01)
}, default));
}));
//Scenario 4: Enabled But End Date < Now - Not Allowed
@ -138,11 +138,11 @@ namespace BTCPayServer.Tests
crowdfundViewModel.EndDate= DateTime.Today.AddDays(-1);
crowdfundViewModel.Enabled = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund()
{
Amount = new decimal(0.01)
}, default));
}));
//Scenario 5: Enabled and within correct timeframe, however target is enforced and Amount is Over - Not Allowed
@ -152,28 +152,28 @@ namespace BTCPayServer.Tests
crowdfundViewModel.TargetAmount = 1;
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.EnforceTargetAmount = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund()
{
Amount = new decimal(1.01)
}, default));
}));
//Scenario 6: Allowed
Assert.IsType<OkObjectResult>(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund()
{
Amount = new decimal(0.05)
}, default));
}));
}
}
[Fact(Timeout = TestTimeout)]
[Fact]
[Trait("Integration", "Integration")]
public async Task CanComputeCrowdfundModel()
public void CanComputeCrowdfundModel()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
tester.Start();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -195,7 +195,7 @@ namespace BTCPayServer.Tests
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.UseAllStoreInvoices = true;
crowdfundViewModel.EnforceTargetAmount = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
var anonAppPubsController = tester.PayTester.GetController<AppsPublicController>();
var publicApps = user.GetController<AppsPublicController>();
@ -253,7 +253,7 @@ namespace BTCPayServer.Tests
Assert.Contains(AppService.GetAppInternalTag(appId), invoiceEntity.InternalTags);
crowdfundViewModel.UseAllStoreInvoices = false;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
Logs.Tester.LogInformation("Because UseAllStoreInvoices is false, let's make sure the invoice is not tagged");
invoice = user.BitPay.CreateInvoice(new Invoice()
@ -272,7 +272,7 @@ namespace BTCPayServer.Tests
Logs.Tester.LogInformation("After turning setting a softcap, let's check that only actual payments are counted");
crowdfundViewModel.EnforceTargetAmount = false;
crowdfundViewModel.UseAllStoreInvoices = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel).Result);
invoice = user.BitPay.CreateInvoice(new Invoice()
{
Buyer = new Buyer() { email = "test@fwf.com" },

View File

@ -26,10 +26,11 @@ namespace BTCPayServer.Tests
_Host = new WebHostBuilder()
.Configure(app =>
{
app.Run(async req =>
app.Run(req =>
{
await _Requests.Writer.WriteAsync(JsonConvert.DeserializeObject<JObject>(await new StreamReader(req.Request.Body).ReadToEndAsync()), _Closed.Token);
_Requests.Writer.WriteAsync(JsonConvert.DeserializeObject<JObject>(new StreamReader(req.Request.Body).ReadToEnd()), _Closed.Token);
req.Response.StatusCode = 200;
return Task.CompletedTask;
});
})
.UseKestrel()

View File

@ -1,26 +1,17 @@
FROM mcr.microsoft.com/dotnet/core/sdk:3.1.100 AS builder
FROM microsoft/dotnet:2.1.500-sdk-alpine3.7 AS builder
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT false
RUN apk add --no-cache icu-libs
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
RUN apt-get update && apt-get install -y --no-install-recommends chromium-driver \
&& rm -rf /var/lib/apt/lists/*
# This should be removed soon https://github.com/dotnet/corefx/issues/30003
RUN apk add --no-cache curl
WORKDIR /source
COPY nuget.config nuget.config
COPY Build/Common.csproj Build/Common.csproj
COPY BTCPayServer/BTCPayServer.csproj BTCPayServer/BTCPayServer.csproj
COPY BTCPayServer.Common/BTCPayServer.Common.csproj BTCPayServer.Common/BTCPayServer.Common.csproj
COPY BTCPayServer.Rating/BTCPayServer.Rating.csproj BTCPayServer.Rating/BTCPayServer.Rating.csproj
COPY BTCPayServer.Data/BTCPayServer.Data.csproj BTCPayServer.Data/BTCPayServer.Data.csproj
RUN cd BTCPayServer && dotnet restore
COPY BTCPayServer.Common/. BTCPayServer.Common/.
COPY BTCPayServer.Rating/. BTCPayServer.Rating/.
COPY BTCPayServer.Data/. BTCPayServer.Data/.
COPY BTCPayServer/. BTCPayServer/.
COPY Build/Version.csproj Build/Version.csproj
ENV SCREEN_HEIGHT 600 \
SCREEN_WIDTH 1200
COPY BTCPayServer.Tests/BTCPayServer.Tests.csproj BTCPayServer.Tests/BTCPayServer.Tests.csproj
RUN dotnet restore BTCPayServer.Tests/BTCPayServer.Tests.csproj
COPY . .
RUN cd BTCPayServer.Tests && dotnet build /p:CI_TESTS=true /p:RazorCompileOnBuild=true
RUN dotnet build
WORKDIR /source/BTCPayServer.Tests
ENTRYPOINT ["./docker-entrypoint.sh"]

View File

@ -1,126 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Configuration;
using BTCPayServer.Controllers;
using BTCPayServer.Models.WalletViewModels;
using BTCPayServer.Services.Wallets;
using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Memory;
using NBitcoin;
using NBitcoin.RPC;
using NBitpayClient;
using Xunit;
using Xunit.Abstractions;
namespace BTCPayServer.Tests
{
public class ElementsTests
{
public const int TestTimeout = 60_000;
public ElementsTests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact]
[Trait("Altcoins", "Altcoins")]
public async Task OnlyShowSupportedWallets()
{
using (var tester = ServerTester.Create())
{
tester.ActivateLBTC();
await tester.StartAsync();
await tester.EnsureChannelsSetup();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("LBTC");
user.RegisterDerivationScheme("BTC");
user.RegisterDerivationScheme("USDT");
Assert.Single(Assert.IsType<ListWalletsViewModel>(Assert.IsType<ViewResult>(await user.GetController<WalletsController>().ListWallets()).Model).Wallets);
}
}
[Fact]
[Trait("Fast", "Fast")]
public void LoadSubChainsAlways()
{
var options = new BTCPayServerOptions();
options.LoadArgs(new ConfigurationRoot(new List<IConfigurationProvider>()
{
new MemoryConfigurationProvider(new MemoryConfigurationSource()
{
InitialData = new[]
{
new KeyValuePair<string, string>("chains", "usdt"),
}
})
}));
Assert.NotNull(options.NetworkProvider.GetNetwork("LBTC"));
Assert.NotNull(options.NetworkProvider.GetNetwork("USDT"));
}
[Fact]
[Trait("Altcoins", "Altcoins")]
public async Task ElementsAssetsAreHandledCorrectly()
{
using (var tester = ServerTester.Create())
{
tester.ActivateLBTC();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("LBTC");
user.RegisterDerivationScheme("USDT");
//no tether on our regtest, lets create it and set it
var tether = tester.NetworkProvider.GetNetwork<ElementsBTCPayNetwork>("USDT");
var lbtc = tester.NetworkProvider.GetNetwork<ElementsBTCPayNetwork>("LBTC");
var issueAssetResult = await tester.LBTCExplorerNode.SendCommandAsync("issueasset", 100000, 0);
tether.AssetId = uint256.Parse(issueAssetResult.Result["asset"].ToString());
((ElementsBTCPayNetwork)tester.PayTester.GetService<BTCPayWalletProvider>().GetWallet("USDT").Network)
.AssetId = tether.AssetId;
Logs.Tester.LogInformation($"Asset is {tether.AssetId}");
Assert.Equal(tether.AssetId, tester.NetworkProvider.GetNetwork<ElementsBTCPayNetwork>("USDT").AssetId);
Assert.Equal(tether.AssetId, ((ElementsBTCPayNetwork)tester.PayTester.GetService<BTCPayWalletProvider>().GetWallet("USDT").Network).AssetId);
//test: register 2 assets on the same elements network and make sure paying an invoice on one does not affect the other in any way
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(0.1m, "BTC"));
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count);
var ci = invoice.CryptoInfo.Single(info => info.CryptoCode.Equals("LBTC"));
//1 lbtc = 1 btc
Assert.Equal(1, ci.Rate);
var star = await tester.LBTCExplorerNode.SendCommandAsync("sendtoaddress", ci.Address,ci.Due, "", "", false, true,
1, "UNSET", lbtc.AssetId);
TestUtils.Eventually(() =>
{
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
Assert.Equal("paid", localInvoice.Status);
Assert.Single(localInvoice.CryptoInfo.Single(info => info.CryptoCode.Equals("LBTC")).Payments);
});
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(0.1m, "BTC"));
ci = invoice.CryptoInfo.Single(info => info.CryptoCode.Equals("USDT"));
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count);
star = await tester.LBTCExplorerNode.SendCommandAsync("sendtoaddress", ci.Address, ci.Due, "", "", false, true,
1, "UNSET", tether.AssetId);
TestUtils.Eventually(() =>
{
var localInvoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
Assert.Equal("paid", localInvoice.Status);
Assert.Single(localInvoice.CryptoInfo.Single(info => info.CryptoCode.Equals("USDT", StringComparison.InvariantCultureIgnoreCase)).Payments);
});
}
}
}
}

View File

@ -1,100 +0,0 @@
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc;
using OpenQA.Selenium;
using Xunit;
namespace BTCPayServer.Tests
{
public static class Extensions
{
public static void ScrollTo(this IWebDriver driver, By by)
{
var element = driver.FindElement(by);
}
/// <summary>
/// Sometimes the chrome driver is fucked up and we need some magic to click on the element.
/// </summary>
/// <param name="element"></param>
public static void ForceClick(this IWebElement element)
{
element.SendKeys(Keys.Return);
}
public static void AssertNoError(this IWebDriver driver)
{
try
{
Assert.NotEmpty(driver.FindElements(By.ClassName("navbar-brand")));
if (driver.PageSource.Contains("alert-danger"))
{
foreach (var dangerAlert in driver.FindElements(By.ClassName("alert-danger")))
Assert.False(dangerAlert.Displayed, "No alert should be displayed");
}
}
catch
{
StringBuilder builder = new StringBuilder();
builder.AppendLine();
foreach (var logKind in new []{ LogType.Browser, LogType.Client, LogType.Driver, LogType.Server })
{
try
{
var logs = driver.Manage().Logs.GetLog(logKind);
builder.AppendLine($"Selenium [{logKind}]:");
foreach (var entry in logs)
{
builder.AppendLine($"[{entry.Level}]: {entry.Message}");
}
}
catch { }
builder.AppendLine($"---------");
}
Logs.Tester.LogInformation(builder.ToString());
builder = new StringBuilder();
builder.AppendLine($"Selenium [Sources]:");
builder.AppendLine(driver.PageSource);
builder.AppendLine($"---------");
Logs.Tester.LogInformation(builder.ToString());
throw;
}
}
public static T AssertViewModel<T>(this IActionResult result)
{
Assert.NotNull(result);
var vr = Assert.IsType<ViewResult>(result);
return Assert.IsType<T>(vr.Model);
}
public static async Task<T> AssertViewModelAsync<T>(this Task<IActionResult> task)
{
var result = await task;
Assert.NotNull(result);
var vr = Assert.IsType<ViewResult>(result);
return Assert.IsType<T>(vr.Model);
}
public static void AssertElementNotFound(this IWebDriver driver, By by)
{
DateTimeOffset now = DateTimeOffset.Now;
var wait = SeleniumTester.ImplicitWait;
while (DateTimeOffset.UtcNow - now < wait)
{
try
{
var webElement = driver.FindElement(by);
if (!webElement.Displayed)
return;
}
catch (NoSuchElementException)
{
return;
}
Thread.Sleep(50);
}
Assert.False(true, "Elements was found");
}
}
}

View File

@ -1,4 +1,4 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Text;

View File

@ -1,5 +1,4 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
@ -36,29 +35,27 @@ namespace BTCPayServer.Tests
}
}
public async Task Advance(TimeSpan time)
public void Advance(TimeSpan time)
{
_Now += time;
List<WaitObj> overdue = new List<WaitObj>();
lock (waits)
{
foreach (var wait in waits.ToArray())
{
if (_Now >= wait.Expiration)
{
overdue.Add(wait);
wait.CTS.TrySetResult(true);
waits.Remove(wait);
}
}
}
foreach (var o in overdue)
o.CTS.TrySetResult(true);
try
{
await Task.WhenAll(overdue.Select(o => o.CTS.Task).ToArray());
}
catch { }
}
public void AdvanceMilliseconds(long milli)
{
Advance(TimeSpan.FromMilliseconds(milli));
}
public override string ToString()
{
return _Now.Millisecond.ToString(CultureInfo.InvariantCulture);

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Rating;
using BTCPayServer.Services.Rates;
@ -10,15 +9,10 @@ namespace BTCPayServer.Tests.Mocks
{
public class MockRateProvider : IRateProvider
{
public List<PairRate> ExchangeRates { get; set; } = new List<PairRate>();
public MockRateProvider()
public ExchangeRates ExchangeRates { get; set; } = new ExchangeRates();
public Task<ExchangeRates> GetRatesAsync()
{
}
public Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
return Task.FromResult(ExchangeRates.ToArray());
return Task.FromResult(ExchangeRates);
}
}
}

View File

@ -1,134 +0,0 @@
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using BTCPayServer.Controllers;
using BTCPayServer.Models.WalletViewModels;
using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc;
using NBitcoin;
using NBitpayClient;
using Xunit;
using Xunit.Abstractions;
using BTCPayServer.Models;
namespace BTCPayServer.Tests
{
public class PSBTTests
{
public PSBTTests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact]
[Trait("Integration", "Integration")]
public async Task CanPlayWithPSBT()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
var invoice = user.BitPay.CreateInvoice(new Invoice()
{
Price = 10,
Currency = "USD",
PosData = "posData",
OrderId = "orderId",
ItemDesc = "Some \", description",
FullNotifications = true
}, Facade.Merchant);
var cashCow = tester.ExplorerNode;
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
cashCow.SendToAddress(invoiceAddress, Money.Coins(1.5m));
TestUtils.Eventually(() =>
{
invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.Equal("paid", invoice.Status);
});
var walletController = user.GetController<WalletsController>();
var walletId = new WalletId(user.StoreId, "BTC");
var sendDestination = new Key().PubKey.Hash.GetAddress(user.SupportedNetwork.NBitcoinNetwork).ToString();
var sendModel = new WalletSendModel()
{
Outputs = new List<WalletSendModel.TransactionOutput>()
{
new WalletSendModel.TransactionOutput()
{
DestinationAddress = sendDestination,
Amount = 0.1m,
}
},
FeeSatoshiPerByte = 1,
CurrentBalance = 1.5m
};
var vmLedger = await walletController.WalletSend(walletId, sendModel, command: "ledger").AssertViewModelAsync<WalletSendLedgerModel>();
PSBT.Parse(vmLedger.PSBT, user.SupportedNetwork.NBitcoinNetwork);
BitcoinAddress.Create(vmLedger.HintChange, user.SupportedNetwork.NBitcoinNetwork);
Assert.NotNull(vmLedger.WebsocketPath);
string redirectedPSBT = AssertRedirectedPSBT(await walletController.WalletSend(walletId, sendModel, command: "analyze-psbt"));
var vmPSBT = await walletController.WalletPSBT(walletId, new WalletPSBTViewModel() { PSBT = redirectedPSBT }).AssertViewModelAsync<WalletPSBTViewModel>();
var unsignedPSBT = PSBT.Parse(vmPSBT.PSBT, user.SupportedNetwork.NBitcoinNetwork);
Assert.NotNull(vmPSBT.Decoded);
var filePSBT = (FileContentResult)(await walletController.WalletPSBT(walletId, vmPSBT, "save-psbt"));
PSBT.Load(filePSBT.FileContents, user.SupportedNetwork.NBitcoinNetwork);
await walletController.WalletPSBT(walletId, vmPSBT, "ledger").AssertViewModelAsync<WalletSendLedgerModel>();
var vmPSBT2 = await walletController.WalletPSBT(walletId, vmPSBT, "broadcast").AssertViewModelAsync<WalletPSBTReadyViewModel>();
Assert.NotEmpty(vmPSBT2.Inputs.Where(i => i.Error != null));
Assert.Equal(vmPSBT.PSBT, vmPSBT2.PSBT);
var signedPSBT = unsignedPSBT.Clone();
signedPSBT.SignAll(user.DerivationScheme, user.ExtKey);
vmPSBT.PSBT = signedPSBT.ToBase64();
var psbtReady = await walletController.WalletPSBT(walletId, vmPSBT, "broadcast").AssertViewModelAsync<WalletPSBTReadyViewModel>();
Assert.Equal(2 + 1, psbtReady.Destinations.Count); // The fee is a destination
Assert.Contains(psbtReady.Destinations, d => d.Destination == sendDestination && !d.Positive);
Assert.Contains(psbtReady.Destinations, d => d.Positive);
var redirect = Assert.IsType<RedirectToActionResult>(await walletController.WalletPSBTReady(walletId, psbtReady, command: "broadcast"));
Assert.Equal(nameof(walletController.WalletTransactions), redirect.ActionName);
vmPSBT.PSBT = unsignedPSBT.ToBase64();
var combineVM = await walletController.WalletPSBT(walletId, vmPSBT, "combine").AssertViewModelAsync<WalletPSBTCombineViewModel>();
Assert.Equal(vmPSBT.PSBT, combineVM.OtherPSBT);
combineVM.PSBT = signedPSBT.ToBase64();
var psbt = AssertRedirectedPSBT(await walletController.WalletPSBTCombine(walletId, combineVM));
var signedPSBT2 = PSBT.Parse(psbt, user.SupportedNetwork.NBitcoinNetwork);
Assert.True(signedPSBT.TryFinalize(out _));
Assert.True(signedPSBT2.TryFinalize(out _));
Assert.Equal(signedPSBT, signedPSBT2);
// Can use uploaded file?
combineVM.PSBT = null;
combineVM.UploadedPSBTFile = TestUtils.GetFormFile("signedPSBT", signedPSBT.ToBytes());
psbt = AssertRedirectedPSBT(await walletController.WalletPSBTCombine(walletId, combineVM));
signedPSBT2 = PSBT.Parse(psbt, user.SupportedNetwork.NBitcoinNetwork);
Assert.True(signedPSBT.TryFinalize(out _));
Assert.True(signedPSBT2.TryFinalize(out _));
Assert.Equal(signedPSBT, signedPSBT2);
var ready = (await walletController.WalletPSBTReady(walletId, signedPSBT.ToBase64())).AssertViewModel<WalletPSBTReadyViewModel>();
Assert.Equal(signedPSBT.ToBase64(), ready.PSBT);
psbt = AssertRedirectedPSBT(await walletController.WalletPSBTReady(walletId, ready, command: "analyze-psbt"));
Assert.Equal(signedPSBT.ToBase64(), psbt);
redirect = Assert.IsType<RedirectToActionResult>(await walletController.WalletPSBTReady(walletId, ready, command: "broadcast"));
Assert.Equal(nameof(walletController.WalletTransactions), redirect.ActionName);
}
}
private static string AssertRedirectedPSBT(IActionResult view)
{
var postRedirectView = Assert.IsType<ViewResult>(view);
var postRedirectViewModel = Assert.IsType<PostRedirectViewModel>(postRedirectView.Model);
var redirectedPSBT = postRedirectViewModel.Parameters.Single(p => p.Key == "psbt").Value;
return redirectedPSBT;
}
}
}

View File

@ -1,240 +0,0 @@
using BTCPayServer.Tests.Logging;
using NBitcoin;
using Xunit;
using Xunit.Abstractions;
using BTCPayServer.Data;
using BTCPayServer.Services.Rates;
using System.Collections.Generic;
using System.Threading.Tasks;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Rating;
namespace BTCPayServer.Tests
{
[Trait("Fast", "Fast")]
public class PaymentHandlerTest
{
private BitcoinLikePaymentHandler handlerBTC;
private LightningLikePaymentHandler handlerLN;
private Dictionary<CurrencyPair, Task<RateResult>> currencyPairRateResult;
public PaymentHandlerTest(ITestOutputHelper helper)
{
#pragma warning disable CS0618
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
Logs.LogProvider = new XUnitLogProvider(helper);
var dummy = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest).ToString();
var networkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
currencyPairRateResult = new Dictionary<CurrencyPair, Task<RateResult>>();
var rateResultUSDBTC = new RateResult();
rateResultUSDBTC.BidAsk= new BidAsk(1m);
var rateResultBTCUSD = new RateResult();
rateResultBTCUSD.BidAsk= new BidAsk(1m);
currencyPairRateResult.Add(new CurrencyPair("USD", "BTC"), Task.FromResult(rateResultUSDBTC));
currencyPairRateResult.Add(new CurrencyPair("BTC", "USD"), Task.FromResult(rateResultBTCUSD));
handlerBTC = new BitcoinLikePaymentHandler(null, networkProvider, null, null);
handlerLN = new LightningLikePaymentHandler(null, null, networkProvider, null);
#pragma warning restore CS0618
}
[Fact]
public void CanPayWithLightningWhenInvoiceTotalUnderLightningMaxValue()
{
#pragma warning disable CS0618
//Given
var store = new StoreBlob
{
OnChainMinValue = null,
LightningMaxValue = new CurrencyValue() {Value = 100.00m, Currency = "USD"}
};
var paymentMethodId = new PaymentMethodId("BTC", PaymentTypes.LightningLike);
//When
var totalInvoiceAmount = new Money(98m, MoneyUnit.BTC);
//Then
var errorMessage = handlerLN.IsPaymentMethodAllowedBasedOnInvoiceAmount(store, currencyPairRateResult,
totalInvoiceAmount, paymentMethodId);
Assert.Equal(errorMessage.Result, string.Empty);
#pragma warning restore CS0618
}
[Fact]
public void CannotPayWithLightningWhenInvoiceTotalAboveLightningMaxValue()
{
#pragma warning disable CS0618
//Given
var store = new StoreBlob
{
OnChainMinValue = null,
LightningMaxValue = new CurrencyValue() {Value = 100.00m, Currency = "USD"}
};
var totalInvoiceAmount = new Money(102m, MoneyUnit.BTC);
//When
var paymentMethodId = new PaymentMethodId("BTC", PaymentTypes.LightningLike);
//Then
var errorMessage = handlerLN.IsPaymentMethodAllowedBasedOnInvoiceAmount(store, currencyPairRateResult,
totalInvoiceAmount, paymentMethodId);
Assert.NotEqual(errorMessage.Result, string.Empty);
#pragma warning restore CS0618
}
[Fact]
public void CanPayWithLightningWhenInvoiceTotalEqualLightningMaxValue()
{
#pragma warning disable CS0618
//Given
var store = new StoreBlob
{
OnChainMinValue = null,
LightningMaxValue = new CurrencyValue() {Value = 100.00m, Currency = "USD"}
};
var paymentMethodId = new PaymentMethodId("BTC", PaymentTypes.LightningLike);
//When
var totalInvoiceAmount = new Money(100m, MoneyUnit.BTC);
//Then
var errorMessage = handlerLN.IsPaymentMethodAllowedBasedOnInvoiceAmount(store, currencyPairRateResult,
totalInvoiceAmount, paymentMethodId);
Assert.Equal(errorMessage.Result, string.Empty);
#pragma warning restore CS0618
}
[Fact]
public void CanPayWithBitcoinWhenInvoiceTotalAboveOnChainMinValue()
{
#pragma warning disable CS0618
//Given
var store = new StoreBlob
{
OnChainMinValue = new CurrencyValue() {Value = 100.00m, Currency = "USD"},
LightningMaxValue = null
};
var paymentMethodId = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
//When
var totalInvoiceAmount = new Money(105m, MoneyUnit.BTC);
//Then
var errorMessage = handlerBTC.IsPaymentMethodAllowedBasedOnInvoiceAmount(store, currencyPairRateResult,
totalInvoiceAmount, paymentMethodId);
Assert.Equal(errorMessage.Result, string.Empty);
#pragma warning restore CS0618
}
[Fact]
public void CannotPayWithBitcoinWhenInvoiceTotalUnderOnChainMinValue()
{
#pragma warning disable CS0618
//Given
var store = new StoreBlob
{
OnChainMinValue = new CurrencyValue() {Value = 100.00m, Currency = "USD"},
LightningMaxValue = null
};
var totalInvoiceAmount = new Money(98m, MoneyUnit.BTC);
//When
var paymentMethodId = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
//Then
var errorMessage = handlerBTC.IsPaymentMethodAllowedBasedOnInvoiceAmount(store, currencyPairRateResult,
totalInvoiceAmount, paymentMethodId);
Assert.NotEqual(errorMessage.Result, string.Empty);
#pragma warning restore CS0618
}
[Fact]
public void CanPayWithBitcoinWhenInvoiceTotalEqualOnChainMinValue()
{
#pragma warning disable CS0618
//Given
var store = new StoreBlob
{
OnChainMinValue = new CurrencyValue() {Value = 100.00m, Currency = "USD"},
LightningMaxValue = null
};
var paymentMethodId = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
//When
var totalInvoiceAmount = new Money(100m, MoneyUnit.BTC);
//Then
var errorMessage = handlerBTC.IsPaymentMethodAllowedBasedOnInvoiceAmount(store, currencyPairRateResult,
totalInvoiceAmount, paymentMethodId);
Assert.Equal(errorMessage.Result, string.Empty);
#pragma warning restore CS0618
}
[Fact]
public void CannotPayWithBitcoinWhenInvoiceTotalUnderOnChainMinValueWhenLightningMaxValueIsGreater()
{
#pragma warning disable CS0618
//Given
var store = new StoreBlob
{
OnChainMinValue = new CurrencyValue() {Value = 50.00m, Currency = "USD"},
LightningMaxValue = new CurrencyValue() {Value = 100.00m, Currency = "USD"}
};
var paymentMethodId = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
//When
var totalInvoiceAmount = new Money(45m, MoneyUnit.BTC);
//Then
var errorMessage = handlerBTC.IsPaymentMethodAllowedBasedOnInvoiceAmount(store, currencyPairRateResult,
totalInvoiceAmount, paymentMethodId);
Assert.NotEqual(errorMessage.Result, string.Empty);
#pragma warning restore CS0618
}
}
}

View File

@ -37,11 +37,11 @@ namespace BTCPayServer.Tests
[Fact]
[Trait("Integration", "Integration")]
public async Task CanCreateViewUpdateAndDeletePaymentRequest()
public void CanCreateViewUpdateAndDeletePaymentRequest()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
tester.Start();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -92,13 +92,13 @@ namespace BTCPayServer.Tests
}
}
[Fact(Timeout = 60 * 2 * 1000)]
[Fact]
[Trait("Integration", "Integration")]
public async Task CanPayPaymentRequestWhenPossible()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
tester.Start();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
@ -153,64 +153,5 @@ namespace BTCPayServer.Tests
}
}
[Fact(Timeout = 60 * 2 * 1000)]
[Trait("Integration", "Integration")]
public async Task CanCancelPaymentWhenPossible()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
var paymentRequestController = user.GetController<PaymentRequestController>();
Assert.IsType<NotFoundResult>(await
paymentRequestController.CancelUnpaidPendingInvoice(Guid.NewGuid().ToString(), false));
var request = new UpdatePaymentRequestViewModel()
{
Title = "original juice",
Currency = "BTC",
Amount = 1,
StoreId = user.StoreId,
Description = "description"
};
var response = Assert
.IsType<RedirectToActionResult>(paymentRequestController.EditPaymentRequest(null, request).Result)
.RouteValues.First();
var paymentRequestId = response.Value.ToString();
var invoiceId = Assert
.IsType<OkObjectResult>(await paymentRequestController.PayPaymentRequest(paymentRequestId, false)).Value
.ToString();
var actionResult = Assert
.IsType<RedirectToActionResult>(await paymentRequestController.PayPaymentRequest(response.Value.ToString()));
Assert.Equal("Checkout", actionResult.ActionName);
Assert.Equal("Invoice", actionResult.ControllerName);
Assert.Contains(actionResult.RouteValues, pair => pair.Key == "Id" && pair.Value.ToString() == invoiceId);
var invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant);
Assert.Equal(InvoiceState.ToString(InvoiceStatus.New), invoice.Status);
Assert.IsType<OkObjectResult>(await
paymentRequestController.CancelUnpaidPendingInvoice(paymentRequestId, false));
invoice = user.BitPay.GetInvoice(invoiceId, Facade.Merchant);
Assert.Equal(InvoiceState.ToString(InvoiceStatus.Invalid), invoice.Status);
Assert.IsType<BadRequestObjectResult>(await
paymentRequestController.CancelUnpaidPendingInvoice(paymentRequestId, false));
}
}
}
}

View File

@ -41,13 +41,8 @@ You can call bitcoin-cli inside the container with `docker exec`, for example, i
```
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
.\docker-bitcoin-cli.ps1 sendtoaddress "mohu16LH66ptoWGEL1GtP6KHTBJYXMWhEf" 0.23111090
```
### Using the test litecoin-cli

View File

@ -34,7 +34,7 @@ namespace BTCPayServer.Tests
builder.AppendLine("DOGE_X = DOGE_BTC * BTC_X * 1.1");
builder.AppendLine("DOGE_BTC = Bittrex(DOGE_BTC)");
builder.AppendLine("// Some other cool comments");
builder.AppendLine("BTC_usd = kraken(BTC_USD)");
builder.AppendLine("BTC_usd = GDax(BTC_USD)");
builder.AppendLine("BTC_X = Coinbase(BTC_X);");
builder.AppendLine("X_X = CoinAverage(X_X) * 1.02");
@ -45,14 +45,14 @@ namespace BTCPayServer.Tests
"DOGE_X = DOGE_BTC * BTC_X * 1.1;\n" +
"DOGE_BTC = bittrex(DOGE_BTC);\n" +
"// Some other cool comments\n" +
"BTC_USD = kraken(BTC_USD);\n" +
"BTC_USD = gdax(BTC_USD);\n" +
"BTC_X = coinbase(BTC_X);\n" +
"X_X = coinaverage(X_X) * 1.02;",
rules.ToString());
var tests = new[]
{
(Pair: "DOGE_USD", Expected: "bittrex(DOGE_BTC) * kraken(BTC_USD) * 1.1"),
(Pair: "BTC_USD", Expected: "kraken(BTC_USD)"),
(Pair: "DOGE_USD", Expected: "bittrex(DOGE_BTC) * gdax(BTC_USD) * 1.1"),
(Pair: "BTC_USD", Expected: "gdax(BTC_USD)"),
(Pair: "BTC_CAD", Expected: "coinbase(BTC_CAD)"),
(Pair: "DOGE_CAD", Expected: "bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1"),
(Pair: "LTC_CAD", Expected: "coinaverage(LTC_CAD) * 1.02"),
@ -62,14 +62,14 @@ namespace BTCPayServer.Tests
Assert.Equal(test.Expected, rules.GetRuleFor(CurrencyPair.Parse(test.Pair)).ToString());
}
rules.Spread = 0.2m;
Assert.Equal("(bittrex(DOGE_BTC) * kraken(BTC_USD) * 1.1) * (0.8, 1.2)", rules.GetRuleFor(CurrencyPair.Parse("DOGE_USD")).ToString());
Assert.Equal("(bittrex(DOGE_BTC) * gdax(BTC_USD) * 1.1) * (0.8, 1.2)", rules.GetRuleFor(CurrencyPair.Parse("DOGE_USD")).ToString());
////////////////
// Check errors conditions
builder = new StringBuilder();
builder.AppendLine("DOGE_X = LTC_CAD * BTC_X * 1.1");
builder.AppendLine("DOGE_BTC = Bittrex(DOGE_BTC)");
builder.AppendLine("BTC_usd = kraken(BTC_USD)");
builder.AppendLine("BTC_usd = GDax(BTC_USD)");
builder.AppendLine("LTC_CHF = LTC_CHF * 1.01");
builder.AppendLine("BTC_X = Coinbase(BTC_X)");
Assert.True(RateRules.TryParse(builder.ToString(), out rules));
@ -77,7 +77,7 @@ namespace BTCPayServer.Tests
tests = new[]
{
(Pair: "LTC_CAD", Expected: "ERR_NO_RULE_MATCH(LTC_CAD)"),
(Pair: "DOGE_USD", Expected: "ERR_NO_RULE_MATCH(LTC_CAD) * kraken(BTC_USD) * 1.1"),
(Pair: "DOGE_USD", Expected: "ERR_NO_RULE_MATCH(LTC_CAD) * gdax(BTC_USD) * 1.1"),
(Pair: "LTC_CHF", Expected: "ERR_TOO_MUCH_NESTED_CALLS(LTC_CHF) * 1.01"),
};
foreach (var test in tests)
@ -90,15 +90,15 @@ namespace BTCPayServer.Tests
builder = new StringBuilder();
builder.AppendLine("DOGE_X = DOGE_BTC * BTC_X * 1.1");
builder.AppendLine("DOGE_BTC = Bittrex(DOGE_BTC)");
builder.AppendLine("BTC_usd = kraken(BTC_USD)");
builder.AppendLine("BTC_usd = GDax(BTC_USD)");
builder.AppendLine("BTC_X = Coinbase(BTC_X)");
builder.AppendLine("X_X = CoinAverage(X_X) * 1.02");
Assert.True(RateRules.TryParse(builder.ToString(), out rules));
var tests2 = new[]
{
(Pair: "DOGE_USD", Expected: "bittrex(DOGE_BTC) * kraken(BTC_USD) * 1.1", ExpectedExchangeRates: "bittrex(DOGE_BTC),kraken(BTC_USD)"),
(Pair: "BTC_USD", Expected: "kraken(BTC_USD)", ExpectedExchangeRates: "kraken(BTC_USD)"),
(Pair: "DOGE_USD", Expected: "bittrex(DOGE_BTC) * gdax(BTC_USD) * 1.1", ExpectedExchangeRates: "bittrex(DOGE_BTC),gdax(BTC_USD)"),
(Pair: "BTC_USD", Expected: "gdax(BTC_USD)", ExpectedExchangeRates: "gdax(BTC_USD)"),
(Pair: "BTC_CAD", Expected: "coinbase(BTC_CAD)", ExpectedExchangeRates: "coinbase(BTC_CAD)"),
(Pair: "DOGE_CAD", Expected: "bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1", ExpectedExchangeRates: "bittrex(DOGE_BTC),coinbase(BTC_CAD)"),
(Pair: "LTC_CAD", Expected: "coinaverage(LTC_CAD) * 1.02", ExpectedExchangeRates: "coinaverage(LTC_CAD)"),

View File

@ -1,325 +0,0 @@
using System;
using BTCPayServer;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;
using System.Text;
using NBitcoin;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using Xunit;
using System.IO;
using System.Net.Http;
using System.Reflection;
using BTCPayServer.Tests.Logging;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Lightning;
using BTCPayServer.Lightning.CLightning;
using BTCPayServer.Views.Stores;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OpenQA.Selenium.Interactions;
namespace BTCPayServer.Tests
{
public class SeleniumTester : IDisposable
{
public IWebDriver Driver { get; set; }
public ServerTester Server { get; set; }
public static SeleniumTester Create([CallerMemberNameAttribute] string scope = null)
{
var server = ServerTester.Create(scope);
return new SeleniumTester()
{
Server = server
};
}
public async Task StartAsync()
{
await Server.StartAsync();
ChromeOptions options = new ChromeOptions();
var isDebug = !Server.PayTester.InContainer;
if (!isDebug)
{
options.AddArguments("headless"); // Comment to view browser
options.AddArguments("window-size=1200x1000"); // Comment to view browser
}
options.AddArgument("shm-size=2g");
if (Server.PayTester.InContainer)
{
options.AddArgument("no-sandbox");
}
Driver = new ChromeDriver(Server.PayTester.InContainer ? "/usr/bin" : Directory.GetCurrentDirectory(), options);
if (isDebug)
{
//when running locally, depending on your resolution, the website may go into mobile responsive mode and screw with navigation of tests
Driver.Manage().Window.Maximize();
}
Logs.Tester.LogInformation("Selenium: Using chrome driver");
Logs.Tester.LogInformation("Selenium: Browsing to " + Server.PayTester.ServerUri);
Logs.Tester.LogInformation($"Selenium: Resolution {Driver.Manage().Window.Size}");
Driver.Manage().Timeouts().ImplicitWait = ImplicitWait;
GoToRegister();
Driver.AssertNoError();
}
internal void AssertHappyMessage()
{
using var cts = new CancellationTokenSource(10_000);
while (!cts.IsCancellationRequested)
{
var success = Driver.FindElements(By.ClassName("alert-success")).Where(el => el.Displayed).Any();
if (success)
return;
Thread.Sleep(100);
}
Logs.Tester.LogInformation(this.Driver.PageSource);
Assert.True(false, "Should have shown happy message");
}
public static readonly TimeSpan ImplicitWait = TimeSpan.FromSeconds(10);
public string Link(string relativeLink)
{
return Server.PayTester.ServerUri.AbsoluteUri.WithoutEndingSlash() + relativeLink.WithStartingSlash();
}
public void GoToRegister()
{
Driver.Navigate().GoToUrl(this.Link("/Account/Register"));
}
public string RegisterNewUser(bool isAdmin = false)
{
var usr = RandomUtils.GetUInt256().ToString().Substring(64 - 20) + "@a.com";
Driver.FindElement(By.Id("Email")).SendKeys(usr);
Driver.FindElement(By.Id("Password")).SendKeys("123456");
Driver.FindElement(By.Id("ConfirmPassword")).SendKeys("123456");
if (isAdmin)
Driver.FindElement(By.Id("IsAdmin")).Click();
Driver.FindElement(By.Id("RegisterButton")).Click();
Driver.AssertNoError();
return usr;
}
public (string storeName, string storeId) CreateNewStore()
{
var usr = "Store" + RandomUtils.GetUInt64().ToString();
Driver.FindElement(By.Id("Stores")).Click();
Driver.FindElement(By.Id("CreateStore")).Click();
Driver.FindElement(By.Id("Name")).SendKeys(usr);
Driver.FindElement(By.Id("Create")).Click();
return (usr, Driver.FindElement(By.Id("Id")).GetAttribute("value"));
}
public string GenerateWallet(string cryptoCode = "BTC", string seed = "", bool importkeys = false, bool privkeys = false)
{
Driver.FindElement(By.Id($"Modify{cryptoCode}")).ForceClick();
Driver.FindElement(By.Id("import-from-btn")).ForceClick();
Driver.FindElement(By.Id("nbxplorergeneratewalletbtn")).ForceClick();
Driver.FindElement(By.Id("ExistingMnemonic")).SendKeys(seed);
SetCheckbox(Driver.FindElement(By.Id("SavePrivateKeys")), privkeys);
SetCheckbox(Driver.FindElement(By.Id("ImportKeysToRPC")), importkeys);
Driver.FindElement(By.Id("btn-generate")).ForceClick();
AssertHappyMessage();
if (string.IsNullOrEmpty(seed))
{
seed = Driver.FindElements(By.ClassName("alert-success")).First().FindElement(By.TagName("code")).Text;
}
Driver.FindElement(By.Id("Confirm")).ForceClick();
AssertHappyMessage();
return seed;
}
public void AddDerivationScheme(string cryptoCode = "BTC", string derivationScheme = "xpub661MyMwAqRbcGABgHMUXDzPzH1tU7eZaAaJQXhDXsSxsqyQzQeU6kznNfSuAyqAK9UaWSaZaMFdNiY5BCF4zBPAzSnwfUAwUhwttuAKwfRX-[legacy]")
{
Driver.FindElement(By.Id($"Modify{cryptoCode}")).ForceClick();
Driver.FindElement(By.ClassName("store-derivation-scheme")).SendKeys(derivationScheme);
Driver.FindElement(By.Id("Continue")).ForceClick();
Driver.FindElement(By.Id("Confirm")).ForceClick();
AssertHappyMessage();
}
public void AddLightningNode(string cryptoCode, LightningConnectionType connectionType)
{
string connectionString = null;
if (connectionType == LightningConnectionType.Charge)
connectionString = "type=charge;server=" + Server.MerchantCharge.Client.Uri.AbsoluteUri;
else if (connectionType == LightningConnectionType.CLightning)
connectionString = "type=clightning;server=" + ((CLightningClient)Server.MerchantLightningD).Address.AbsoluteUri;
else if (connectionType == LightningConnectionType.LndREST)
connectionString = $"type=lnd-rest;server={Server.MerchantLnd.Swagger.BaseUrl};allowinsecure=true";
else
throw new NotSupportedException(connectionType.ToString());
Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).ForceClick();
Driver.FindElement(By.Name($"ConnectionString")).SendKeys(connectionString);
Driver.FindElement(By.Id($"save")).ForceClick();
}
public void AddInternalLightningNode(string cryptoCode)
{
Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).ForceClick();
Driver.FindElement(By.Id($"internal-ln-node-setter")).ForceClick();
Driver.FindElement(By.Id($"save")).ForceClick();
}
public void ClickOnAllSideMenus()
{
var links = Driver.FindElements(By.CssSelector(".nav-pills .nav-link")).Select(c => c.GetAttribute("href")).ToList();
Driver.AssertNoError();
Assert.NotEmpty(links);
foreach (var l in links)
{
Logs.Tester.LogInformation($"Checking no error on {l}");
Driver.Navigate().GoToUrl(l);
Driver.AssertNoError();
}
}
public void Dispose()
{
if (Driver != null)
{
try
{
Driver.Close();
}
catch { }
Driver.Dispose();
}
if (Server != null)
Server.Dispose();
}
internal void AssertNotFound()
{
Assert.Contains("Status Code: 404; Not Found", Driver.PageSource);
}
public void GoToHome()
{
Driver.Navigate().GoToUrl(Server.PayTester.ServerUri);
}
public void Logout()
{
Driver.FindElement(By.Id("Logout")).Click();
}
public void Login(string user, string password)
{
Driver.FindElement(By.Id("Email")).SendKeys(user);
Driver.FindElement(By.Id("Password")).SendKeys(password);
Driver.FindElement(By.Id("LoginButton")).Click();
}
public void GoToStore(string storeId, StoreNavPages storeNavPage = StoreNavPages.Index)
{
Driver.FindElement(By.Id("Stores")).Click();
Driver.FindElement(By.Id($"update-store-{storeId}")).Click();
if (storeNavPage != StoreNavPages.Index)
{
Driver.FindElement(By.Id(storeNavPage.ToString())).Click();
}
}
public void GoToInvoiceCheckout(string invoiceId)
{
Driver.FindElement(By.Id("Invoices")).Click();
Driver.FindElement(By.Id($"invoice-checkout-{invoiceId}")).Click();
CheckForJSErrors();
}
public void SetCheckbox(IWebElement element, bool value)
{
if ((value && !element.Selected) || (!value && element.Selected))
{
element.Click();
}
if (value != element.Selected)
{
SetCheckbox(element, value);
}
}
public void SetCheckbox(SeleniumTester s, string inputName, bool value)
{
SetCheckbox(s.Driver.FindElement(By.Name(inputName)), value);
}
public void ScrollToElement(IWebElement element)
{
Actions actions = new Actions(Driver);
actions.MoveToElement(element);
actions.Perform();
}
public void GoToInvoices()
{
Driver.FindElement(By.Id("Invoices")).Click();
}
public void GoToCreateInvoicePage()
{
GoToInvoices();
Driver.FindElement(By.Id("CreateNewInvoice")).Click();
}
public string CreateInvoice(string store, decimal amount = 100, string currency = "USD", string refundEmail = "")
{
GoToInvoices();
Driver.FindElement(By.Id("CreateNewInvoice")).Click();
Driver.FindElement(By.Id("Amount")).SendKeys(amount.ToString(CultureInfo.InvariantCulture));
var currencyEl = Driver.FindElement(By.Id("Currency"));
currencyEl.Clear();
currencyEl.SendKeys(currency);
Driver.FindElement(By.Id("BuyerEmail")).SendKeys(refundEmail);
Driver.FindElement(By.Name("StoreId")).SendKeys(store + Keys.Enter);
Driver.FindElement(By.Id("Create")).ForceClick();
Assert.True(Driver.PageSource.Contains("just created!"), "Unable to create Invoice");
var statusElement = Driver.FindElement(By.ClassName("alert-success"));
var id = statusElement.Text.Split(" ")[1];
return id;
}
private void CheckForJSErrors()
{
//wait for seleniun update: https://stackoverflow.com/questions/57520296/selenium-webdriver-3-141-0-driver-manage-logs-availablelogtypes-throwing-syste
// var errorStrings = new List<string>
// {
// "SyntaxError",
// "EvalError",
// "ReferenceError",
// "RangeError",
// "TypeError",
// "URIError"
// };
//
// var jsErrors = Driver.Manage().Logs.GetLog(LogType.Browser).Where(x => errorStrings.Any(e => x.Message.Contains(e)));
//
// if (jsErrors.Any())
// {
// Logs.Tester.LogInformation("JavaScript error(s):" + Environment.NewLine + jsErrors.Aggregate("", (s, entry) => s + entry.Message + Environment.NewLine));
// }
// Assert.Empty(jsErrors);
}
}
}

View File

@ -1,506 +0,0 @@
using System;
using Xunit;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using BTCPayServer.Tests.Logging;
using Xunit.Abstractions;
using OpenQA.Selenium.Interactions;
using System.Linq;
using NBitcoin;
using System.Threading.Tasks;
using System.Text.RegularExpressions;
namespace BTCPayServer.Tests
{
[Trait("Selenium", "Selenium")]
public class ChromeTests
{
public const int TestTimeout = TestUtils.TestTimeout;
public ChromeTests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact(Timeout = TestTimeout)]
public async Task CanNavigateServerSettings()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.RegisterNewUser(true);
s.Driver.FindElement(By.Id("ServerSettings")).Click();
s.Driver.AssertNoError();
s.ClickOnAllSideMenus();
s.Driver.FindElement(By.LinkText("Services")).Click();
Logs.Tester.LogInformation("Let's check if we can access the logs");
s.Driver.FindElement(By.LinkText("Logs")).Click();
s.Driver.FindElement(By.PartialLinkText(".log")).Click();
Assert.Contains("Starting listening NBXplorer", s.Driver.PageSource);
s.Driver.Quit();
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Lightning", "Lightning")]
public async Task CanUseLndSeedBackup()
{
using (var s = SeleniumTester.Create())
{
s.Server.ActivateLightning();
await s.StartAsync();
s.RegisterNewUser(true);
s.Driver.FindElement(By.Id("ServerSettings")).Click();
s.Driver.AssertNoError();
s.Driver.FindElement(By.LinkText("Services")).Click();
Logs.Tester.LogInformation("Let's if we can access LND's seed");
Assert.Contains("server/services/lndseedbackup/BTC", s.Driver.PageSource);
s.Driver.Navigate().GoToUrl(s.Link("/server/services/lndseedbackup/BTC"));
s.Driver.FindElement(By.Id("details")).Click();
var seedEl = s.Driver.FindElement(By.Id("SeedTextArea"));
Assert.True(seedEl.Displayed);
Assert.Contains("about over million", seedEl.Text, StringComparison.OrdinalIgnoreCase);
var passEl = s.Driver.FindElement(By.Id("PasswordInput"));
Assert.True(passEl.Displayed);
Assert.Contains(passEl.Text, "hellorockstar", StringComparison.OrdinalIgnoreCase);
s.Driver.FindElement(By.Id("delete")).Click();
s.Driver.FindElement(By.Id("continue")).Click();
s.AssertHappyMessage();
seedEl = s.Driver.FindElement(By.Id("SeedTextArea"));
Assert.Contains("Seed removed", seedEl.Text, StringComparison.OrdinalIgnoreCase);
}
}
[Fact(Timeout = TestTimeout)]
public async Task NewUserLogin()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
//Register & Log Out
var email = s.RegisterNewUser();
s.Driver.FindElement(By.Id("Logout")).Click();
s.Driver.AssertNoError();
Assert.Contains("Account/Login", s.Driver.Url);
// Should show the Tor address
Assert.Contains("wsaxew3qa5ljfuenfebmaf3m5ykgatct3p6zjrqwoouj3foererde3id.onion", s.Driver.PageSource);
s.Driver.Navigate().GoToUrl(s.Link("/invoices"));
Assert.Contains("ReturnUrl=%2Finvoices", s.Driver.Url);
// We should be redirected to login
//Same User Can Log Back In
s.Driver.FindElement(By.Id("Email")).SendKeys(email);
s.Driver.FindElement(By.Id("Password")).SendKeys("123456");
s.Driver.FindElement(By.Id("LoginButton")).Click();
// We should be redirected to invoice
Assert.EndsWith("/invoices", s.Driver.Url);
// Should not be able to reach server settings
s.Driver.Navigate().GoToUrl(s.Link("/server/users"));
Assert.Contains("ReturnUrl=%2Fserver%2Fusers", s.Driver.Url);
//Change Password & Log Out
s.Driver.FindElement(By.Id("MySettings")).Click();
s.Driver.FindElement(By.Id("ChangePassword")).Click();
s.Driver.FindElement(By.Id("OldPassword")).SendKeys("123456");
s.Driver.FindElement(By.Id("NewPassword")).SendKeys("abc???");
s.Driver.FindElement(By.Id("ConfirmPassword")).SendKeys("abc???");
s.Driver.FindElement(By.Id("UpdatePassword")).Click();
s.Driver.FindElement(By.Id("Logout")).Click();
s.Driver.AssertNoError();
//Log In With New Password
s.Driver.FindElement(By.Id("Email")).SendKeys(email);
s.Driver.FindElement(By.Id("Password")).SendKeys("abc???");
s.Driver.FindElement(By.Id("LoginButton")).Click();
Assert.True(s.Driver.PageSource.Contains("Stores"), "Can't Access Stores");
s.Driver.FindElement(By.Id("MySettings")).Click();
s.ClickOnAllSideMenus();
s.Driver.Quit();
}
}
static void LogIn(SeleniumTester s, string email)
{
s.Driver.FindElement(By.Id("Email")).SendKeys(email);
s.Driver.FindElement(By.Id("Password")).SendKeys("123456");
s.Driver.FindElement(By.Id("LoginButton")).Click();
s.Driver.AssertNoError();
}
[Fact(Timeout = TestTimeout)]
public async Task CanUseSSHService()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
var alice = s.RegisterNewUser(isAdmin: true);
s.Driver.Navigate().GoToUrl(s.Link("/server/services"));
Assert.Contains("server/services/ssh", s.Driver.PageSource);
using (var client = await s.Server.PayTester.GetService<BTCPayServer.Configuration.BTCPayServerOptions>().SSHSettings.ConnectAsync())
{
var result = await client.RunBash("echo hello");
Assert.Equal(string.Empty, result.Error);
Assert.Equal("hello\n", result.Output);
Assert.Equal(0, result.ExitStatus);
}
s.Driver.Navigate().GoToUrl(s.Link("/server/services/ssh"));
s.Driver.AssertNoError();
s.Driver.FindElement(By.Id("SSHKeyFileContent")).Clear();
s.Driver.FindElement(By.Id("SSHKeyFileContent")).SendKeys("tes't\r\ntest2");
s.Driver.FindElement(By.Id("submit")).ForceClick();
s.Driver.AssertNoError();
var text = s.Driver.FindElement(By.Id("SSHKeyFileContent")).Text;
// Browser replace \n to \r\n, so it is hard to compare exactly what we want
Assert.Contains("tes't", text);
Assert.Contains("test2", text);
Assert.True(s.Driver.PageSource.Contains("authorized_keys has been updated", StringComparison.OrdinalIgnoreCase));
s.Driver.FindElement(By.Id("SSHKeyFileContent")).Clear();
s.Driver.FindElement(By.Id("submit")).ForceClick();
text = s.Driver.FindElement(By.Id("SSHKeyFileContent")).Text;
Assert.DoesNotContain("test2", text);
}
}
[Fact(Timeout = TestTimeout)]
public async Task CanUseDynamicDns()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
var alice = s.RegisterNewUser(isAdmin: true);
s.Driver.Navigate().GoToUrl(s.Link("/server/services"));
Assert.Contains("Dynamic DNS", s.Driver.PageSource);
s.Driver.Navigate().GoToUrl(s.Link("/server/services/dynamic-dns"));
s.Driver.AssertNoError();
if (s.Driver.PageSource.Contains("pouet.hello.com"))
{
// Cleanup old test run
s.Driver.Navigate().GoToUrl(s.Link("/server/services/dynamic-dns/pouet.hello.com/delete"));
s.Driver.FindElement(By.Id("continue")).Click();
}
s.Driver.FindElement(By.Id("AddDynamicDNS")).Click();
s.Driver.AssertNoError();
// We will just cheat for test purposes by only querying the server
s.Driver.FindElement(By.Id("ServiceUrl")).SendKeys(s.Link("/"));
s.Driver.FindElement(By.Id("Settings_Hostname")).SendKeys("pouet.hello.com");
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("MyLog");
s.Driver.FindElement(By.Id("Settings_Password")).SendKeys("MyLog" + Keys.Enter);
s.Driver.AssertNoError();
Assert.Contains("The Dynamic DNS has been successfully queried", s.Driver.PageSource);
Assert.EndsWith("/server/services/dynamic-dns", s.Driver.Url);
// Try to do the same thing should fail (hostname already exists)
s.Driver.FindElement(By.Id("AddDynamicDNS")).Click();
s.Driver.AssertNoError();
s.Driver.FindElement(By.Id("ServiceUrl")).SendKeys(s.Link("/"));
s.Driver.FindElement(By.Id("Settings_Hostname")).SendKeys("pouet.hello.com");
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("MyLog");
s.Driver.FindElement(By.Id("Settings_Password")).SendKeys("MyLog" + Keys.Enter);
s.Driver.AssertNoError();
Assert.Contains("This hostname already exists", s.Driver.PageSource);
// Delete it
s.Driver.Navigate().GoToUrl(s.Link("/server/services/dynamic-dns"));
Assert.Contains("/server/services/dynamic-dns/pouet.hello.com/delete", s.Driver.PageSource);
s.Driver.Navigate().GoToUrl(s.Link("/server/services/dynamic-dns/pouet.hello.com/delete"));
s.Driver.FindElement(By.Id("continue")).Click();
s.Driver.AssertNoError();
Assert.DoesNotContain("/server/services/dynamic-dns/pouet.hello.com/delete", s.Driver.PageSource);
}
}
[Fact(Timeout = TestTimeout)]
public async Task CanCreateStores()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
var alice = s.RegisterNewUser();
var store = s.CreateNewStore().storeName;
s.AddDerivationScheme();
s.Driver.AssertNoError();
Assert.Contains(store, s.Driver.PageSource);
var storeUrl = s.Driver.Url;
s.ClickOnAllSideMenus();
s.GoToInvoices();
s.CreateInvoice(store);
s.AssertHappyMessage();
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
var invoiceUrl = s.Driver.Url;
// When logout we should not be able to access store and invoice details
s.Driver.FindElement(By.Id("Logout")).Click();
s.Driver.Navigate().GoToUrl(storeUrl);
Assert.Contains("ReturnUrl", s.Driver.Url);
s.Driver.Navigate().GoToUrl(invoiceUrl);
Assert.Contains("ReturnUrl", s.Driver.Url);
s.GoToRegister();
// When logged we should not be able to access store and invoice details
var bob = s.RegisterNewUser();
s.Driver.Navigate().GoToUrl(storeUrl);
Assert.Contains("ReturnUrl", s.Driver.Url);
s.Driver.Navigate().GoToUrl(invoiceUrl);
s.AssertNotFound();
s.GoToHome();
s.Logout();
// Let's add Bob as a guest to alice's store
LogIn(s, alice);
s.Driver.Navigate().GoToUrl(storeUrl + "/users");
s.Driver.FindElement(By.Id("Email")).SendKeys(bob + Keys.Enter);
Assert.Contains("User added successfully", s.Driver.PageSource);
s.Logout();
// Bob should not have access to store, but should have access to invoice
LogIn(s, bob);
s.Driver.Navigate().GoToUrl(storeUrl);
Assert.Contains("ReturnUrl", s.Driver.Url);
s.Driver.Navigate().GoToUrl(invoiceUrl);
s.Driver.AssertNoError();
// Alice should be able to delete the store
s.Logout();
LogIn(s, alice);
s.Driver.FindElement(By.Id("Stores")).Click();
s.Driver.FindElement(By.LinkText("Remove")).Click();
s.Driver.FindElement(By.Id("continue")).Click();
s.Driver.FindElement(By.Id("Stores")).Click();
s.Driver.Navigate().GoToUrl(storeUrl);
Assert.Contains("ReturnUrl", s.Driver.Url);
}
}
[Fact(Timeout = TestTimeout)]
public async Task CanUsePairing()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.Driver.Navigate().GoToUrl(s.Link("/api-access-request"));
Assert.Contains("ReturnUrl", s.Driver.Url);
s.GoToRegister();
var alice = s.RegisterNewUser();
var store = s.CreateNewStore().storeName;
s.AddDerivationScheme();
s.Driver.FindElement(By.Id("Tokens")).Click();
s.Driver.FindElement(By.Id("CreateNewToken")).Click();
s.Driver.FindElement(By.Id("RequestPairing")).Click();
string pairingCode = AssertUrlHasPairingCode(s);
s.Driver.FindElement(By.Id("ApprovePairing")).Click();
s.AssertHappyMessage();
Assert.Contains(pairingCode, s.Driver.PageSource);
var client = new NBitpayClient.Bitpay(new Key(), s.Server.PayTester.ServerUri);
await client.AuthorizeClient(new NBitpayClient.PairingCode(pairingCode));
await client.CreateInvoiceAsync(new NBitpayClient.Invoice()
{
Price = 0.000000012m,
Currency = "USD",
FullNotifications = true
}, NBitpayClient.Facade.Merchant);
client = new NBitpayClient.Bitpay(new Key(), s.Server.PayTester.ServerUri);
var code = await client.RequestClientAuthorizationAsync("hehe", NBitpayClient.Facade.Merchant);
s.Driver.Navigate().GoToUrl(code.CreateLink(s.Server.PayTester.ServerUri));
s.Driver.FindElement(By.Id("ApprovePairing")).Click();
await client.CreateInvoiceAsync(new NBitpayClient.Invoice()
{
Price = 0.000000012m,
Currency = "USD",
FullNotifications = true
}, NBitpayClient.Facade.Merchant);
s.Driver.Navigate().GoToUrl(s.Link("/api-tokens"));
s.Driver.FindElement(By.Id("RequestPairing")).Click();
s.Driver.FindElement(By.Id("ApprovePairing")).Click();
AssertUrlHasPairingCode(s);
}
}
private static string AssertUrlHasPairingCode(SeleniumTester s)
{
var regex = Regex.Match(new Uri(s.Driver.Url, UriKind.Absolute).Query, "pairingCode=([^&]*)");
Assert.True(regex.Success, $"{s.Driver.Url} does not match expected regex");
var pairingCode = regex.Groups[1].Value;
return pairingCode;
}
[Fact(Timeout = TestTimeout)]
public async Task CanCreateAppPoS()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.RegisterNewUser();
var store = s.CreateNewStore();
s.Driver.FindElement(By.Id("Apps")).Click();
s.Driver.FindElement(By.Id("CreateNewApp")).Click();
s.Driver.FindElement(By.Name("Name")).SendKeys("PoS" + Guid.NewGuid());
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("PointOfSale" + Keys.Enter);
s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(store + Keys.Enter);
s.Driver.FindElement(By.Id("Create")).Click();
s.Driver.FindElement(By.Id("EnableShoppingCart")).Click();
s.Driver.FindElement(By.Id("SaveSettings")).ForceClick();
s.Driver.FindElement(By.Id("ViewApp")).ForceClick();
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
Assert.True(s.Driver.PageSource.Contains("Tea shop"), "Unable to create PoS");
s.Driver.Quit();
}
}
[Fact(Timeout = TestTimeout)]
public async Task CanCreateAppCF()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.RegisterNewUser();
var store = s.CreateNewStore();
s.AddDerivationScheme();
s.Driver.FindElement(By.Id("Apps")).Click();
s.Driver.FindElement(By.Id("CreateNewApp")).Click();
s.Driver.FindElement(By.Name("Name")).SendKeys("CF" + Guid.NewGuid());
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("Crowdfund" + Keys.Enter);
s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(store + Keys.Enter);
s.Driver.FindElement(By.Id("Create")).Click();
s.Driver.FindElement(By.Id("Title")).SendKeys("Kukkstarter");
s.Driver.FindElement(By.CssSelector("div.note-editable.card-block")).SendKeys("1BTC = 1BTC");
s.Driver.FindElement(By.Id("TargetCurrency")).SendKeys("JPY");
s.Driver.FindElement(By.Id("TargetAmount")).SendKeys("700");
s.Driver.FindElement(By.Id("SaveSettings")).ForceClick();
s.Driver.FindElement(By.Id("ViewApp")).ForceClick();
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
Assert.True(s.Driver.PageSource.Contains("Currently Active!"), "Unable to create CF");
s.Driver.Quit();
}
}
[Fact(Timeout = TestTimeout)]
public async Task CanCreatePayRequest()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.RegisterNewUser();
s.CreateNewStore();
s.AddDerivationScheme();
s.Driver.FindElement(By.Id("PaymentRequests")).Click();
s.Driver.FindElement(By.Id("CreatePaymentRequest")).Click();
s.Driver.FindElement(By.Id("Title")).SendKeys("Pay123");
s.Driver.FindElement(By.Id("Amount")).SendKeys("700");
s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC");
s.Driver.FindElement(By.Id("SaveButton")).ForceClick();
s.Driver.FindElement(By.Name("ViewAppButton")).SendKeys(Keys.Return);
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
Assert.True(s.Driver.PageSource.Contains("Amount due"), "Unable to create Payment Request");
s.Driver.Quit();
}
}
[Fact(Timeout = TestTimeout)]
public async Task CanManageWallet()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.RegisterNewUser(true);
var storeId = s.CreateNewStore();
// In this test, we try to spend from a manual seed. We import the xpub 49'/0'/0', then try to use the seed
// to sign the transaction
var mnemonic = s.GenerateWallet("BTC", "", true, false);
var invoiceId = s.CreateInvoice(storeId.storeId);
var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
var address = invoice.EntityToDTO().Addresses["BTC"];
var result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
Assert.True(result.IsWatchOnly);
s.GoToStore(storeId.storeId);
mnemonic = s.GenerateWallet("BTC", "", true, true);
var root = new Mnemonic(mnemonic).DeriveExtKey();
invoiceId = s.CreateInvoice(storeId.storeId);
invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice( invoiceId);
address = invoice.EntityToDTO().Addresses["BTC"];
result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
Assert.False(result.IsWatchOnly);
var tx = s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create(address, Network.RegTest), Money.Coins(3.0m));
s.Server.ExplorerNode.Generate(1);
s.Driver.FindElement(By.Id("Wallets")).Click();
s.Driver.FindElement(By.LinkText("Manage")).Click();
s.ClickOnAllSideMenus();
// Make sure we can rescan, because we are admin!
s.Driver.FindElement(By.Id("WalletRescan")).ForceClick();
Assert.Contains("The batch size make sure", s.Driver.PageSource);
// We setup the fingerprint and the account key path
s.Driver.FindElement(By.Id("WalletSettings")).ForceClick();
// s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).SendKeys("8bafd160");
// s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).SendKeys("m/49'/0'/0'" + Keys.Enter);
// Check the tx sent earlier arrived
s.Driver.FindElement(By.Id("WalletTransactions")).ForceClick();
var walletTransactionLink = s.Driver.Url;
Assert.Contains(tx.ToString(), s.Driver.PageSource);
void SignWith(string signingSource)
{
// Send to bob
s.Driver.FindElement(By.Id("WalletSend")).Click();
var bob = new Key().PubKey.Hash.GetAddress(Network.RegTest);
SetTransactionOutput(0, bob, 1);
s.Driver.ScrollTo(By.Id("SendMenu"));
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
s.Driver.FindElement(By.CssSelector("button[value=seed]")).Click();
// Input the seed
s.Driver.FindElement(By.Id("SeedOrKey")).SendKeys(signingSource + Keys.Enter);
// Broadcast
Assert.Contains(bob.ToString(), s.Driver.PageSource);
Assert.Contains("1.00000000", s.Driver.PageSource);
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
Assert.Equal(walletTransactionLink, s.Driver.Url);
}
void SetTransactionOutput(int index, BitcoinAddress dest, decimal amount, bool subtract = false)
{
s.Driver.FindElement(By.Id($"Outputs_{index}__DestinationAddress")).SendKeys(dest.ToString());
var amountElement = s.Driver.FindElement(By.Id($"Outputs_{index}__Amount"));
amountElement.Clear();
amountElement.SendKeys(amount.ToString());
var checkboxElement = s.Driver.FindElement(By.Id($"Outputs_{index}__SubtractFeesFromOutput"));
if (checkboxElement.Selected != subtract)
{
checkboxElement.Click();
}
}
SignWith(mnemonic);
}
}
}
}

View File

@ -23,7 +23,6 @@ using BTCPayServer.Payments.Lightning;
using BTCPayServer.Lightning.CLightning;
using BTCPayServer.Lightning;
using BTCPayServer.Services;
using BTCPayServer.Tests.Logging;
namespace BTCPayServer.Tests
{
@ -44,51 +43,32 @@ namespace BTCPayServer.Tests
Directory.CreateDirectory(_Directory);
NetworkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
ExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_BTCRPCCONNECTION", "server=http://127.0.0.1:43782;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBitcoinNetwork);
ExplorerNode.ScanRPCCapabilities();
ExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_BTCRPCCONNECTION", "server=http://127.0.0.1:43782;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork("BTC").NBitcoinNetwork);
LTCExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_LTCRPCCONNECTION", "server=http://127.0.0.1:43783;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork("LTC").NBitcoinNetwork);
ExplorerClient = new ExplorerClient(NetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBXplorerNetwork, new Uri(GetEnvironment("TESTS_BTCNBXPLORERURL", "http://127.0.0.1:32838/")));
ExplorerClient = new ExplorerClient(NetworkProvider.GetNetwork("BTC").NBXplorerNetwork, new Uri(GetEnvironment("TESTS_BTCNBXPLORERURL", "http://127.0.0.1:32838/")));
LTCExplorerClient = new ExplorerClient(NetworkProvider.GetNetwork("LTC").NBXplorerNetwork, new Uri(GetEnvironment("TESTS_LTCNBXPLORERURL", "http://127.0.0.1:32838/")));
var btc = NetworkProvider.GetNetwork("BTC").NBitcoinNetwork;
CustomerLightningD = LightningClientFactory.CreateClient(GetEnvironment("TEST_CUSTOMERLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30992/"), btc);
MerchantLightningD = LightningClientFactory.CreateClient(GetEnvironment("TEST_MERCHANTLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30993/"), btc);
MerchantCharge = new ChargeTester(this, "TEST_MERCHANTCHARGE", "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify", "merchant_lightningd", btc);
MerchantLnd = new LndMockTester(this, "TEST_MERCHANTLND", "https://lnd:lnd@127.0.0.1:53280/", "merchant_lnd", btc);
PayTester = new BTCPayServerTester(Path.Combine(_Directory, "pay"))
{
NBXplorerUri = ExplorerClient.Address,
LTCNBXplorerUri = LTCExplorerClient.Address,
TestDatabase = Enum.Parse<TestDatabases>(GetEnvironment("TESTS_DB", TestDatabases.Postgres.ToString()), true),
Postgres = GetEnvironment("TESTS_POSTGRES", "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver"),
MySQL = GetEnvironment("TESTS_MYSQL", "User ID=root;Host=127.0.0.1;Port=33036;Database=btcpayserver")
MySQL = GetEnvironment("TESTS_MYSQL", "User ID=root;Host=127.0.0.1;Port=33036;Database=btcpayserver"),
IntegratedLightning = MerchantCharge.Client.Uri
};
PayTester.Port = int.Parse(GetEnvironment("TESTS_PORT", Utils.FreeTcpPort().ToString(CultureInfo.InvariantCulture)), CultureInfo.InvariantCulture);
PayTester.HostName = GetEnvironment("TESTS_HOSTNAME", "127.0.0.1");
PayTester.InContainer = bool.Parse(GetEnvironment("TESTS_INCONTAINER", "false"));
PayTester.SSHPassword = GetEnvironment("TESTS_SSHPASSWORD", "opD3i2282D");
PayTester.SSHKeyFile = GetEnvironment("TESTS_SSHKEYFILE", "");
PayTester.SSHConnection = GetEnvironment("TESTS_SSHCONNECTION", "root@127.0.0.1:21622");
}
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);
LTCExplorerClient = new ExplorerClient(NetworkProvider.GetNetwork<BTCPayNetwork>("LTC").NBXplorerNetwork, new Uri(GetEnvironment("TESTS_LTCNBXPLORERURL", "http://127.0.0.1:32838/")));
PayTester.Chains.Add("LTC");
PayTester.LTCNBXplorerUri = LTCExplorerClient.Address;
}
public void ActivateLBTC()
{
LBTCExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_LBTCRPCCONNECTION", "server=http://127.0.0.1:19332;liquid:liquid")), NetworkProvider.GetNetwork<BTCPayNetwork>("LBTC").NBitcoinNetwork);
LBTCExplorerClient = new ExplorerClient(NetworkProvider.GetNetwork<BTCPayNetwork>("LBTC").NBXplorerNetwork, new Uri(GetEnvironment("TESTS_LBTCNBXPLORERURL", "http://127.0.0.1:32838/")));
PayTester.Chains.Add("LBTC");
PayTester.LBTCNBXplorerUri = LBTCExplorerClient.Address;
}
public void ActivateLightning()
{
var btc = NetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBitcoinNetwork;
CustomerLightningD = LightningClientFactory.CreateClient(GetEnvironment("TEST_CUSTOMERLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30992/"), btc);
MerchantLightningD = LightningClientFactory.CreateClient(GetEnvironment("TEST_MERCHANTLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30993/"), btc);
MerchantCharge = new ChargeTester(this, "TEST_MERCHANTCHARGE", "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify", "merchant_lightningd", btc);
MerchantLnd = new LndMockTester(this, "TEST_MERCHANTLND", "https://lnd:lnd@127.0.0.1:53280/", "merchant_lnd", btc);
PayTester.UseLightning = true;
PayTester.IntegratedLightning = MerchantCharge.Client.Uri;
}
public bool Dockerized
@ -96,20 +76,18 @@ namespace BTCPayServer.Tests
get; set;
}
public Task StartAsync()
public void Start()
{
return PayTester.StartAsync();
PayTester.Start();
}
/// <summary>
/// Connect a customer LN node to the merchant LN node
/// </summary>
/// <returns></returns>
public async Task EnsureChannelsSetup()
public Task EnsureChannelsSetup()
{
Logs.Tester.LogInformation("Connecting channels");
await BTCPayServer.Lightning.Tests.ConnectChannels.ConnectAll(ExplorerNode, GetLightningSenderClients(), GetLightningDestClients()).ConfigureAwait(false);
Logs.Tester.LogInformation("Channels connected");
return BTCPayServer.Lightning.Tests.ConnectChannels.ConnectAll(ExplorerNode, GetLightningSenderClients(), GetLightningDestClients());
}
private IEnumerable<ILightningClient> GetLightningSenderClients()
@ -162,15 +140,12 @@ namespace BTCPayServer.Tests
{
get; set;
}
public RPCClient LBTCExplorerNode { get; set; }
public ExplorerClient ExplorerClient
{
get; set;
}
public ExplorerClient LTCExplorerClient { get; set; }
public ExplorerClient LBTCExplorerClient { get; set; }
HttpClient _Http = new HttpClient();
@ -183,14 +158,12 @@ namespace BTCPayServer.Tests
public void Dispose()
{
Logs.Tester.LogInformation("Disposing the BTCPayTester...");
foreach (var store in Stores)
{
Xunit.Assert.True(PayTester.StoreRepository.DeleteStore(store).GetAwaiter().GetResult());
}
if (PayTester != null)
PayTester.Dispose();
Logs.Tester.LogInformation("BTCPayTester disposed");
}
}
}

View File

@ -1,259 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using BTCPayServer.Controllers;
using BTCPayServer.Models;
using BTCPayServer.Models.ServerViewModels;
using BTCPayServer.Storage.Models;
using BTCPayServer.Storage.Services.Providers.AzureBlobStorage.Configuration;
using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration;
using BTCPayServer.Storage.ViewModels;
using BTCPayServer.Tests.Logging;
using DBriize.Utils;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities.Resources;
using Xunit;
using Xunit.Abstractions;
namespace BTCPayServer.Tests
{
public class StorageTests
{
public StorageTests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) {Name = "Tests"};
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact(Timeout = TestUtils.TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanConfigureStorage()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<ServerController>(user.UserId, user.StoreId);
//Once we select a provider, redirect to its view
var localResult = Assert
.IsType<RedirectToActionResult>(controller.Storage(new StorageSettings()
{
Provider = StorageProvider.FileSystem
}));
Assert.Equal(nameof(ServerController.StorageProvider), localResult.ActionName);
Assert.Equal(StorageProvider.FileSystem.ToString(), localResult.RouteValues["provider"]);
var AmazonS3result = Assert
.IsType<RedirectToActionResult>(controller.Storage(new StorageSettings()
{
Provider = StorageProvider.AmazonS3
}));
Assert.Equal(nameof(ServerController.StorageProvider), AmazonS3result.ActionName);
Assert.Equal(StorageProvider.AmazonS3.ToString(), AmazonS3result.RouteValues["provider"]);
var GoogleResult = Assert
.IsType<RedirectToActionResult>(controller.Storage(new StorageSettings()
{
Provider = StorageProvider.GoogleCloudStorage
}));
Assert.Equal(nameof(ServerController.StorageProvider), GoogleResult.ActionName);
Assert.Equal(StorageProvider.GoogleCloudStorage.ToString(), GoogleResult.RouteValues["provider"]);
var AzureResult = Assert
.IsType<RedirectToActionResult>(controller.Storage(new StorageSettings()
{
Provider = StorageProvider.AzureBlobStorage
}));
Assert.Equal(nameof(ServerController.StorageProvider), AzureResult.ActionName);
Assert.Equal(StorageProvider.AzureBlobStorage.ToString(), AzureResult.RouteValues["provider"]);
//Cool, we get redirected to the config pages
//Let's configure this stuff
//Let's try and cheat and go to an invalid storage provider config
Assert.Equal(nameof(Storage), (Assert
.IsType<RedirectToActionResult>(await controller.StorageProvider("I am not a real provider"))
.ActionName));
//ok no more messing around, let's configure this shit.
var fileSystemStorageConfiguration = Assert.IsType<FileSystemStorageConfiguration>(Assert
.IsType<ViewResult>(await controller.StorageProvider(StorageProvider.FileSystem.ToString()))
.Model);
//local file system does not need config, easy days!
Assert.IsType<ViewResult>(
await controller.EditFileSystemStorageProvider(fileSystemStorageConfiguration));
//ok cool, let's see if this got set right
var shouldBeRedirectingToLocalStorageConfigPage =
Assert.IsType<RedirectToActionResult>(await controller.Storage());
Assert.Equal(nameof(StorageProvider), shouldBeRedirectingToLocalStorageConfigPage.ActionName);
Assert.Equal(StorageProvider.FileSystem,
shouldBeRedirectingToLocalStorageConfigPage.RouteValues["provider"]);
//if we tell the settings page to force, it should allow us to select a new provider
Assert.IsType<ChooseStorageViewModel>(Assert.IsType<ViewResult>(await controller.Storage(true)).Model);
//awesome, now let's see if the files result says we're all set up
var viewFilesViewModel =
Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files()).Model);
Assert.True(viewFilesViewModel.StorageConfigured);
Assert.Empty(viewFilesViewModel.Files);
}
}
[Fact]
[Trait("Integration", "Integration")]
public async void CanUseLocalProviderFiles()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<ServerController>(user.UserId, user.StoreId);
var fileSystemStorageConfiguration = Assert.IsType<FileSystemStorageConfiguration>(Assert
.IsType<ViewResult>(await controller.StorageProvider(StorageProvider.FileSystem.ToString()))
.Model);
Assert.IsType<ViewResult>(
await controller.EditFileSystemStorageProvider(fileSystemStorageConfiguration));
var shouldBeRedirectingToLocalStorageConfigPage =
Assert.IsType<RedirectToActionResult>(await controller.Storage());
Assert.Equal(nameof(StorageProvider), shouldBeRedirectingToLocalStorageConfigPage.ActionName);
Assert.Equal(StorageProvider.FileSystem,
shouldBeRedirectingToLocalStorageConfigPage.RouteValues["provider"]);
await CanUploadRemoveFiles(controller);
}
}
[Fact(Timeout = TestUtils.TestTimeout)]
[Trait("ExternalIntegration", "ExternalIntegration")]
public async Task CanUseAzureBlobStorage()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<ServerController>(user.UserId, user.StoreId);
var azureBlobStorageConfiguration = Assert.IsType<AzureBlobStorageConfiguration>(Assert
.IsType<ViewResult>(await controller.StorageProvider(StorageProvider.AzureBlobStorage.ToString()))
.Model);
azureBlobStorageConfiguration.ConnectionString = GetFromSecrets("AzureBlobStorageConnectionString");
azureBlobStorageConfiguration.ContainerName = "testscontainer";
Assert.IsType<ViewResult>(
await controller.EditAzureBlobStorageStorageProvider(azureBlobStorageConfiguration));
var shouldBeRedirectingToAzureStorageConfigPage =
Assert.IsType<RedirectToActionResult>(await controller.Storage());
Assert.Equal(nameof(StorageProvider), shouldBeRedirectingToAzureStorageConfigPage.ActionName);
Assert.Equal(StorageProvider.AzureBlobStorage,
shouldBeRedirectingToAzureStorageConfigPage.RouteValues["provider"]);
//seems like azure config worked, let's see if the conn string was actually saved
Assert.Equal(azureBlobStorageConfiguration.ConnectionString, Assert
.IsType<AzureBlobStorageConfiguration>(Assert
.IsType<ViewResult>(
await controller.StorageProvider(StorageProvider.AzureBlobStorage.ToString()))
.Model).ConnectionString);
await CanUploadRemoveFiles(controller);
}
}
private async Task CanUploadRemoveFiles(ServerController controller)
{
var fileContent = "content";
var uploadFormFileResult = Assert.IsType<RedirectToActionResult>(await controller.CreateFile(TestUtils.GetFormFile("uploadtestfile.txt", fileContent)));
Assert.True(uploadFormFileResult.RouteValues.ContainsKey("fileId"));
var fileId = uploadFormFileResult.RouteValues["fileId"].ToString();
Assert.Equal("Files", uploadFormFileResult.ActionName);
//check if file was uploaded and saved in db
var viewFilesViewModel =
Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files(fileId)).Model);
Assert.NotEmpty(viewFilesViewModel.Files);
Assert.Equal(fileId, viewFilesViewModel.SelectedFileId);
Assert.NotEmpty(viewFilesViewModel.DirectFileUrl);
//verify file is available and the same
var net = new System.Net.WebClient();
var data = await net.DownloadStringTaskAsync(new Uri(viewFilesViewModel.DirectFileUrl));
Assert.Equal(fileContent, data);
//create a temporary link to file
var tmpLinkGenerate = Assert.IsType<RedirectToActionResult>(await controller.CreateTemporaryFileUrl(fileId,
new ServerController.CreateTemporaryFileUrlViewModel()
{
IsDownload = true,
TimeAmount = 1,
TimeType = ServerController.CreateTemporaryFileUrlViewModel.TmpFileTimeType.Minutes
}));
var statusMessageModel = controller.TempData.GetStatusMessageModel();
Assert.NotNull(statusMessageModel);
Assert.Equal(StatusMessageModel.StatusSeverity.Success, statusMessageModel.Severity);
var index = statusMessageModel.Html.IndexOf("target='_blank'>");
var url = statusMessageModel.Html.Substring(index).ReplaceMultiple(new Dictionary<string, string>()
{
{"</a>", string.Empty}, {"target='_blank'>", string.Empty}
});
//verify tmpfile is available and the same
data = await net.DownloadStringTaskAsync(new Uri(url));
Assert.Equal(fileContent, data);
//delete file
Assert.IsType<RedirectToActionResult>(await controller.DeleteFile(fileId));
controller.TempData.GetStatusMessageModel();
Assert.NotNull(statusMessageModel);
Assert.Equal(StatusMessageModel.StatusSeverity.Success, statusMessageModel.Severity);
//attempt to fetch deleted file
viewFilesViewModel =
Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files(fileId)).Model);
Assert.Null(viewFilesViewModel.DirectFileUrl);
Assert.Null(viewFilesViewModel.SelectedFileId);
}
private static string GetFromSecrets(string key)
{
var connStr = Environment.GetEnvironmentVariable($"TESTS_{key}");
if (!string.IsNullOrEmpty(connStr) && connStr != "none")
return connStr;
var builder = new ConfigurationBuilder();
builder.AddUserSecrets("AB0AC1DD-9D26-485B-9416-56A33F268117");
var config = builder.Build();
var token = config[key];
Assert.False(token == null, $"{key} is not set.\n Run \"dotnet user-secrets set {key} <value>\"");
return token;
}
}
}

View File

@ -18,9 +18,6 @@ using BTCPayServer.Tests.Logging;
using BTCPayServer.Lightning;
using BTCPayServer.Lightning.CLightning;
using BTCPayServer.Data;
using OpenIddict.Abstractions;
using OpenIddict.Core;
using Microsoft.AspNetCore.Identity;
namespace BTCPayServer.Tests
{
@ -38,13 +35,6 @@ namespace BTCPayServer.Tests
GrantAccessAsync().GetAwaiter().GetResult();
}
public async Task MakeAdmin()
{
var userManager = parent.PayTester.GetService<UserManager<ApplicationUser>>();
var u = await userManager.FindByIdAsync(UserId);
await userManager.AddToRoleAsync(u, Roles.ServerAdmin);
}
public void Register()
{
RegisterAsync().GetAwaiter().GetResult();
@ -86,8 +76,7 @@ namespace BTCPayServer.Tests
public T GetController<T>(bool setImplicitStore = true) where T : Controller
{
var controller = parent.PayTester.GetController<T>(UserId, setImplicitStore ? StoreId : null, IsAdmin);
return controller;
return parent.PayTester.GetController<T>(UserId, setImplicitStore ? StoreId : null);
}
public async Task CreateStoreAsync()
@ -106,10 +95,10 @@ namespace BTCPayServer.Tests
}
public async Task<WalletId> RegisterDerivationSchemeAsync(string cryptoCode, bool segwit = false)
{
SupportedNetwork = parent.NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
SupportedNetwork = parent.NetworkProvider.GetNetwork(cryptoCode);
var store = parent.PayTester.GetController<StoresController>(UserId, StoreId);
ExtKey = new ExtKey().GetWif(SupportedNetwork.NBitcoinNetwork);
DerivationScheme = SupportedNetwork.NBXplorerNetwork.DerivationStrategyFactory.Parse(ExtKey.Neuter().ToString() + (segwit ? "" : "-[legacy]"));
DerivationScheme = new DerivationStrategyFactory(SupportedNetwork.NBitcoinNetwork).Parse(ExtKey.Neuter().ToString() + (segwit ? "" : "-[legacy]"));
await store.AddDerivationScheme(StoreId, new DerivationSchemeViewModel()
{
DerivationScheme = DerivationScheme.ToString(),
@ -124,18 +113,15 @@ namespace BTCPayServer.Tests
private async Task RegisterAsync()
{
var account = parent.PayTester.GetController<AccountController>();
RegisterDetails = new RegisterViewModel()
await account.Register(new RegisterViewModel()
{
Email = Guid.NewGuid() + "@toto.com",
ConfirmPassword = "Kitten0@",
Password = "Kitten0@",
};
await account.Register(RegisterDetails);
});
UserId = account.RegisteredUserId;
}
public RegisterViewModel RegisterDetails{ get; set; }
public Bitpay BitPay
{
get; set;
@ -149,7 +135,6 @@ namespace BTCPayServer.Tests
{
get; set;
}
public bool IsAdmin { get; internal set; }
public void RegisterLightningNode(string cryptoCode, LightningConnectionType connectionType)
{
@ -178,14 +163,5 @@ namespace BTCPayServer.Tests
if (storeController.ModelState.ErrorCount != 0)
Assert.False(true, storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
}
public async Task<BTCPayOpenIdClient> RegisterOpenIdClient(OpenIddictApplicationDescriptor descriptor, string secret = null)
{
var openIddictApplicationManager = parent.PayTester.GetService<OpenIddictApplicationManager<BTCPayOpenIdClient>>();
var client = new BTCPayOpenIdClient { Id = Guid.NewGuid().ToString(), ApplicationUserId = UserId};
await openIddictApplicationManager.PopulateAsync(client, descriptor);
await openIddictApplicationManager.CreateAsync(client, secret);
return client;
}
}
}

View File

@ -1 +0,0 @@
{"wallet_password":"hellorockstar", "cipher_seed_mnemonic":["about","over","million","coast","nature","gold","fabric","aware","adult","salute","sock","envelope","oblige","shed","basic","need","analyst","spot","worth","garbage","planet","genre","leave","cereal"]}

View File

@ -1 +0,0 @@
rhrk7ncuqfcbkt7deaysn7st32hrf2tzztoumkj2vp3cqzy2d2arvdad.onion

View File

@ -1 +0,0 @@
r32tqfpchpiptrfltyd4ku7q3ir4yidyfe4em63iz2g5wjcauyvyj4id.onion

View File

@ -1 +0,0 @@
wsaxew3qa5ljfuenfebmaf3m5ykgatct3p6zjrqwoouj3foererde3id.onion

View File

@ -1 +0,0 @@
wzaq2ek6a5g6jyhr7giqsz2gbr6qdtcuwywcyvfplvrk7ugup3virvad.onion

View File

@ -1 +0,0 @@
jzebv74oaugje6aeykzcypzxs6pvt27wdmrcpe4pkjfprxgem2vyi7qd.onion

View File

@ -1 +0,0 @@
aeyn6mou5himgmog4tlnbjlgx5bwtwsqttd7sn7hay6lksvlfwmtkqyd.onion

View File

@ -1 +0,0 @@
uewlcjmuuwcgs43lpcc7d64k2pf4xs7yyer7ztsq6cblyjxue3bc35qd.onion

View File

@ -1 +0,0 @@
4iz3q7dp5xn5isqxsslrvbjrltqoqqocq25zmjgzvvxm5yp57jj4buid.onion

View File

@ -1 +0,0 @@
ii3kqeayarsxr2jqkmr2l5np2f4vn6lznk55upd3udfbfskbjdzoq4yd.onion

View File

@ -1,24 +0,0 @@
# For the hidden service BTC-P2P
HiddenServiceDir hidden_services/BTC-P2P
# Redirecting to btcpayserver_bitcoind
HiddenServicePort 8333 172.19.0.2:39388
# For the hidden service BTC-RPC
HiddenServiceDir hidden_services/BTC-RPC
# Redirecting to btcpayserver_bitcoind
HiddenServicePort 8332 172.19.0.2:43782
# For the hidden service BTCPayServer
HiddenServiceDir hidden_services/BTCPayServer
# Redirecting to nginx
HiddenServicePort 80 172.19.0.13:80
# For the hidden service BTCTransmuter
HiddenServiceDir hidden_services/BTCTransmuter
# Redirecting to nginx
HiddenServicePort 80 172.19.0.13:80
# For the hidden service WooCommerce
HiddenServiceDir hidden_services/WooCommerce
# Redirecting to generated_woocommerce_1
HiddenServicePort 80 172.19.0.15:80
# For the hidden service c-lightning
HiddenServiceDir hidden_services/c-lightning
# Redirecting to btcpayserver_clightning_bitcoin
HiddenServicePort 9735 172.19.0.10:9735

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