Compare commits

..

1 Commits

Author SHA1 Message Date
437daff63e Enable SSL was ignored 2018-05-22 18:05:44 +09:00
942 changed files with 29732 additions and 116953 deletions

View File

@ -1,147 +0,0 @@
version: 2
jobs:
fast_tests:
machine:
enabled: 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
# publish jobs require $DOCKERHUB_REPO, $DOCKERHUB_USER, $DOCKERHUB_PASS defined
amd64:
machine:
enabled: 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 login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-amd64
arm32v7:
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-arm32v7 -f arm32v7.Dockerfile .
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:
machine:
enabled: true
image: circleci/classic:201808-01
steps:
- run:
command: |
# Turn on Experimental features
sudo mkdir $HOME/.docker
sudo sh -c 'echo "{ \"experimental\": \"enabled\" }" >> $HOME/.docker/config.json'
#
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 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
publish:
jobs:
- amd64:
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
tags:
only: /(v[1-9]+(\.[0-9]+)*)|(v[a-z]+)/
- arm32v7:
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:
requires:
- amd64
- arm32v7
- arm64v8
filters:
branches:
ignore: /.*/
tags:
only: /(v[1-9]+(\.[0-9]+)*)|(v[a-z]+)/

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.

1
.gitignore vendored
View File

@ -293,4 +293,3 @@ BTCPayServer/wwwroot/bundles/*
!BTCPayServer/wwwroot/bundles/.gitignore
.vscode
BTCPayServer/testpwd

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,33 +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 InitBitcoinplus()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("XBC");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Bitcoinplus",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://chainz.cryptoid.info/xbc/tx.dws?{0}" : "https://chainz.cryptoid.info/xbc/tx.dws?{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "bitcoinplus",
DefaultRateRules = new[]
{
"XBC_X = XBC_BTC * BTC_X",
"XBC_BTC = cryptopia(XBC_BTC)"
},
CryptoImagePath = "imlegacy/bitcoinplus.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("65'") : new KeyPath("1'")
});
}
}
}

View File

@ -1,34 +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 InitBitcore()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("BTX");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Bitcore",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://insight.bitcore.cc/tx/{0}" : "https://insight.bitcore.cc/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "bitcore",
DefaultRateRules = new[]
{
"BTX_X = BTX_BTC * BTC_X",
"BTX_BTC = hitbtc(BTX_BTC)"
},
CryptoImagePath = "imlegacy/bitcore.svg",
LightningImagePath = "imlegacy/bitcore-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("160'") : new KeyPath("1'")
});
}
}
}

View File

@ -1,33 +0,0 @@
using NBitcoin;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitDash()
{
//not needed: NBitcoin.Altcoins.Dash.Instance.EnsureRegistered();
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("DASH");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Dash",
BlockExplorerLink = NetworkType == NetworkType.Mainnet
? "https://insight.dash.org/insight/tx/{0}"
: "https://testnet-insight.dashevo.org/insight/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "dash",
DefaultRateRules = new[]
{
"DASH_X = DASH_BTC * BTC_X",
"DASH_BTC = bittrex(DASH_BTC)"
},
CryptoImagePath = "imlegacy/dash.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
//https://github.com/satoshilabs/slips/blob/master/slip-0044.md
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("5'")
: new KeyPath("1'")
});
}
}
}

View File

@ -1,35 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitGroestlcoin()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("GRS");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Groestlcoin",
BlockExplorerLink = NetworkType == NetworkType.Mainnet
? "https://chainz.cryptoid.info/grs/tx.dws?{0}.htm"
: "https://chainz.cryptoid.info/grs-test/tx.dws?{0}.htm",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "groestlcoin",
DefaultRateRules = new[]
{
"GRS_X = GRS_BTC * BTC_X",
"GRS_BTC = bittrex(GRS_BTC)"
},
CryptoImagePath = "imlegacy/groestlcoin.png",
LightningImagePath = "imlegacy/groestlcoin-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("17'") : new KeyPath("1'")
});
}
}
}

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,33 +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 InitPolis()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("POLIS");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Polis",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockbook.polispay.org/tx/{0}" : "https://blockbook.polispay.org/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "polis",
DefaultRateRules = new[]
{
"POLIS_X = POLIS_BTC * BTC_X",
"POLIS_BTC = polispay(POLIS_BTC)"
},
CryptoImagePath = "imlegacy/polis.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1997'") : new KeyPath("1'")
});
}
}
}

View File

@ -1,33 +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 InitViacoin()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("VIA");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Viacoin",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://explorer.viacoin.org/tx/{0}" : "https://explorer.viacoin.org/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "viacoin",
DefaultRateRules = new[]
{
"VIA_X = VIA_BTC * BTC_X",
"VIA_BTC = bittrex(VIA_BTC)"
},
CryptoImagePath = "imlegacy/viacoin.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("14'") : new KeyPath("1'")
});
}
}
}

View File

@ -1,134 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
Dictionary<string, BTCPayNetworkBase> _Networks = new Dictionary<string, BTCPayNetworkBase>();
private readonly NBXplorerNetworkProvider _NBXplorerNetworkProvider;
public NBXplorerNetworkProvider NBXplorerNetworkProvider
{
get
{
return _NBXplorerNetworkProvider;
}
}
BTCPayNetworkProvider(BTCPayNetworkProvider unfiltered, string[] cryptoCodes)
{
UnfilteredNetworks = unfiltered.UnfilteredNetworks ?? unfiltered;
NetworkType = unfiltered.NetworkType;
_NBXplorerNetworkProvider = new NBXplorerNetworkProvider(unfiltered.NetworkType);
_Networks = new Dictionary<string, BTCPayNetworkBase>();
cryptoCodes = cryptoCodes.Select(c => c.ToUpperInvariant()).ToArray();
foreach (var network in unfiltered._Networks)
{
if(cryptoCodes.Contains(network.Key))
{
_Networks.Add(network.Key, network.Value);
}
}
}
public BTCPayNetworkProvider UnfilteredNetworks { get; }
public NetworkType NetworkType { get; private set; }
public BTCPayNetworkProvider(NetworkType networkType)
{
UnfilteredNetworks = this;
_NBXplorerNetworkProvider = new NBXplorerNetworkProvider(networkType);
NetworkType = networkType;
InitBitcoin();
InitLiquid();
InitLiquidAssets();
InitLitecoin();
InitBitcore();
InitDogecoin();
InitBitcoinGold();
InitMonacoin();
InitDash();
InitFeathercoin();
InitGroestlcoin();
InitViacoin();
InitMonero();
InitPolis();
// Assume that electrum mappings are same as BTC if not specified
foreach (var network in _Networks.Values.OfType<BTCPayNetwork>())
{
if(network.ElectrumMapping.Count == 0)
{
network.ElectrumMapping = GetNetwork<BTCPayNetwork>("BTC").ElectrumMapping;
if (!network.NBitcoinNetwork.Consensus.SupportSegwit)
{
network.ElectrumMapping =
network.ElectrumMapping
.Where(kv => kv.Value == DerivationType.Legacy)
.ToDictionary(k => k.Key, k => k.Value);
}
}
}
// Disabled because of https://twitter.com/Cryptopia_NZ/status/1085084168852291586
//InitBitcoinplus();
//InitUfo();
}
/// <summary>
/// Keep only the specified crypto
/// </summary>
/// <param name="cryptoCodes">Crypto to support</param>
/// <returns></returns>
public BTCPayNetworkProvider Filter(string[] cryptoCodes)
{
return new BTCPayNetworkProvider(this, cryptoCodes);
}
[Obsolete("To use only for legacy stuff")]
public BTCPayNetwork BTC => GetNetwork<BTCPayNetwork>("BTC");
public void Add(BTCPayNetwork network)
{
if (network.NBitcoinNetwork == null)
return;
Add(network as BTCPayNetworkBase);
}
public void Add(BTCPayNetworkBase network)
{
_Networks.Add(network.CryptoCode.ToUpperInvariant(), network);
}
public IEnumerable<BTCPayNetworkBase> GetAll()
{
return _Networks.Values.ToArray();
}
public bool Support(string cryptoCode)
{
return _Networks.ContainsKey(cryptoCode.ToUpperInvariant());
}
public BTCPayNetworkBase GetNetwork(string cryptoCode)
{
return GetNetwork<BTCPayNetworkBase>(cryptoCode.ToUpperInvariant());
}
public T GetNetwork<T>(string cryptoCode) where T: BTCPayNetworkBase
{
if (cryptoCode == null)
throw new ArgumentNullException(nameof(cryptoCode));
if(!_Networks.TryGetValue(cryptoCode.ToUpperInvariant(), out BTCPayNetworkBase network))
{
if (cryptoCode == "XBT")
return GetNetwork<T>("BTC");
}
return network as T;
}
}
}

View File

@ -1,40 +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
});
}
}
}

View File

@ -1,58 +0,0 @@
using NBitcoin;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitLiquidAssets()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("LBTC");
Add(new ElementsBTCPayNetwork()
{
CryptoCode = "USDt",
NetworkCryptoCode = "LBTC",
ShowSyncSummary = false,
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
});
Add(new ElementsBTCPayNetwork()
{
CryptoCode = "ETB",
NetworkCryptoCode = "LBTC",
ShowSyncSummary = false,
DefaultRateRules = new[]
{
"ETB_X = ETB_BTC * BTC_X",
"ETB_BTC = bitpay(ETB_BTC)"
},
Divisibility = 2,
AssetId = new uint256("aa775044c32a7df391902b3659f46dfe004ccb2644ce2ddc7dba31e889391caf"),
DisplayName = "Ethiopian Birr",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "liquidnetwork",
CryptoImagePath = "imlegacy/etb.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
SupportRBF = true
});
}
}
}

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 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, Money cryptoInfoDue)
{
return $"{base.GenerateBIP21(cryptoInfoAddress, cryptoInfoDue)}&assetid={AssetId}";
}
}
}

View File

@ -1,27 +0,0 @@
using NBitcoin;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitMonero()
{
Add(new MoneroLikeSpecificBtcPayNetwork()
{
CryptoCode = "XMR",
DisplayName = "Monero",
Divisibility = 12,
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,155 +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, Money cryptoInfoDue)
{
return $"{UriScheme}:{cryptoInfoAddress}?amount={cryptoInfoDue.ToString(false, true)}";
}
}
public abstract class BTCPayNetworkBase
{
public bool ShowSyncSummary { get; set; } = true;
public string CryptoCode { get; internal set; }
public string BlockExplorerLink { get; internal set; }
public string DisplayName { get; set; }
public int Divisibility { get; set; } = 8;
[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,13 +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.Design" Version="3.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.1" />
<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.1" />
</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,283 +0,0 @@
using System;
using System.Linq;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Infrastructure;
using OpenIddict.EntityFrameworkCore.Models;
namespace BTCPayServer.Data
{
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
public ApplicationDbContext CreateDbContext(string[] args)
{
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
builder.UseSqlite("Data Source=temp.db");
return new ApplicationDbContext(builder.Options, true);
}
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
private readonly bool _designTime;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, bool designTime = false)
: base(options)
{
_designTime = designTime;
}
public DbSet<InvoiceData> Invoices
{
get; set;
}
public DbSet<AppData> Apps
{
get; set;
}
public DbSet<InvoiceEventData> InvoiceEvents
{
get; set;
}
public DbSet<HistoricalAddressInvoiceData> HistoricalAddressInvoices
{
get; set;
}
public DbSet<PendingInvoiceData> PendingInvoices
{
get; set;
}
public DbSet<RefundAddressesData> RefundAddresses
{
get; set;
}
public DbSet<PaymentData> Payments
{
get; set;
}
public DbSet<PaymentRequestData> PaymentRequests
{
get; set;
}
public DbSet<WalletData> Wallets { get; set; }
public DbSet<WalletTransactionData> WalletTransactions { get; set; }
public DbSet<StoreData> Stores
{
get; set;
}
public DbSet<UserStore> UserStore
{
get; set;
}
public DbSet<AddressInvoiceData> AddressInvoices
{
get; set;
}
public DbSet<SettingData> Settings
{
get; set;
}
public DbSet<PairingCodeData> PairingCodes
{
get; set;
}
public DbSet<PairedSINData> PairedSINData
{
get; set;
}
public DbSet<APIKeyData> ApiKeys
{
get; set;
}
public DbSet<StoredFile> Files
{
get; set;
}
public DbSet<U2FDevice> U2FDevices { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var isConfigured = optionsBuilder.Options.Extensions.OfType<RelationalOptionsExtension>().Any();
if (!isConfigured)
optionsBuilder.UseSqlite("Data Source=temp.db");
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<InvoiceData>()
.HasOne(o => o.StoreData)
.WithMany(a => a.Invoices).OnDelete(DeleteBehavior.Cascade);
builder.Entity<InvoiceData>().HasIndex(o => o.StoreDataId);
builder.Entity<PaymentData>()
.HasOne(o => o.InvoiceData)
.WithMany(i => i.Payments).OnDelete(DeleteBehavior.Cascade);
builder.Entity<PaymentData>()
.HasIndex(o => o.InvoiceDataId);
builder.Entity<RefundAddressesData>()
.HasOne(o => o.InvoiceData)
.WithMany(i => i.RefundAddresses).OnDelete(DeleteBehavior.Cascade);
builder.Entity<RefundAddressesData>()
.HasIndex(o => o.InvoiceDataId);
builder.Entity<UserStore>()
.HasOne(o => o.StoreData)
.WithMany(i => i.UserStores).OnDelete(DeleteBehavior.Cascade);
builder.Entity<UserStore>()
.HasKey(t => new
{
t.ApplicationUserId,
t.StoreDataId
});
builder.Entity<APIKeyData>()
.HasOne(o => o.StoreData)
.WithMany(i => i.APIKeys)
.HasForeignKey(i => i.StoreId).OnDelete(DeleteBehavior.Cascade);
builder.Entity<APIKeyData>()
.HasIndex(o => o.StoreId);
builder.Entity<AppData>()
.HasOne(o => o.StoreData)
.WithMany(i => i.Apps).OnDelete(DeleteBehavior.Cascade);
builder.Entity<AppData>()
.HasOne(a => a.StoreData);
builder.Entity<UserStore>()
.HasOne(pt => pt.ApplicationUser)
.WithMany(p => p.UserStores)
.HasForeignKey(pt => pt.ApplicationUserId);
builder.Entity<UserStore>()
.HasOne(pt => pt.StoreData)
.WithMany(t => t.UserStores)
.HasForeignKey(pt => pt.StoreDataId);
builder.Entity<AddressInvoiceData>()
.HasOne(o => o.InvoiceData)
.WithMany(i => i.AddressInvoices).OnDelete(DeleteBehavior.Cascade);
builder.Entity<AddressInvoiceData>()
#pragma warning disable CS0618
.HasKey(o => o.Address);
#pragma warning restore CS0618
builder.Entity<PairingCodeData>()
.HasKey(o => o.Id);
builder.Entity<PendingInvoiceData>()
.HasOne(o => o.InvoiceData)
.WithMany(o => o.PendingInvoices)
.HasForeignKey(o => o.Id).OnDelete(DeleteBehavior.Cascade);
builder.Entity<PairedSINData>()
.HasOne(o => o.StoreData)
.WithMany(i => i.PairedSINs).OnDelete(DeleteBehavior.Cascade);
builder.Entity<PairedSINData>(b =>
{
b.HasIndex(o => o.SIN);
b.HasIndex(o => o.StoreDataId);
});
builder.Entity<HistoricalAddressInvoiceData>()
.HasOne(o => o.InvoiceData)
.WithMany(i => i.HistoricalAddressInvoices).OnDelete(DeleteBehavior.Cascade);
builder.Entity<HistoricalAddressInvoiceData>()
.HasKey(o => new
{
o.InvoiceDataId,
#pragma warning disable CS0618
o.Address
#pragma warning restore CS0618
});
builder.Entity<InvoiceEventData>()
.HasOne(o => o.InvoiceData)
.WithMany(i => i.Events).OnDelete(DeleteBehavior.Cascade);
builder.Entity<InvoiceEventData>()
.HasKey(o => new
{
o.InvoiceDataId,
#pragma warning disable CS0618
o.UniqueId
#pragma warning restore CS0618
});
builder.Entity<PaymentRequestData>()
.HasOne(o => o.StoreData)
.WithMany(i => i.PaymentRequests)
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<PaymentRequestData>()
.Property(e => e.Created)
.HasDefaultValue(new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero));
builder.Entity<PaymentRequestData>()
.HasIndex(o => o.Status);
builder.Entity<WalletTransactionData>()
.HasKey(o => new
{
o.WalletDataId,
#pragma warning disable CS0618
o.TransactionId
#pragma warning restore CS0618
});
builder.Entity<WalletTransactionData>()
.HasOne(o => o.WalletData)
.WithMany(w => w.WalletTransactions).OnDelete(DeleteBehavior.Cascade);
builder.UseOpenIddict<BTCPayOpenIdClient, BTCPayOpenIdAuthorization, OpenIddictScope<string>, BTCPayOpenIdToken, string>();
if (Database.IsSqlite() && !_designTime)
{
// SQLite does not have proper support for DateTimeOffset via Entity Framework Core, see the limitations
// here: https://docs.microsoft.com/en-us/ef/core/providers/sqlite/limitations#query-limitations
// To work around this, when the Sqlite database provider is used, all model properties of type DateTimeOffset
// use the DateTimeOffsetToBinaryConverter
// Based on: https://github.com/aspnet/EntityFrameworkCore/issues/10784#issuecomment-415769754
// This only supports millisecond precision, but should be sufficient for most use cases.
foreach (var entityType in builder.Model.GetEntityTypes())
{
var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == typeof(DateTimeOffset));
foreach (var property in properties)
{
builder
.Entity(entityType.Name)
.Property(property.Name)
.HasConversion(new Microsoft.EntityFrameworkCore.Storage.ValueConversion.DateTimeOffsetToBinaryConverter());
}
}
}
}
}
}

View File

@ -1,100 +0,0 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations;
using JetBrains.Annotations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Metadata;
namespace BTCPayServer.Data
{
public enum DatabaseType
{
Sqlite,
Postgres,
MySQL,
}
public class ApplicationDbContextFactory
{
string _ConnectionString;
DatabaseType _Type;
public ApplicationDbContextFactory(DatabaseType type, string connectionString)
{
_ConnectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
_Type = type;
}
public DatabaseType Type
{
get
{
return _Type;
}
}
public ApplicationDbContext CreateContext()
{
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
ConfigureBuilder(builder);
return new ApplicationDbContext(builder.Options);
}
class CustomNpgsqlMigrationsSqlGenerator : NpgsqlMigrationsSqlGenerator
{
public CustomNpgsqlMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies, IMigrationsAnnotationProvider annotations, Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal.INpgsqlOptions opts) : base(dependencies, annotations, opts)
{
}
protected override void Generate(NpgsqlCreateDatabaseOperation operation, IModel model, MigrationCommandListBuilder builder)
{
builder
.Append("CREATE DATABASE ")
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name));
// POSTGRES gotcha: Indexed Text column (even if PK) are not used if we are not using C locale
builder
.Append(" TEMPLATE ")
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("template0"));
builder
.Append(" LC_CTYPE ")
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("C"));
builder
.Append(" LC_COLLATE ")
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("C"));
builder
.Append(" ENCODING ")
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier("UTF8"));
if (operation.Tablespace != null)
{
builder
.Append(" TABLESPACE ")
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Tablespace));
}
builder.AppendLine(Dependencies.SqlGenerationHelper.StatementTerminator);
EndStatement(builder, suppressTransaction: true);
}
}
public void ConfigureBuilder(DbContextOptionsBuilder builder)
{
if (_Type == DatabaseType.Sqlite)
builder.UseSqlite(_ConnectionString, o => o.MigrationsAssembly("BTCPayServer.Data"));
else if (_Type == DatabaseType.Postgres)
builder
.UseNpgsql(_ConnectionString, o => o.MigrationsAssembly("BTCPayServer.Data").EnableRetryOnFailure(10))
.ReplaceService<IMigrationsSqlGenerator, CustomNpgsqlMigrationsSqlGenerator>();
else if (_Type == DatabaseType.MySQL)
builder.UseMySql(_ConnectionString, o => o.MigrationsAssembly("BTCPayServer.Data").EnableRetryOnFailure(10));
}
}
}

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,177 +0,0 @@
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20180719095626_CanDeleteStores")]
public partial class CanDeleteStores : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
if (this.SupportDropForeignKey(migrationBuilder.ActiveProvider))
{
migrationBuilder.DropForeignKey(
name: "FK_AddressInvoices_Invoices_InvoiceDataId",
table: "AddressInvoices");
migrationBuilder.DropForeignKey(
name: "FK_Apps_Stores_StoreDataId",
table: "Apps");
migrationBuilder.DropForeignKey(
name: "FK_Invoices_Stores_StoreDataId",
table: "Invoices");
migrationBuilder.DropForeignKey(
name: "FK_Payments_Invoices_InvoiceDataId",
table: "Payments");
migrationBuilder.DropForeignKey(
name: "FK_RefundAddresses_Invoices_InvoiceDataId",
table: "RefundAddresses");
migrationBuilder.AddForeignKey(
name: "FK_AddressInvoices_Invoices_InvoiceDataId",
table: "AddressInvoices",
column: "InvoiceDataId",
principalTable: "Invoices",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_ApiKeys_Stores_StoreId",
table: "ApiKeys",
column: "StoreId",
principalTable: "Stores",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Apps_Stores_StoreDataId",
table: "Apps",
column: "StoreDataId",
principalTable: "Stores",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Invoices_Stores_StoreDataId",
table: "Invoices",
column: "StoreDataId",
principalTable: "Stores",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_PairedSINData_Stores_StoreDataId",
table: "PairedSINData",
column: "StoreDataId",
principalTable: "Stores",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_Payments_Invoices_InvoiceDataId",
table: "Payments",
column: "InvoiceDataId",
principalTable: "Invoices",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_PendingInvoices_Invoices_Id",
table: "PendingInvoices",
column: "Id",
principalTable: "Invoices",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_RefundAddresses_Invoices_InvoiceDataId",
table: "RefundAddresses",
column: "InvoiceDataId",
principalTable: "Invoices",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
}
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_AddressInvoices_Invoices_InvoiceDataId",
table: "AddressInvoices");
migrationBuilder.DropForeignKey(
name: "FK_ApiKeys_Stores_StoreId",
table: "ApiKeys");
migrationBuilder.DropForeignKey(
name: "FK_Apps_Stores_StoreDataId",
table: "Apps");
migrationBuilder.DropForeignKey(
name: "FK_Invoices_Stores_StoreDataId",
table: "Invoices");
migrationBuilder.DropForeignKey(
name: "FK_PairedSINData_Stores_StoreDataId",
table: "PairedSINData");
migrationBuilder.DropForeignKey(
name: "FK_Payments_Invoices_InvoiceDataId",
table: "Payments");
migrationBuilder.DropForeignKey(
name: "FK_PendingInvoices_Invoices_Id",
table: "PendingInvoices");
migrationBuilder.DropForeignKey(
name: "FK_RefundAddresses_Invoices_InvoiceDataId",
table: "RefundAddresses");
migrationBuilder.AddForeignKey(
name: "FK_AddressInvoices_Invoices_InvoiceDataId",
table: "AddressInvoices",
column: "InvoiceDataId",
principalTable: "Invoices",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
migrationBuilder.AddForeignKey(
name: "FK_Apps_Stores_StoreDataId",
table: "Apps",
column: "StoreDataId",
principalTable: "Stores",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
migrationBuilder.AddForeignKey(
name: "FK_Invoices_Stores_StoreDataId",
table: "Invoices",
column: "StoreDataId",
principalTable: "Stores",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
migrationBuilder.AddForeignKey(
name: "FK_Payments_Invoices_InvoiceDataId",
table: "Payments",
column: "InvoiceDataId",
principalTable: "Invoices",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
migrationBuilder.AddForeignKey(
name: "FK_RefundAddresses_Invoices_InvoiceDataId",
table: "RefundAddresses",
column: "InvoiceDataId",
principalTable: "Invoices",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
}
}

View File

@ -1,52 +0,0 @@
using System;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20190121133309_AddPaymentRequests")]
public partial class AddPaymentRequests : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
migrationBuilder.CreateTable(
name: "PaymentRequests",
columns: table => new
{
Id = table.Column<string>(nullable: false, maxLength: maxLength),
StoreDataId = table.Column<string>(nullable: true, maxLength: maxLength),
Status = table.Column<int>(nullable: false),
Blob = table.Column<byte[]>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_PaymentRequests", x => x.Id);
table.ForeignKey(
name: "FK_PaymentRequests_Stores_StoreDataId",
column: x => x.StoreDataId,
principalTable: "Stores",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_PaymentRequests_Status",
table: "PaymentRequests",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_PaymentRequests_StoreDataId",
table: "PaymentRequests",
column: "StoreDataId");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "PaymentRequests");
}
}
}

View File

@ -1,28 +0,0 @@
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20190219032533_AppsTagging")]
public partial class AppsTagging : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
migrationBuilder.AddColumn<bool>(
name: "TagAllInvoices",
table: "Apps",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "TagAllInvoices",
table: "Apps");
}
}
}

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,17 +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.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,272 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace BTCPayServer.Rating
{
public class ExchangeRates : IEnumerable<ExchangeRate>
{
Dictionary<string, ExchangeRate> _AllRates = new Dictionary<string, ExchangeRate>();
public ExchangeRates()
{
}
public ExchangeRates(string exchangeName, IEnumerable<PairRate> rates)
{
foreach (var rate in rates)
{
Add(new ExchangeRate(exchangeName, rate.CurrencyPair, rate.BidAsk));
}
}
public ExchangeRates(IEnumerable<ExchangeRate> rates)
{
foreach (var rate in rates)
{
Add(rate);
}
}
List<ExchangeRate> _Rates = new List<ExchangeRate>();
public MultiValueDictionary<string, ExchangeRate> ByExchange
{
get;
private set;
} = new MultiValueDictionary<string, ExchangeRate>();
public void Add(ExchangeRate rate)
{
// 1 DOGE is always 1 DOGE
if (rate.CurrencyPair.Left == rate.CurrencyPair.Right)
return;
var key = $"({rate.Exchange}) {rate.CurrencyPair}";
if (_AllRates.TryAdd(key, rate))
{
_Rates.Add(rate);
ByExchange.Add(rate.Exchange, rate);
}
else
{
if (rate.BidAsk != null)
{
_AllRates[key].BidAsk = rate.BidAsk;
}
}
}
public IEnumerator<ExchangeRate> GetEnumerator()
{
return _Rates.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public void SetRate(string exchangeName, CurrencyPair currencyPair, BidAsk bidAsk)
{
if (ByExchange.TryGetValue(exchangeName, out var rates))
{
var rate = rates.FirstOrDefault(r => r.CurrencyPair == currencyPair);
if(rate != null)
{
rate.BidAsk = bidAsk;
}
var invPair = currencyPair.Inverse();
var invRate = rates.FirstOrDefault(r => r.CurrencyPair == invPair);
if (invRate != null)
{
invRate.BidAsk = bidAsk?.Inverse();
}
}
}
public BidAsk GetRate(string exchangeName, CurrencyPair currencyPair)
{
if (currencyPair.Left == currencyPair.Right)
return BidAsk.One;
if (ByExchange.TryGetValue(exchangeName, out var rates))
{
var rate = rates.FirstOrDefault(r => r.CurrencyPair == currencyPair);
if (rate != null)
return rate.BidAsk;
}
return null;
}
}
public class BidAsk
{
private readonly static BidAsk _One = new BidAsk(1.0m);
public static BidAsk One
{
get
{
return _One;
}
}
private readonly static BidAsk _Zero = new BidAsk(0.0m);
public static BidAsk Zero
{
get
{
return _Zero;
}
}
public BidAsk(decimal bid, decimal ask)
{
if (bid > ask)
throw new ArgumentException("the bid should be lower than ask", nameof(bid));
_Ask = ask;
_Bid = bid;
}
public BidAsk(decimal v) : this(v, v)
{
}
private readonly decimal _Bid;
public decimal Bid
{
get
{
return _Bid;
}
}
private readonly decimal _Ask;
public decimal Ask
{
get
{
return _Ask;
}
}
public decimal Center => (Ask + Bid) / 2.0m;
public BidAsk Inverse()
{
return new BidAsk(1.0m / Ask, 1.0m / Bid);
}
public static BidAsk operator +(BidAsk a, BidAsk b)
{
return new BidAsk(a.Bid + b.Bid, a.Ask + b.Ask);
}
public static BidAsk operator +(BidAsk a)
{
return new BidAsk(a.Bid, a.Ask);
}
public static BidAsk operator -(BidAsk a)
{
return new BidAsk(-a.Bid, -a.Ask);
}
public static BidAsk operator *(BidAsk a, BidAsk b)
{
return new BidAsk(a.Bid * b.Bid, a.Ask * b.Ask);
}
public static BidAsk operator /(BidAsk a, BidAsk b)
{
// This one is tricky.
// BTC_EUR = (6000, 6100)
// Implicit rule give
// EUR_BTC = 1 / BTC_EUR
// Or
// EUR_BTC = (1, 1) / BTC_EUR
// Naive calculation would give us ( 1/6000, 1/6100) = (0.000166, 0.000163)
// However, this is an invalid BidAsk!!! because 0.000166 > 0.000163
// So instead, we need to calculate (1/6100, 1/6000)
return new BidAsk(a.Bid / b.Ask, a.Ask / b.Bid);
}
public static BidAsk operator -(BidAsk a, BidAsk b)
{
return new BidAsk(a.Bid - b.Bid, a.Ask - b.Ask);
}
public override bool Equals(object obj)
{
BidAsk item = obj as BidAsk;
if (item == null)
return false;
return Bid == item.Bid && Ask == item.Ask;
}
public static bool operator ==(BidAsk a, BidAsk b)
{
if (System.Object.ReferenceEquals(a, b))
return true;
if (((object)a == null) || ((object)b == null))
return false;
return a.Bid == b.Bid && a.Ask == b.Ask;
}
public static bool operator !=(BidAsk a, BidAsk b)
{
return !(a == b);
}
public override int GetHashCode()
{
return ToString().GetHashCode(StringComparison.InvariantCulture);
}
public override string ToString()
{
if (Bid == Ask)
return Bid.ToString(CultureInfo.InvariantCulture);
return $"({Bid.ToString(CultureInfo.InvariantCulture)} , {Ask.ToString(CultureInfo.InvariantCulture)})";
}
}
public class PairRate
{
public PairRate(CurrencyPair currencyPair, BidAsk bidAsk)
{
if (currencyPair == null)
throw new ArgumentNullException(nameof(currencyPair));
if (bidAsk == null)
throw new ArgumentNullException(nameof(bidAsk));
this.CurrencyPair = currencyPair;
this.BidAsk = bidAsk;
}
public CurrencyPair CurrencyPair { get; }
public BidAsk BidAsk { get; }
public override string ToString()
{
return $"{CurrencyPair} == {BidAsk}";
}
}
public class ExchangeRate
{
public ExchangeRate()
{
}
public ExchangeRate(string exchange, CurrencyPair currencyPair, BidAsk bidAsk)
{
this.Exchange = exchange;
this.CurrencyPair = currencyPair;
this.BidAsk = bidAsk;
}
public string Exchange { get; set; }
public CurrencyPair CurrencyPair { get; set; }
public BidAsk BidAsk { get; set; }
public override string ToString()
{
if (BidAsk == null)
return $"{Exchange}({CurrencyPair})";
return $"{Exchange}({CurrencyPair}) == {BidAsk.ToString()}";
}
}
}

View File

@ -1,269 +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(IRateProvider inner)
{
if (inner == null)
throw new ArgumentNullException(nameof(inner));
_Inner = inner;
}
public BackgroundFetcherState GetState()
{
var state = new BackgroundFetcherState()
{
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 (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 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,75 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Rating;
using ExchangeSharp;
namespace BTCPayServer.Services.Rates
{
public class ExchangeSharpRateProvider<T> : IRateProvider where T : ExchangeAPI, new()
{
HttpClient _httpClient;
public ExchangeSharpRateProvider(HttpClient httpClient, bool reverseCurrencyPair = false)
{
if (httpClient == null)
throw new ArgumentNullException(nameof(httpClient));
ReverseCurrencyPair = reverseCurrencyPair;
_httpClient = httpClient;
}
public bool ReverseCurrencyPair
{
get; set;
}
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
await new SynchronizationContextRemover();
var exchangeAPI = new T();
exchangeAPI.RequestMaker = new HttpClientRequestMaker(exchangeAPI, _httpClient, cancellationToken);
var rates = await exchangeAPI.GetTickersAsync();
var exchangeRateTasks = rates
.Where(t => t.Value.Ask != 0m && t.Value.Bid != 0m)
.Select(t => CreateExchangeRate(exchangeAPI, t));
var exchangeRates = await Task.WhenAll(exchangeRateTasks);
return exchangeRates
.Where(t => t != null)
.ToArray();
}
// ExchangeSymbolToGlobalSymbol throws exception which would kill perf
ConcurrentDictionary<string, string> notFoundSymbols = new ConcurrentDictionary<string, string>();
private async Task<PairRate> CreateExchangeRate(T exchangeAPI, KeyValuePair<string, ExchangeTicker> ticker)
{
if (notFoundSymbols.TryGetValue(ticker.Key, out _))
return null;
try
{
var tickerName = await exchangeAPI.ExchangeMarketSymbolToGlobalMarketSymbolAsync(ticker.Key);
if (!CurrencyPair.TryParse(tickerName, out var pair))
{
notFoundSymbols.TryAdd(ticker.Key, ticker.Key);
return null;
}
if (ReverseCurrencyPair)
pair = new CurrencyPair(pair.Right, pair.Left);
return new PairRate(pair, new BidAsk(ticker.Value.Bid, ticker.Value.Ask));
}
catch (ArgumentException)
{
notFoundSymbols.TryAdd(ticker.Key, ticker.Key);
return null;
}
}
}
}

View File

@ -1,216 +0,0 @@
using System;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using ExchangeSharp;
using System.Threading;
namespace BTCPayServer.Services.Rates
{
internal class HttpClientRequestMaker : IAPIRequestMaker
{
class InternalHttpWebRequest : IHttpWebRequest
{
internal readonly HttpWebRequest Request;
public Uri RequestUri => Request.RequestUri;
public string Method
{
get
{
return Request.Method;
}
set
{
Request.Method = value;
}
}
public int Timeout
{
get
{
return Request.Timeout;
}
set
{
Request.Timeout = value;
}
}
public int ReadWriteTimeout
{
get
{
return Request.ReadWriteTimeout;
}
set
{
Request.ReadWriteTimeout = value;
}
}
public InternalHttpWebRequest(Uri fullUri)
{
Request = ((WebRequest.Create(fullUri) as HttpWebRequest) ?? throw new NullReferenceException("Failed to create HttpWebRequest"));
Request.KeepAlive = false;
}
public void AddHeader(string header, string value)
{
switch (header.ToStringLowerInvariant())
{
case "content-type":
Request.ContentType = value;
break;
case "content-length":
Request.ContentLength = value.ConvertInvariant<long>(0L);
break;
case "user-agent":
Request.UserAgent = value;
break;
case "accept":
Request.Accept = value;
break;
case "connection":
Request.Connection = value;
break;
default:
Request.Headers[header] = value;
break;
}
}
public Task WriteAllAsync(byte[] data, int index, int length)
{
throw new NotImplementedException();
}
public HttpRequestMessage ToHttpRequestMessage()
{
var httpRequest = new HttpRequestMessage(HttpMethod.Get, Request.RequestUri);
CopyHeadersFrom(httpRequest, Request);
return httpRequest;
}
internal void CopyHeadersFrom(HttpRequestMessage message, HttpWebRequest request)
{
foreach (string headerName in request.Headers)
{
string[] headerValues = request.Headers.GetValues(headerName);
if (!message.Headers.TryAddWithoutValidation(headerName, headerValues))
{
if (message.Content != null)
message.Content.Headers.TryAddWithoutValidation(headerName, headerValues);
}
}
}
}
class InternalHttpWebResponse : IHttpWebResponse
{
public InternalHttpWebResponse(HttpResponseMessage httpResponseMessage)
{
var headers = new Dictionary<string, List<string>>();
foreach (var h in httpResponseMessage.Headers)
{
if (!headers.TryGetValue(h.Key, out var list))
{
list = new List<string>();
headers.Add(h.Key, list);
}
list.AddRange(h.Value);
}
Headers = new Dictionary<string, IReadOnlyList<string>>(headers.Count);
foreach (var item in headers)
{
Headers.Add(item.Key, item.Value.AsReadOnly());
}
}
public Dictionary<string, IReadOnlyList<string>> Headers { get; }
static IReadOnlyList<string> Empty = new List<string>().AsReadOnly();
public IReadOnlyList<string> GetHeader(string name)
{
Headers.TryGetValue(name, out var list);
return list ?? Empty;
}
}
private readonly IAPIRequestHandler api;
private readonly HttpClient _httpClient;
private readonly CancellationToken _cancellationToken;
public HttpClientRequestMaker(IAPIRequestHandler api, HttpClient httpClient, CancellationToken cancellationToken)
{
if (api == null)
throw new ArgumentNullException(nameof(api));
if (httpClient == null)
throw new ArgumentNullException(nameof(httpClient));
this.api = api;
_httpClient = httpClient;
_cancellationToken = cancellationToken;
}
public Action<IAPIRequestMaker, RequestMakerState, object> RequestStateChanged
{
get;
set;
}
public async Task<string> MakeRequestAsync(string url, string baseUrl = null, Dictionary<string, object> payload = null, string method = null)
{
await default(SynchronizationContextRemover);
await api.RateLimit.WaitToProceedAsync();
if (url[0] != '/')
{
url = "/" + url;
}
string uri2 = (baseUrl ?? api.BaseUrl) + url;
if (method == null)
{
method = api.RequestMethod;
}
Uri uri = api.ProcessRequestUrl(new UriBuilder(uri2), payload, method);
InternalHttpWebRequest request = new InternalHttpWebRequest(uri)
{
Method = method
};
request.AddHeader("content-type", api.RequestContentType);
request.AddHeader("user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.117 Safari/537.36");
int num3 = request.Timeout = (request.ReadWriteTimeout = (int)api.RequestTimeout.TotalMilliseconds);
await api.ProcessRequestAsync(request, payload);
try
{
RequestStateChanged?.Invoke(this, RequestMakerState.Begin, uri.AbsoluteUri);
using var webHttpRequest = request.ToHttpRequestMessage();
using var webHttpResponse = await _httpClient.SendAsync(webHttpRequest, _cancellationToken);
string text = await webHttpResponse.Content.ReadAsStringAsync();
if (!webHttpResponse.IsSuccessStatusCode)
{
if (string.IsNullOrWhiteSpace(text))
{
throw new APIException($"{webHttpResponse.StatusCode.ConvertInvariant<int>(0)} - {webHttpResponse.StatusCode}");
}
throw new APIException(text);
}
api.ProcessResponse(new InternalHttpWebResponse(webHttpResponse));
// local reference to handle delegate becoming null, extended discussion here:
// https://github.com/btcpayserver/btcpayserver/commit/00747906849f093712c3907c99404c55b3defa66#r37022103
var requestStateChanged = RequestStateChanged;
if (requestStateChanged != null)
{
requestStateChanged(this, RequestMakerState.Finished, text);
return text;
}
return text;
}
catch (Exception arg)
{
RequestStateChanged?.Invoke(this, RequestMakerState.Error, arg);
throw;
}
}
}
}

View File

@ -1,194 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Rating;
using ExchangeSharp;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Services.Rates
{
// Make sure that only one request is sent to kraken in general
public class KrakenExchangeRateProvider : IRateProvider
{
public KrakenExchangeRateProvider()
{
_Helper = new ExchangeKrakenAPI();
}
ExchangeKrakenAPI _Helper;
public HttpClient HttpClient
{
get
{
return _LocalClient ?? _Client;
}
set
{
_LocalClient = value;
}
}
HttpClient _LocalClient;
static HttpClient _Client = new HttpClient();
// ExchangeSymbolToGlobalSymbol throws exception which would kill perf
ConcurrentDictionary<string, string> notFoundSymbols = new ConcurrentDictionary<string, string>(new Dictionary<string, string>()
{
{"ADAXBT","ADAXBT"},
{ "BSVUSD","BSVUSD"},
{ "QTUMEUR","QTUMEUR"},
{ "QTUMXBT","QTUMXBT"},
{ "EOSUSD","EOSUSD"},
{ "XTZUSD","XTZUSD"},
{ "XREPZUSD","XREPZUSD"},
{ "ADAEUR","ADAEUR"},
{ "ADAUSD","ADAUSD"},
{ "GNOEUR","GNOEUR"},
{ "XTZETH","XTZETH"},
{ "XXRPZJPY","XXRPZJPY"},
{ "XXRPZCAD","XXRPZCAD"},
{ "XTZEUR","XTZEUR"},
{ "QTUMETH","QTUMETH"},
{ "XXLMZUSD","XXLMZUSD"},
{ "QTUMCAD","QTUMCAD"},
{ "QTUMUSD","QTUMUSD"},
{ "XTZXBT","XTZXBT"},
{ "GNOUSD","GNOUSD"},
{ "ADAETH","ADAETH"},
{ "ADACAD","ADACAD"},
{ "XTZCAD","XTZCAD"},
{ "BSVEUR","BSVEUR"},
{ "XZECZJPY","XZECZJPY"},
{ "XXLMZEUR","XXLMZEUR"},
{"EOSEUR","EOSEUR"},
{"BSVXBT","BSVXBT"}
});
string[] _Symbols = Array.Empty<string>();
DateTimeOffset? _LastSymbolUpdate = null;
Dictionary<string, string> _TickerMapping = new Dictionary<string, string>()
{
{ "XXDG", "DOGE" },
{ "XXBT", "BTC" },
{ "XBT", "BTC" },
{ "DASH", "DASH" },
{ "ZUSD", "USD" },
{ "ZEUR", "EUR" },
{ "ZJPY", "JPY" },
{ "ZCAD", "CAD" },
{ "ZGBP", "GBP" }
};
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var result = new List<PairRate>();
var symbols = await GetSymbolsAsync(cancellationToken);
var normalizedPairsList = symbols.Where(s => !notFoundSymbols.ContainsKey(s)).Select(s => _Helper.NormalizeMarketSymbol(s)).ToList();
var csvPairsList = string.Join(",", normalizedPairsList);
JToken apiTickers = await MakeJsonRequestAsync<JToken>("/0/public/Ticker", null, new Dictionary<string, object> { { "pair", csvPairsList } }, cancellationToken: cancellationToken);
var tickers = new List<KeyValuePair<string, ExchangeTicker>>();
foreach (string symbol in symbols)
{
var ticker = ConvertToExchangeTicker(symbol, apiTickers[symbol]);
if (ticker != null)
{
try
{
string global = null;
var mapped1 = _TickerMapping.Where(t => symbol.StartsWith(t.Key, StringComparison.OrdinalIgnoreCase))
.Select(t => new { KrakenTicker = t.Key, PayTicker = t.Value }).SingleOrDefault();
if (mapped1 != null)
{
var p2 = symbol.Substring(mapped1.KrakenTicker.Length);
if (_TickerMapping.TryGetValue(p2, out var mapped2))
p2 = mapped2;
global = $"{p2}_{mapped1.PayTicker}";
}
else
{
global = await _Helper.ExchangeMarketSymbolToGlobalMarketSymbolAsync(symbol);
}
if (CurrencyPair.TryParse(global, out var pair))
result.Add(new PairRate(pair.Inverse(), new BidAsk(ticker.Bid, ticker.Ask)));
else
notFoundSymbols.TryAdd(symbol, symbol);
}
catch (ArgumentException)
{
notFoundSymbols.TryAdd(symbol, symbol);
}
}
}
return result.ToArray();
}
private static ExchangeTicker ConvertToExchangeTicker(string symbol, JToken ticker)
{
if (ticker == null)
return null;
decimal last = ticker["c"][0].ConvertInvariant<decimal>();
return new ExchangeTicker
{
Ask = ticker["a"][0].ConvertInvariant<decimal>(),
Bid = ticker["b"][0].ConvertInvariant<decimal>(),
Last = last,
Volume = new ExchangeVolume
{
BaseCurrencyVolume = ticker["v"][1].ConvertInvariant<decimal>(),
BaseCurrency = symbol,
QuoteCurrencyVolume = ticker["v"][1].ConvertInvariant<decimal>() * last,
QuoteCurrency = symbol,
Timestamp = DateTime.UtcNow
}
};
}
private async Task<string[]> GetSymbolsAsync(CancellationToken cancellationToken)
{
if (_LastSymbolUpdate != null && DateTimeOffset.UtcNow - _LastSymbolUpdate.Value < TimeSpan.FromDays(0.5))
{
return _Symbols;
}
else
{
JToken json = await MakeJsonRequestAsync<JToken>("/0/public/AssetPairs", cancellationToken: cancellationToken);
var symbols = (from prop in json.Children<JProperty>() where !prop.Name.Contains(".d", StringComparison.OrdinalIgnoreCase) select prop.Name).ToArray();
_Symbols = symbols;
_LastSymbolUpdate = DateTimeOffset.UtcNow;
return symbols;
}
}
private async Task<T> MakeJsonRequestAsync<T>(string url, string baseUrl = null, Dictionary<string, object> payload = null, string requestMethod = null, CancellationToken cancellationToken = default)
{
StringBuilder sb = new StringBuilder();
sb.Append("https://api.kraken.com");
;
sb.Append(url);
if (payload != null)
{
sb.Append("?");
sb.Append(String.Join('&', payload.Select(kv => $"{kv.Key}={kv.Value}").OfType<object>().ToArray()));
}
var request = new HttpRequestMessage(HttpMethod.Get, sb.ToString());
var response = await HttpClient.SendAsync(request, cancellationToken);
string stringResult = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<T>(stringResult);
if (result is JToken json)
{
if (!(json is JArray) && json["error"] is JArray error && error.Count != 0)
{
throw new APIException(error[0].ToStringInvariant());
}
result = (T)(object)(json["result"] ?? json);
}
return result;
}
}
}

View File

@ -1,29 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Rating;
namespace BTCPayServer.Services.Rates
{
public class NullRateProvider : IRateProvider
{
private NullRateProvider()
{
}
private static readonly NullRateProvider _Instance = new NullRateProvider();
public static NullRateProvider Instance
{
get
{
return _Instance;
}
}
public Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
return Task.FromResult(Array.Empty<PairRate>());
}
}
}

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 PolisRateProvider : IRateProvider
{
private readonly HttpClient _httpClient;
public PolisRateProvider(HttpClient httpClient)
{
_httpClient = httpClient ?? new HttpClient();
}
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync("https://obol.polispay.com/complex/btc/polis", cancellationToken); //Returns complex rate from BTC to POLIS
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
var value = jobj["data"].Value<decimal>();
return new[] { new PairRate(new CurrencyPair("BTC", "POLIS"), new BidAsk(value)) };
}
}
}

View File

@ -1,95 +0,0 @@
using System;
using System.Collections.Concurrent;
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.Options;
using static BTCPayServer.Services.Rates.RateProviderFactory;
namespace BTCPayServer.Services.Rates
{
public class ExchangeException
{
public Exception Exception { get; set; }
public string ExchangeName { get; set; }
}
public class RateResult
{
public List<ExchangeException> ExchangeExceptions { get; set; } = new List<ExchangeException>();
public string Rule { get; set; }
public string EvaluatedRule { get; set; }
public HashSet<RateRulesErrors> Errors { get; set; }
public BidAsk BidAsk { get; set; }
public TimeSpan Latency { get; internal set; }
}
public class RateFetcher
{
private readonly RateProviderFactory _rateProviderFactory;
public RateFetcher(RateProviderFactory rateProviderFactory)
{
_rateProviderFactory = rateProviderFactory;
}
public RateProviderFactory RateProviderFactory => _rateProviderFactory;
public async Task<RateResult> FetchRate(CurrencyPair pair, RateRules rules, CancellationToken cancellationToken)
{
return await FetchRates(new HashSet<CurrencyPair>(new[] { pair }), rules, cancellationToken).First().Value;
}
public Dictionary<CurrencyPair, Task<RateResult>> FetchRates(HashSet<CurrencyPair> pairs, RateRules rules, CancellationToken cancellationToken)
{
if (rules == null)
throw new ArgumentNullException(nameof(rules));
var fetchingRates = new Dictionary<CurrencyPair, Task<RateResult>>();
var fetchingExchanges = new Dictionary<string, Task<QueryRateResult>>();
var consolidatedRates = new ExchangeRates();
foreach (var i in pairs.Select(p => (Pair: p, RateRule: rules.GetRuleFor(p))))
{
var dependentQueries = new List<Task<QueryRateResult>>();
foreach (var requiredExchange in i.RateRule.ExchangeRates)
{
if (!fetchingExchanges.TryGetValue(requiredExchange.Exchange, out var fetching))
{
fetching = _rateProviderFactory.QueryRates(requiredExchange.Exchange, cancellationToken);
fetchingExchanges.Add(requiredExchange.Exchange, fetching);
}
dependentQueries.Add(fetching);
}
fetchingRates.Add(i.Pair, GetRuleValue(dependentQueries, i.RateRule));
}
return fetchingRates;
}
private async Task<RateResult> GetRuleValue(List<Task<QueryRateResult>> dependentQueries, RateRule rateRule)
{
var result = new RateResult();
foreach (var queryAsync in dependentQueries)
{
var query = await queryAsync;
result.Latency = query.Latency;
if (query.Exception != null)
result.ExchangeExceptions.Add(query.Exception);
foreach (var rule in query.PairRates)
{
rateRule.ExchangeRates.SetRate(query.Exchange, rule.CurrencyPair, rule.BidAsk);
}
}
rateRule.Reevaluate();
result.BidAsk = rateRule.BidAsk;
result.Errors = rateRule.Errors;
result.EvaluatedRule = rateRule.ToString(true);
result.Rule = rateRule.ToString(false);
return result;
}
}
}

View File

@ -1,200 +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");
yield return new AvailableRateProvider("polispay", "PolisPay", "https://obol.polispay.com/complex/btc/polis");
yield return new AvailableRateProvider("bitfinex", "Bitfinex", "https://api.bitfinex.com/v2/tickers?symbols=tBTCUSD,tLTCUSD,tLTCBTC,tETHUSD,tETHBTC,tETCBTC,tETCUSD,tRRTUSD,tRRTBTC,tZECUSD,tZECBTC,tXMRUSD,tXMRBTC,tDSHUSD,tDSHBTC,tBTCEUR,tBTCJPY,tXRPUSD,tXRPBTC,tIOTUSD,tIOTBTC,tIOTETH,tEOSUSD,tEOSBTC,tEOSETH,tSANUSD,tSANBTC,tSANETH,tOMGUSD,tOMGBTC,tOMGETH,tNEOUSD,tNEOBTC,tNEOETH,tETPUSD,tETPBTC,tETPETH,tQTMUSD,tQTMBTC,tQTMETH,tAVTUSD,tAVTBTC,tAVTETH,tEDOUSD,tEDOBTC,tEDOETH,tBTGUSD,tBTGBTC,tDATUSD,tDATBTC,tDATETH,tQSHUSD,tQSHBTC,tQSHETH,tYYWUSD,tYYWBTC,tYYWETH,tGNTUSD,tGNTBTC,tGNTETH,tSNTUSD,tSNTBTC,tSNTETH,tIOTEUR,tBATUSD,tBATBTC,tBATETH,tMNAUSD,tMNABTC,tMNAETH,tFUNUSD,tFUNBTC,tFUNETH,tZRXUSD,tZRXBTC,tZRXETH,tTNBUSD,tTNBBTC,tTNBETH,tSPKUSD,tSPKBTC,tSPKETH,tTRXUSD,tTRXBTC,tTRXETH,tRCNUSD,tRCNBTC,tRCNETH,tRLCUSD,tRLCBTC,tRLCETH,tAIDUSD,tAIDBTC,tAIDETH,tSNGUSD,tSNGBTC,tSNGETH,tREPUSD,tREPBTC,tREPETH,tELFUSD,tELFBTC,tELFETH,tNECUSD,tNECBTC,tNECETH,tBTCGBP,tETHEUR,tETHJPY,tETHGBP,tNEOEUR,tNEOJPY,tNEOGBP,tEOSEUR,tEOSJPY,tEOSGBP,tIOTJPY,tIOTGBP,tIOSUSD,tIOSBTC,tIOSETH,tAIOUSD,tAIOBTC,tAIOETH,tREQUSD,tREQBTC,tREQETH,tRDNUSD,tRDNBTC,tRDNETH,tLRCUSD,tLRCBTC,tLRCETH,tWAXUSD,tWAXBTC,tWAXETH,tDAIUSD,tDAIBTC,tDAIETH,tAGIUSD,tAGIBTC,tAGIETH,tBFTUSD,tBFTBTC,tBFTETH,tMTNUSD,tMTNBTC,tMTNETH,tODEUSD,tODEBTC,tODEETH,tANTUSD,tANTBTC,tANTETH,tDTHUSD,tDTHBTC,tDTHETH,tMITUSD,tMITBTC,tMITETH,tSTJUSD,tSTJBTC,tSTJETH,tXLMUSD,tXLMEUR,tXLMJPY,tXLMGBP,tXLMBTC,tXLMETH,tXVGUSD,tXVGEUR,tXVGJPY,tXVGGBP,tXVGBTC,tXVGETH,tBCIUSD,tBCIBTC,tMKRUSD,tMKRBTC,tMKRETH,tKNCUSD,tKNCBTC,tKNCETH,tPOAUSD,tPOABTC,tPOAETH,tEVTUSD,tLYMUSD,tLYMBTC,tLYMETH,tUTKUSD,tUTKBTC,tUTKETH,tVEEUSD,tVEEBTC,tVEEETH,tDADUSD,tDADBTC,tDADETH,tORSUSD,tORSBTC,tORSETH,tAUCUSD,tAUCBTC,tAUCETH,tPOYUSD,tPOYBTC,tPOYETH,tFSNUSD,tFSNBTC,tFSNETH,tCBTUSD,tCBTBTC,tCBTETH,tZCNUSD,tZCNBTC,tZCNETH,tSENUSD,tSENBTC,tSENETH,tNCAUSD,tNCABTC,tNCAETH,tCNDUSD,tCNDBTC,tCNDETH,tCTXUSD,tCTXBTC,tCTXETH,tPAIUSD,tPAIBTC,tSEEUSD,tSEEBTC,tSEEETH,tESSUSD,tESSBTC,tESSETH,tATMUSD,tATMBTC,tATMETH,tHOTUSD,tHOTBTC,tHOTETH,tDTAUSD,tDTABTC,tDTAETH,tIQXUSD,tIQXBTC,tIQXEOS,tWPRUSD,tWPRBTC,tWPRETH,tZILUSD,tZILBTC,tZILETH,tBNTUSD,tBNTBTC,tBNTETH,tABSUSD,tABSETH,tXRAUSD,tXRAETH,tMANUSD,tMANETH,tBBNUSD,tBBNETH,tNIOUSD,tNIOETH,tDGXUSD,tDGXETH,tVETUSD,tVETBTC,tVETETH,tUTNUSD,tUTNETH,tTKNUSD,tTKNETH,tGOTUSD,tGOTEUR,tGOTETH,tXTZUSD,tXTZBTC,tCNNUSD,tCNNETH,tBOXUSD,tBOXETH,tTRXEUR,tTRXGBP,tTRXJPY,tMGOUSD,tMGOETH,tRTEUSD,tRTEETH,tYGGUSD,tYGGETH,tMLNUSD,tMLNETH,tWTCUSD,tWTCETH,tCSXUSD,tCSXETH,tOMNUSD,tOMNBTC,tINTUSD,tINTETH,tDRNUSD,tDRNETH,tPNKUSD,tPNKETH,tDGBUSD,tDGBBTC,tBSVUSD,tBSVBTC,tBABUSD,tBABBTC,tWLOUSD,tWLOXLM,tVLDUSD,tVLDETH,tENJUSD,tENJETH,tONLUSD,tONLETH,tRBTUSD,tRBTBTC,tUSTUSD,tEUTEUR,tEUTUSD,tGSDUSD,tUDCUSD,tTSDUSD,tPAXUSD,tRIFUSD,tRIFBTC,tPASUSD,tPASETH,tVSYUSD,tVSYBTC,tZRXDAI,tMKRDAI,tOMGDAI,tBTTUSD,tBTTBTC,tBTCUST,tETHUST,tCLOUSD,tCLOBTC,tIMPUSD,tIMPETH,tLTCUST,tEOSUST,tBABUST,tSCRUSD,tSCRETH,tGNOUSD,tGNOETH,tGENUSD,tGENETH,tATOUSD,tATOBTC,tATOETH,tWBTUSD,tXCHUSD,tEUSUSD,tWBTETH,tXCHETH,tEUSETH,tLEOUSD,tLEOBTC,tLEOUST,tLEOEOS,tLEOETH,tASTUSD,tASTETH,tFOAUSD,tFOAETH,tUFRUSD,tUFRETH,tZBTUSD,tZBTUST,tOKBUSD,tUSKUSD,tGTXUSD,tKANUSD,tOKBUST,tOKBETH,tOKBBTC,tUSKUST,tUSKETH,tUSKBTC,tUSKEOS,tGTXUST,tKANUST,tAMPUSD,tALGUSD,tALGBTC,tALGUST,tBTCXCH,tSWMUSD,tSWMETH,tTRIUSD,tTRIETH,tLOOUSD,tLOOETH,tAMPUST,tDUSK:USD,tDUSK:BTC,tUOSUSD,tUOSBTC,tRRBUSD,tRRBUST,tDTXUSD,tDTXUST,tAMPBTC,tFTTUSD,tFTTUST,tPAXUST,tUDCUST,tTSDUST,tBTC:CNHT,tUST:CNHT,tCNH:CNHT,tCHZUSD,tCHZUST,tBTCF0:USTF0,tETHF0:USTF0");
yield return new AvailableRateProvider("okex", "OKEx", "https://www.okex.com/api/futures/v3/instruments/ticker");
yield return new AvailableRateProvider("coinbasepro", "Coinbase Pro", "https://api.pro.coinbase.com/products");
}
void InitExchanges()
{
// We need to be careful to only add exchanges which OnGetTickers implementation make only 1 request
AddExchangeSharpProviders<ExchangeBinanceAPI>("binance");
AddExchangeSharpProviders<ExchangeBittrexAPI>("bittrex");
AddExchangeSharpProviders<ExchangePoloniexAPI>("poloniex");
AddExchangeSharpProviders<ExchangeHitBTCAPI>("hitbtc");
AddExchangeSharpProviders<ExchangeNDAXAPI>("ndax");
// 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")));
Providers.Add("polispay", new PolisRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_POLIS")));
// Backward compatibility: coinaverage should be using coingecko to prevent stores from breaking
Providers.Add("coinaverage", new CoinGeckoRateProvider(_httpClientFactory));
AddExchangeSharpProviders<ExchangeBitfinexAPI>("bitfinex");
AddExchangeSharpProviders<ExchangeOKExAPI>("okex");
AddExchangeSharpProviders<ExchangeCoinbaseAPI>("coinbasepro");
// Those exchanges make too many requests, exchange sharp do not parallelize so it is too slow...
//AddExchangeSharpProviders<ExchangeGeminiAPI>("gemini");
//AddExchangeSharpProviders<ExchangeBitstampAPI>("bitstamp");
//AddExchangeSharpProviders<ExchangeBitMEXAPI>("bitmex");
foreach (var provider in Providers.ToArray())
{
var prov = new BackgroundFetcherRateProvider(Providers[provider.Key]);
prov.RefreshRate = TimeSpan.FromMinutes(1.0);
prov.ValidatyTime = TimeSpan.FromMinutes(5.0);
Providers[provider.Key] = prov;
}
Providers["gdax"] = Providers["coinbasepro"];
foreach (var supportedExchange in GetCoinGeckoSupportedExchanges())
{
if (!Providers.ContainsKey(supportedExchange.Id) && supportedExchange.Id != CoinGeckoRateProvider.CoinGeckoName)
{
var coingecko = new CoinGeckoRateProvider(_httpClientFactory)
{
UnderlyingExchange = supportedExchange.SourceId
};
var bgFetcher = new BackgroundFetcherRateProvider(coingecko);
bgFetcher.RefreshRate = TimeSpan.FromMinutes(1.0);
bgFetcher.ValidatyTime = TimeSpan.FromMinutes(5.0);
Providers.Add(supportedExchange.Id, bgFetcher);
}
}
}
private IRateProvider AddExchangeSharpProviders<T>(string providerName) where T: ExchangeAPI, new()
{
var provider = new ExchangeSharpRateProvider<T>(_httpClientFactory.CreateClient($"EXCHANGE_{providerName}".ToUpperInvariant()), true);
Providers.Add(providerName, provider);
return provider;
}
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));
}
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,36 +1,20 @@
<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>
<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="79.0.3945.3600" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.0" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BTCPayServer\BTCPayServer.csproj" />
</ItemGroup>
<ItemGroup>
@ -40,14 +24,6 @@
<None Update="docker-compose.yml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="xunit.runner.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BTCPayServer.Rating\BTCPayServer.Rating.csproj" />
<ProjectReference Include="..\BTCPayServer\BTCPayServer.csproj" />
</ItemGroup>
</Project>

View File

@ -1,6 +1,4 @@
using BTCPayServer.Configuration;
using System.Linq;
using BTCPayServer.HostedServices;
using BTCPayServer.Hosting;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning;
@ -13,6 +11,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,22 +31,10 @@ 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
{
public enum TestDatabases
{
Postgres,
MySQL,
}
public class BTCPayServerTester : IDisposable
{
private string _Directory;
@ -63,7 +50,6 @@ namespace BTCPayServer.Tests
}
public Uri LTCNBXplorerUri { get; set; }
public Uri LBTCNBXplorerUri { get; set; }
public Uri ServerUri
{
@ -71,11 +57,6 @@ namespace BTCPayServer.Tests
set;
}
public string MySQL
{
get; set;
}
public string Postgres
{
get; set;
@ -87,17 +68,10 @@ namespace BTCPayServer.Tests
get; set;
}
public TestDatabases TestDatabase
{
get; set;
}
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 +80,35 @@ 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");
config.AppendLine($"btc.lightning={IntegratedLightning.AbsoluteUri}");
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}");
if (TestDatabase == TestDatabases.MySQL && !String.IsNullOrEmpty(MySQL))
config.AppendLine($"mysql=" + MySQL);
else if (!String.IsNullOrEmpty(Postgres))
if (Postgres != null)
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" });
var conf = new DefaultConfiguration() { Logger = Logs.LogProvider.CreateLogger("Console") }.CreateConfiguration(new[] { "--datadir", _Directory, "--conf", confPath });
_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,101 +118,40 @@ 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));
if (MockRates)
var rateProvider = (BTCPayRateProviderFactory)_Host.Services.GetService(typeof(BTCPayRateProviderFactory));
rateProvider.DirectProviders.Clear();
var coinAverageMock = new MockRateProvider();
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
{
var rateProvider = (RateProviderFactory)_Host.Services.GetService(typeof(RateProviderFactory));
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);
var bitflyerMock = new MockRateProvider();
bitflyerMock.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_JPY"), new BidAsk(700000m)));
rateProvider.Providers.Add("bitflyer", bitflyerMock);
var quadrigacx = new MockRateProvider();
quadrigacx.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_CAD"), 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)));
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))
Exchange = "coinaverage",
CurrencyPair = CurrencyPair.Parse("BTC_USD"),
Value = 5000m
});
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
{
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)
Exchange = "coinaverage",
CurrencyPair = CurrencyPair.Parse("BTC_CAD"),
Value = 4500m
});
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
{
var resp = await HttpClient.GetAsync("/", cancellationToken).ConfigureAwait(false);
if (resp.StatusCode == HttpStatusCode.OK)
break;
await Task.Delay(10, cancellationToken).ConfigureAwait(false);
}
Exchange = "coinaverage",
CurrencyPair = CurrencyPair.Parse("LTC_USD"),
Value = 500m
});
rateProvider.DirectProviders.Add("coinaverage", coinAverageMock);
}
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;
internal set;
}
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,26 +160,17 @@ 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) where T : Controller
{
var context = new DefaultHttpContext();
context.Request.Host = new HostString("127.0.0.1", Port);
context.Request.Host = new HostString("127.0.0.1");
context.Request.Scheme = "http";
context.Request.Protocol = "http";
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));
context.User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, userId) }, Policies.CookieAuthentication));
}
if (storeId != null)
if(storeId != null)
{
context.SetStoreData(GetService<StoreRepository>().FindStore(storeId, userId).GetAwaiter().GetResult());
}

View File

@ -1,246 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments.Changelly;
using BTCPayServer.Payments.Changelly.Models;
using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Xunit;
using Xunit.Abstractions;
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)]
[Trait("Integration", "Integration")]
public async void CanSetChangellyPaymentMethod()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
var storeBlob = controller.CurrentStore.GetStoreBlob();
Assert.Null(storeBlob.ChangellySettings);
var updateModel = new UpdateChangellySettingsViewModel()
{
ApiSecret = "secret",
ApiKey = "key",
ApiUrl = "http://gozo.com",
ChangellyMerchantId = "aaa",
};
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await controller.UpdateChangellySettings(user.StoreId, updateModel, "save")).ActionName);
var store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
storeBlob = controller.CurrentStore.GetStoreBlob();
Assert.NotNull(storeBlob.ChangellySettings);
Assert.NotNull(storeBlob.ChangellySettings);
Assert.IsType<ChangellySettings>(storeBlob.ChangellySettings);
Assert.Equal(storeBlob.ChangellySettings.ApiKey, updateModel.ApiKey);
Assert.Equal(storeBlob.ChangellySettings.ApiSecret,
updateModel.ApiSecret);
Assert.Equal(storeBlob.ChangellySettings.ApiUrl, updateModel.ApiUrl);
Assert.Equal(storeBlob.ChangellySettings.ChangellyMerchantId,
updateModel.ChangellyMerchantId);
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanToggleChangellyPaymentMethod()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
var updateModel = new UpdateChangellySettingsViewModel()
{
ApiSecret = "secret",
ApiKey = "key",
ApiUrl = "http://gozo.com",
ChangellyMerchantId = "aaa",
Enabled = true
};
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await controller.UpdateChangellySettings(user.StoreId, updateModel, "save")).ActionName);
var store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
Assert.True(store.GetStoreBlob().ChangellySettings.Enabled);
updateModel.Enabled = false;
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await controller.UpdateChangellySettings(user.StoreId, updateModel, "save")).ActionName);
store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
Assert.False(store.GetStoreBlob().ChangellySettings.Enabled);
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async void CannotUseChangellyApiWithoutChangellyPaymentMethodSet()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var changellyController =
tester.PayTester.GetController<ChangellyController>(user.UserId, user.StoreId);
changellyController.IsTest = true;
//test non existing payment method
Assert.IsType<BitpayErrorModel>(Assert
.IsType<BadRequestObjectResult>(await changellyController.GetCurrencyList(user.StoreId))
.Value);
var updateModel = CreateDefaultChangellyParams(false);
var storesController = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
//set payment method but disabled
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await storesController.UpdateChangellySettings(user.StoreId, updateModel, "save")).ActionName);
Assert.IsType<BitpayErrorModel>(Assert
.IsType<BadRequestObjectResult>(await changellyController.GetCurrencyList(user.StoreId))
.Value);
updateModel.Enabled = true;
//test with enabled method
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await storesController.UpdateChangellySettings(user.StoreId, updateModel, "save")).ActionName);
Assert.IsNotType<BitpayErrorModel>(Assert
.IsType<OkObjectResult>(await changellyController.GetCurrencyList(user.StoreId))
.Value);
}
}
UpdateChangellySettingsViewModel CreateDefaultChangellyParams(bool enabled)
{
return new UpdateChangellySettingsViewModel()
{
ApiKey = "6ed02cdf1b614d89a8c0ceb170eebb61",
ApiSecret = "8fbd66a2af5fd15a6b5f8ed0159c5842e32a18538521ffa145bd6c9e124d3483",
ChangellyMerchantId = "804298eb5753",
Enabled = enabled
};
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async void CanGetCurrencyListFromChangelly()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
//save changelly settings
var updateModel = CreateDefaultChangellyParams(true);
var storesController = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
//confirm saved
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await storesController.UpdateChangellySettings(user.StoreId, updateModel, "save")).ActionName);
var factory = UnitTest1.CreateBTCPayRateFactory();
var fetcher = new RateFetcher(factory);
var httpClientFactory = TestUtils.CreateHttpFactory();
var changellyController = new ChangellyController(
new ChangellyClientProvider(tester.PayTester.StoreRepository, httpClientFactory),
tester.NetworkProvider, fetcher);
changellyController.IsTest = true;
var result = Assert
.IsType<OkObjectResult>(await changellyController.GetCurrencyList(user.StoreId))
.Value as IEnumerable<CurrencyFull>;
Assert.True(result.Any());
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async void CanCalculateToAmountForChangelly()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var updateModel = CreateDefaultChangellyParams(true);
var storesController = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await storesController.UpdateChangellySettings(user.StoreId, updateModel, "save")).ActionName);
var factory = UnitTest1.CreateBTCPayRateFactory();
var fetcher = new RateFetcher(factory);
var httpClientFactory = TestUtils.CreateHttpFactory();
var changellyController = new ChangellyController(
new ChangellyClientProvider(tester.PayTester.StoreRepository, httpClientFactory),
tester.NetworkProvider, fetcher);
changellyController.IsTest = true;
Assert.IsType<decimal>(Assert
.IsType<OkObjectResult>(await changellyController.CalculateAmount(user.StoreId, "ltc", "btc", 1.0m, default))
.Value);
}
}
[Fact]
[Trait("Fast", "Fast")]
public void CanComputeBaseAmount()
{
Assert.Equal(1, ChangellyCalculationHelper.ComputeBaseAmount(1, 1));
Assert.Equal(0.5m, ChangellyCalculationHelper.ComputeBaseAmount(1, 0.5m));
Assert.Equal(2, ChangellyCalculationHelper.ComputeBaseAmount(0.5m, 1));
Assert.Equal(4m, ChangellyCalculationHelper.ComputeBaseAmount(1, 4));
}
[Fact]
[Trait("Fast", "Fast")]
public void CanComputeCorrectAmount()
{
Assert.Equal(1, ChangellyCalculationHelper.ComputeCorrectAmount(0.5m, 1, 2));
Assert.Equal(0.25m, ChangellyCalculationHelper.ComputeCorrectAmount(0.5m, 1, 0.5m));
Assert.Equal(20, ChangellyCalculationHelper.ComputeCorrectAmount(10, 1, 2));
}
}
}

View File

@ -1,9 +1,8 @@
using System;
using System.Collections.Generic;
using System.Text;
using BTCPayServer.Lightning;
using BTCPayServer.Lightning.Charge;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Payments.Lightning.Charge;
using BTCPayServer.Payments.Lightning.CLightning;
using NBitcoin;
namespace BTCPayServer.Tests
@ -16,7 +15,7 @@ namespace BTCPayServer.Tests
{
this._Parent = serverTester;
var url = serverTester.GetEnvironment(environmentName, defaultValue);
Client = (ChargeClient)LightningClientFactory.CreateClient(url, network);
Client = new ChargeClient(new Uri(url), network);
P2PHost = _Parent.GetEnvironment(environmentName + "_HOST", defaultHost);
}
public ChargeClient Client { get; set; }

View File

@ -1,221 +0,0 @@
using System;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Lightning;
using BTCPayServer.Payments;
using BTCPayServer.Tests.Logging;
using BTCPayServer.Views.Stores;
using NBitcoin;
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);
}
}
[Fact(Timeout = TestTimeout)]
public async Task CanUseJSModal()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser();
var store = s.CreateNewStore();
s.GoToStore(store.storeId);
s.AddDerivationScheme();
var invoiceId = s.CreateInvoice(store.storeId, 0.001m, "BTC", "a@x.com");
var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
s.Driver.Navigate()
.GoToUrl(new Uri(s.Server.PayTester.ServerUri, $"tests/index.html?invoice={invoiceId}"));
TestUtils.Eventually(() =>
{
Assert.True(s.Driver.FindElement(By.Name("btcpay")).Displayed);
});
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(invoice
.GetPaymentMethod(new PaymentMethodId("BTC", PaymentTypes.BTCLike))
.GetPaymentMethodDetails().GetPaymentDestination(), Network.RegTest),
new Money(0.001m, MoneyUnit.BTC));
IWebElement closebutton = null;
TestUtils.Eventually(() =>
{
var iframe = s.Driver.SwitchTo().Frame("btcpay");
closebutton = iframe.FindElement(By.ClassName("close-action"));
Assert.True(closebutton.Displayed);
});
closebutton.Click();
s.Driver.AssertElementNotFound(By.Name("btcpay"));
Assert.Equal(s.Driver.Url,
new Uri(s.Server.PayTester.ServerUri, $"tests/index.html?invoice={invoiceId}").ToString());
}
}
}
}

View File

@ -1,91 +0,0 @@
using BTCPayServer.Controllers;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments.CoinSwitch;
using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc;
using Xunit;
using Xunit.Abstractions;
using BTCPayServer.Data;
using System.Threading.Tasks;
namespace BTCPayServer.Tests
{
public class CoinSwitchTests
{
public CoinSwitchTests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) {Name = "Tests"};
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact]
[Trait("Integration", "Integration")]
public async Task CanSetCoinSwitchPaymentMethod()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
var storeBlob = controller.CurrentStore.GetStoreBlob();
Assert.Null(storeBlob.CoinSwitchSettings);
var updateModel = new UpdateCoinSwitchSettingsViewModel()
{
MerchantId = "aaa",
};
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await controller.UpdateCoinSwitchSettings(user.StoreId, updateModel, "save")).ActionName);
var store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
storeBlob = controller.CurrentStore.GetStoreBlob();
Assert.NotNull(storeBlob.CoinSwitchSettings);
Assert.NotNull(storeBlob.CoinSwitchSettings);
Assert.IsType<CoinSwitchSettings>(storeBlob.CoinSwitchSettings);
Assert.Equal(storeBlob.CoinSwitchSettings.MerchantId,
updateModel.MerchantId);
}
}
[Fact]
[Trait("Integration", "Integration")]
public async Task CanToggleCoinSwitchPaymentMethod()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var controller = tester.PayTester.GetController<StoresController>(user.UserId, user.StoreId);
var updateModel = new UpdateCoinSwitchSettingsViewModel()
{
MerchantId = "aaa",
Enabled = true
};
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await controller.UpdateCoinSwitchSettings(user.StoreId, updateModel, "save")).ActionName);
var store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
Assert.True(store.GetStoreBlob().CoinSwitchSettings.Enabled);
updateModel.Enabled = false;
Assert.Equal("UpdateStore", Assert.IsType<RedirectToActionResult>(
await controller.UpdateCoinSwitchSettings(user.StoreId, updateModel, "save")).ActionName);
store = await tester.PayTester.StoreRepository.FindStore(user.StoreId);
Assert.False(store.GetStoreBlob().CoinSwitchSettings.Enabled);
}
}
}
}

View File

@ -1,304 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Events;
using BTCPayServer.Models;
using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments.Changelly;
using BTCPayServer.Payments.Changelly.Models;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores;
using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using NBitcoin;
using NBitpayClient;
using Xunit;
using Xunit.Abstractions;
using static BTCPayServer.Tests.UnitTest1;
namespace BTCPayServer.Tests
{
public class CrowdfundTests
{
public CrowdfundTests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) {Name = "Tests"};
Logs.LogProvider = new XUnitLogProvider(helper);
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanCreateAndDeleteCrowdfundApp()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var user2 = tester.NewAccount();
user2.GrantAccess();
var apps = user.GetController<AppsController>();
var apps2 = user2.GetController<AppsController>();
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp().Result).Model);
Assert.NotNull(vm.SelectedAppType);
Assert.Null(vm.Name);
vm.Name = "test";
vm.SelectedAppType = AppType.Crowdfund.ToString();
var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.CreateApp(vm).Result);
Assert.Equal(nameof(apps.UpdateCrowdfund), redirectToAction.ActionName);
var appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps().Result).Model);
var appList2 =
Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps2.ListApps().Result).Model);
Assert.Single(appList.Apps);
Assert.Empty(appList2.Apps);
Assert.Equal("test", appList.Apps[0].AppName);
Assert.Equal(apps.CreatedAppId, appList.Apps[0].Id);
Assert.True(appList.Apps[0].IsOwner);
Assert.Equal(user.StoreId, appList.Apps[0].StoreId);
Assert.IsType<NotFoundResult>(apps2.DeleteApp(appList.Apps[0].Id).Result);
Assert.IsType<ViewResult>(apps.DeleteApp(appList.Apps[0].Id).Result);
redirectToAction = Assert.IsType<RedirectToActionResult>(apps.DeleteAppPost(appList.Apps[0].Id).Result);
Assert.Equal(nameof(apps.ListApps), redirectToAction.ActionName);
appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps().Result).Model);
Assert.Empty(appList.Apps);
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanContributeOnlyWhenAllowed()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
var apps = user.GetController<AppsController>();
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp().Result).Model);
vm.Name = "test";
vm.SelectedAppType = AppType.Crowdfund.ToString();
Assert.IsType<RedirectToActionResult>(apps.CreateApp(vm).Result);
var appId = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps().Result).Model)
.Apps[0].Id;
//Scenario 1: Not Enabled - Not Allowed
var crowdfundViewModel = Assert.IsType<UpdateCrowdfundViewModel>(Assert
.IsType<ViewResult>(apps.UpdateCrowdfund(appId).Result).Model);
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.Enabled = false;
crowdfundViewModel.EndDate = null;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
var anonAppPubsController = tester.PayTester.GetController<AppsPublicController>();
var publicApps = user.GetController<AppsPublicController>();
Assert.IsType<NotFoundObjectResult>(await anonAppPubsController.ContributeToCrowdfund(appId, new ContributeToCrowdfund()
{
Amount = new decimal(0.01)
}, default));
Assert.IsType<NotFoundResult>(await anonAppPubsController.ViewCrowdfund(appId, string.Empty));
//Scenario 2: Not Enabled But Admin - Allowed
Assert.IsType<OkObjectResult>(await publicApps.ContributeToCrowdfund(appId, new ContributeToCrowdfund()
{
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));
//Scenario 3: Enabled But Start Date > Now - Not Allowed
crowdfundViewModel.StartDate= DateTime.Today.AddDays(2);
crowdfundViewModel.Enabled = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").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
crowdfundViewModel.StartDate= DateTime.Today.AddDays(-2);
crowdfundViewModel.EndDate= DateTime.Today.AddDays(-1);
crowdfundViewModel.Enabled = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").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
crowdfundViewModel.StartDate= DateTime.Today.AddDays(-2);
crowdfundViewModel.EndDate= DateTime.Today.AddDays(2);
crowdfundViewModel.Enabled = true;
crowdfundViewModel.TargetAmount = 1;
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.EnforceTargetAmount = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").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)]
[Trait("Integration", "Integration")]
public async Task CanComputeCrowdfundModel()
{
using (var tester = ServerTester.Create())
{
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
user.ModifyStore(s => s.NetworkFeeMode = NetworkFeeMode.Never);
var apps = user.GetController<AppsController>();
var vm = Assert.IsType<CreateAppViewModel>(Assert.IsType<ViewResult>(apps.CreateApp().Result).Model);
vm.Name = "test";
vm.SelectedAppType = AppType.Crowdfund.ToString();
Assert.IsType<RedirectToActionResult>(apps.CreateApp(vm).Result);
var appId = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps().Result).Model)
.Apps[0].Id;
Logs.Tester.LogInformation("We create an invoice with a hardcap");
var crowdfundViewModel = Assert.IsType<UpdateCrowdfundViewModel>(Assert
.IsType<ViewResult>(apps.UpdateCrowdfund(appId).Result).Model);
crowdfundViewModel.Enabled = true;
crowdfundViewModel.EndDate = null;
crowdfundViewModel.TargetAmount = 100;
crowdfundViewModel.TargetCurrency = "BTC";
crowdfundViewModel.UseAllStoreInvoices = true;
crowdfundViewModel.EnforceTargetAmount = true;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
var anonAppPubsController = tester.PayTester.GetController<AppsPublicController>();
var publicApps = user.GetController<AppsPublicController>();
var model = Assert.IsType<ViewCrowdfundViewModel>(Assert
.IsType<ViewResult>(publicApps.ViewCrowdfund(appId, String.Empty).Result).Model);
Assert.Equal(crowdfundViewModel.TargetAmount, model.TargetAmount );
Assert.Equal(crowdfundViewModel.EndDate, model.EndDate );
Assert.Equal(crowdfundViewModel.StartDate, model.StartDate );
Assert.Equal(crowdfundViewModel.TargetCurrency, model.TargetCurrency );
Assert.Equal(0m, model.Info.CurrentAmount );
Assert.Equal(0m, model.Info.CurrentPendingAmount);
Assert.Equal(0m, model.Info.ProgressPercentage);
Logs.Tester.LogInformation("Unpaid invoices should show as pending contribution because it is hardcap");
Logs.Tester.LogInformation("Because UseAllStoreInvoices is true, we can manually create an invoice and it should show as contribution");
var invoice = user.BitPay.CreateInvoice(new Invoice()
{
Buyer = new Buyer() { email = "test@fwf.com" },
Price = 1m,
Currency = "BTC",
PosData = "posData",
ItemDesc = "Some description",
TransactionSpeed = "high",
FullNotifications = true
}, Facade.Merchant);
model = Assert.IsType<ViewCrowdfundViewModel>(Assert
.IsType<ViewResult>(publicApps.ViewCrowdfund(appId, String.Empty).Result).Model);
Assert.Equal(0m ,model.Info.CurrentAmount);
Assert.Equal(1m, model.Info.CurrentPendingAmount);
Assert.Equal(0m, model.Info.ProgressPercentage);
Assert.Equal(1m, model.Info.PendingProgressPercentage);
Logs.Tester.LogInformation("Let's check current amount change once payment is confirmed");
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, tester.ExplorerNode.Network);
tester.ExplorerNode.SendToAddress(invoiceAddress, invoice.BtcDue);
tester.ExplorerNode.Generate(1); // By default invoice confirmed at 1 block
TestUtils.Eventually(() =>
{
model = Assert.IsType<ViewCrowdfundViewModel>(Assert
.IsType<ViewResult>(publicApps.ViewCrowdfund(appId, String.Empty).Result).Model);
Assert.Equal(1m, model.Info.CurrentAmount);
Assert.Equal(0m, model.Info.CurrentPendingAmount);
});
Logs.Tester.LogInformation("Because UseAllStoreInvoices is true, let's make sure the invoice is tagged");
var invoiceEntity = tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id).GetAwaiter().GetResult();
Assert.True(invoiceEntity.Version >= InvoiceEntity.InternalTagSupport_Version);
Assert.Contains(AppService.GetAppInternalTag(appId), invoiceEntity.InternalTags);
crowdfundViewModel.UseAllStoreInvoices = false;
Assert.IsType<RedirectToActionResult>(apps.UpdateCrowdfund(appId, crowdfundViewModel, "save").Result);
Logs.Tester.LogInformation("Because UseAllStoreInvoices is false, let's make sure the invoice is not tagged");
invoice = user.BitPay.CreateInvoice(new Invoice()
{
Buyer = new Buyer() { email = "test@fwf.com" },
Price = 1m,
Currency = "BTC",
PosData = "posData",
ItemDesc = "Some description",
TransactionSpeed = "high",
FullNotifications = true
}, Facade.Merchant);
invoiceEntity = tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id).GetAwaiter().GetResult();
Assert.DoesNotContain(AppService.GetAppInternalTag(appId), invoiceEntity.InternalTags);
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);
invoice = user.BitPay.CreateInvoice(new Invoice()
{
Buyer = new Buyer() { email = "test@fwf.com" },
Price = 1m,
Currency = "BTC",
PosData = "posData",
ItemDesc = "Some description",
TransactionSpeed = "high",
FullNotifications = true
}, Facade.Merchant);
Assert.Equal(0m, model.Info.CurrentPendingAmount);
invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, tester.ExplorerNode.Network);
tester.ExplorerNode.SendToAddress(invoiceAddress, Money.Coins(0.5m));
tester.ExplorerNode.SendToAddress(invoiceAddress, Money.Coins(0.2m));
TestUtils.Eventually(() =>
{
model = Assert.IsType<ViewCrowdfundViewModel>(Assert
.IsType<ViewResult>(publicApps.ViewCrowdfund(appId, String.Empty).Result).Model);
Assert.Equal(0.7m, model.Info.CurrentPendingAmount);
});
}
}
}
}

View File

@ -8,28 +8,32 @@ using Microsoft.AspNetCore.Builder;
using System.Threading.Tasks;
using System.Threading;
using Microsoft.AspNetCore.Hosting.Server.Features;
using System.Threading.Channels;
using System.IO;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
namespace BTCPayServer.Tests
{
public class CustomServer : IDisposable
{
TaskCompletionSource<bool> _Evt = null;
IWebHost _Host = null;
CancellationTokenSource _Closed = new CancellationTokenSource();
Channel<JObject> _Requests = Channel.CreateUnbounded<JObject>();
public CustomServer()
{
{
var port = Utils.FreeTcpPort();
_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);
while (_Act == null)
{
Thread.Sleep(10);
_Closed.Token.ThrowIfCancellationRequested();
}
_Act(req);
_Act = null;
_Evt.TrySetResult(true);
req.Response.StatusCode = 200;
return Task.CompletedTask;
});
})
.UseKestrel()
@ -43,24 +47,22 @@ namespace BTCPayServer.Tests
return new Uri(_Host.ServerFeatures.Get<IServerAddressesFeature>().Addresses.First());
}
public async Task<JObject> GetNextRequest()
Action<HttpContext> _Act;
public void ProcessNextRequest(Action<HttpContext> act)
{
using (CancellationTokenSource cancellation = new CancellationTokenSource(2000000))
var source = new TaskCompletionSource<bool>();
CancellationTokenSource cancellation = new CancellationTokenSource(20000);
cancellation.Token.Register(() => source.TrySetCanceled());
source = new TaskCompletionSource<bool>();
_Evt = source;
_Act = act;
try
{
try
{
JObject req = null;
while(!await _Requests.Reader.WaitToReadAsync(cancellation.Token) ||
!_Requests.Reader.TryRead(out req))
{
}
return req;
}
catch (TaskCanceledException)
{
throw new Xunit.Sdk.XunitException("Callback to the webserver was expected, check if the callback url is accessible from internet");
}
_Evt.Task.GetAwaiter().GetResult();
}
catch (TaskCanceledException)
{
throw new Xunit.Sdk.XunitException("Callback to the webserver was expected, check if the callback url is accessible from internet");
}
}

View File

@ -1,26 +1,12 @@
FROM mcr.microsoft.com/dotnet/core/sdk:3.1.101 AS builder
RUN apt-get update && apt-get install -y --no-install-recommends chromium-driver \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /source
COPY nuget.config nuget.config
COPY Build/Common.csproj Build/Common.csproj
FROM microsoft/dotnet:2.1.300-rc1-sdk-alpine3.7
WORKDIR /app
# caches restore result by copying csproj file separately
COPY BTCPayServer.Tests/BTCPayServer.Tests.csproj BTCPayServer.Tests/BTCPayServer.Tests.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
WORKDIR /app/BTCPayServer.Tests
RUN dotnet restore
# copies the rest of your code
COPY . ../.
COPY . .
RUN cd BTCPayServer.Tests && dotnet build /p:CI_TESTS=true /p:RazorCompileOnBuild=true
WORKDIR /source/BTCPayServer.Tests
ENTRYPOINT ["./docker-entrypoint.sh"]
ENTRYPOINT ["dotnet", "test"]

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

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using BTCPayServer.Lightning.CLightning;
using BTCPayServer.Payments.Lightning.CLightning;
using NBitcoin;
namespace BTCPayServer.Tests
@ -13,10 +13,10 @@ namespace BTCPayServer.Tests
public LightningDTester(ServerTester parent, string environmentName, string defaultRPC, string defaultHost, Network network)
{
this.parent = parent;
RPC = new CLightningClient(new Uri(parent.GetEnvironment(environmentName, defaultRPC)), network);
RPC = new CLightningRPCClient(new Uri(parent.GetEnvironment(environmentName, defaultRPC)), network);
}
public CLightningClient RPC { get; }
public CLightningRPCClient RPC { get; }
public string P2PHost { get; }
}

View File

@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
using BTCPayServer.Lightning.LND;
using NBitcoin;
namespace BTCPayServer.Tests.Lnd
{
public class LndMockTester
{
private ServerTester _Parent;
public LndMockTester(ServerTester serverTester, string environmentName, string defaultValue, string defaultHost, Network network)
{
this._Parent = serverTester;
var url = serverTester.GetEnvironment(environmentName, defaultValue);
Swagger = new LndSwaggerClient(new LndRestSettings(new Uri(url)) { AllowInsecure = true });
Client = new LndClient(Swagger, network);
P2PHost = _Parent.GetEnvironment(environmentName + "_HOST", defaultHost);
}
public LndSwaggerClient Swagger { get; set; }
public LndClient Client { get; set; }
public string P2PHost { get; }
}
}

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