Compare commits
171 Commits
v1.0.3.141
...
v1.0.3.148
Author | SHA1 | Date | |
---|---|---|---|
9ccb472c7a | |||
c35afd5e9a | |||
74adaf1d1f | |||
0fce8d0739 | |||
dbb6408acb | |||
5dbdb4b399 | |||
58d9a48787 | |||
4baeb7bc71 | |||
1a3da096a7 | |||
ba0e501e38 | |||
bff95e4655 | |||
4f03f3c9cb | |||
d48334b97f | |||
90da81f68e | |||
9ba1403f5c | |||
846fd815ff | |||
60e0f775ed | |||
529c2df1cc | |||
430a9eb261 | |||
d3408b91be | |||
d5febb30e7 | |||
63c4ec1809 | |||
6ac8cd19d3 | |||
c95bceef4d | |||
3449bba4b3 | |||
d94b016e63 | |||
629dfcf152 | |||
9876208b7d | |||
df617d5186 | |||
21f715bfc6 | |||
18a2c1a00f | |||
6c2fdecebe | |||
c3b7779ea3 | |||
83ea95ed6d | |||
a816e37621 | |||
33c65a6548 | |||
bf57701cf0 | |||
bfcd90d8d1 | |||
0387306918 | |||
c99d26a55d | |||
7efeeb7c28 | |||
3164783b31 | |||
28c441924a | |||
3b3ec7fc21 | |||
dfa0201726 | |||
2b889d9e29 | |||
08688f69c0 | |||
4a0d29c700 | |||
fa916d4862 | |||
1973647b51 | |||
12133cd7d3 | |||
9b66ba226b | |||
96731fabc7 | |||
87f1ab7caa | |||
4cf6f8e753 | |||
dc59c4ca47 | |||
4f046ed1d3 | |||
d689222e04 | |||
57985e78e5 | |||
731341b749 | |||
f12186e09f | |||
4d7480db15 | |||
0485a9178d | |||
17d2b20cd5 | |||
aa459d0ff3 | |||
656ff7029e | |||
8e00e6d8e3 | |||
8bcb2381a0 | |||
a73d2db02a | |||
47eb087d1b | |||
ed6a01469a | |||
7cfe5f0421 | |||
1aef7f7ea6 | |||
9142c48a0b | |||
45e139c5b7 | |||
6706658377 | |||
a75b6201b7 | |||
f724d6c0cf | |||
0dccbeac3d | |||
7d2dc45dfb | |||
f284ef9052 | |||
80790bd9b0 | |||
2da9434571 | |||
579e0d2e09 | |||
33703b83a3 | |||
23b9dfed2c | |||
b8f6ef8844 | |||
6f6b4c8ead | |||
5d87dd5861 | |||
02c8bf4469 | |||
540cb912f3 | |||
93f490f570 | |||
de2e222ae5 | |||
12055a000b | |||
6addb3e481 | |||
d9cd916440 | |||
452a705b75 | |||
1c8206c749 | |||
062c42e524 | |||
2d932ebb21 | |||
0e0fa53517 | |||
4e20730379 | |||
1d70d935b8 | |||
9b8f42cdf6 | |||
eb85b1a7b4 | |||
df7e2073df | |||
84d943d6cc | |||
a1d82b0e8b | |||
ab7c124302 | |||
544597348b | |||
1559c510be | |||
3e08581e9b | |||
49c70d9b04 | |||
50e7d8389c | |||
ea5bd6d435 | |||
cbb37674e3 | |||
e70d5ceb08 | |||
5d7b253edd | |||
df38b84bbb | |||
a3fc75ea3b | |||
71a8166027 | |||
564dd95d81 | |||
eb16b435df | |||
f94daed06d | |||
e841bfa148 | |||
03b76380e7 | |||
c6671f7122 | |||
db56c2b106 | |||
f7b2c836b7 | |||
6ec379b538 | |||
d6e1d34ebf | |||
bc2a039ea2 | |||
e31e600144 | |||
91f83d751f | |||
b8288f1efa | |||
fa61c2bcdd | |||
2d168e1d9b | |||
3e7ad4a4f6 | |||
d2357ee8b9 | |||
9f04c7d0bc | |||
9baa7eed80 | |||
287fbc523f | |||
ea6df35c87 | |||
2e0db1a430 | |||
7ccf0c3612 | |||
1b58058796 | |||
98276bcb3d | |||
1aa4dbb90d | |||
eef623fb4b | |||
84bbbcbe10 | |||
a324e2aeaf | |||
d6b3530384 | |||
dfe8a10e1a | |||
5afa847e6e | |||
1bcbc9bab0 | |||
b915544798 | |||
2b9f390c64 | |||
ee42d5c7b4 | |||
f809dd51a6 | |||
1a8d6e5c05 | |||
869ba745b2 | |||
180dfb6edf | |||
45b08ac8d2 | |||
9a4b385432 | |||
08289b89c5 | |||
a31d1d81c8 | |||
e4c7bb0378 | |||
374aaf2e2b | |||
52fd686993 | |||
03c36ef0d2 | |||
71a6ffac2e |
@ -66,6 +66,20 @@ jobs:
|
||||
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
|
||||
@ -80,9 +94,10 @@ jobs:
|
||||
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
|
||||
#
|
||||
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
|
||||
sudo docker manifest create --amend $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-amd64 $DOCKERHUB_REPO:$LATEST_TAG-arm32v7
|
||||
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:
|
||||
@ -114,10 +129,17 @@ workflows:
|
||||
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]+)/
|
||||
- multiarch:
|
||||
requires:
|
||||
- amd64
|
||||
- arm32v7
|
||||
- arm64v8
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
|
@ -17,7 +17,6 @@ namespace BTCPayServer
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Bitcoin",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/tx/{0}" : "https://blockstream.info/testnet/tx/{0}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "bitcoin",
|
||||
CryptoImagePath = "imlegacy/bitcoin.svg",
|
||||
|
@ -12,7 +12,6 @@ namespace BTCPayServer
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "BGold",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://explorer.bitcoingold.org/insight/tx/{0}/" : "https://test-explorer.bitcoingold.org/insight/tx/{0}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "bitcoingold",
|
||||
DefaultRateRules = new[]
|
||||
|
@ -17,7 +17,6 @@ namespace BTCPayServer
|
||||
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}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "bitcoinplus",
|
||||
DefaultRateRules = new[]
|
||||
|
@ -17,7 +17,6 @@ namespace BTCPayServer
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Bitcore",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://insight.bitcore.cc/tx/{0}" : "https://insight.bitcore.cc/tx/{0}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "bitcore",
|
||||
DefaultRateRules = new[]
|
||||
|
@ -15,7 +15,6 @@ namespace BTCPayServer
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet
|
||||
? "https://insight.dash.org/insight/tx/{0}"
|
||||
: "https://testnet-insight.dashevo.org/insight/tx/{0}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "dash",
|
||||
DefaultRateRules = new[]
|
||||
|
@ -17,7 +17,6 @@ namespace BTCPayServer
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Dogecoin",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://dogechain.info/tx/{0}" : "https://dogechain.info/tx/{0}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "dogecoin",
|
||||
DefaultRateRules = new[]
|
||||
|
@ -17,7 +17,6 @@ namespace BTCPayServer
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Feathercoin",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://explorer.feathercoin.com/tx/{0}" : "https://explorer.feathercoin.com/tx/{0}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "feathercoin",
|
||||
DefaultRateRules = new[]
|
||||
|
@ -18,7 +18,6 @@ namespace BTCPayServer
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet
|
||||
? "https://chainz.cryptoid.info/grs/tx.dws?{0}.htm"
|
||||
: "https://chainz.cryptoid.info/grs-test/tx.dws?{0}.htm",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "groestlcoin",
|
||||
DefaultRateRules = new[]
|
||||
|
@ -19,9 +19,13 @@ namespace BTCPayServer
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet
|
||||
? "https://live.blockcypher.com/ltc/tx/{0}/"
|
||||
: "http://explorer.litecointools.com/tx/{0}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
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),
|
||||
|
@ -17,7 +17,6 @@ namespace BTCPayServer
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Monacoin",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://mona.insight.monaco-ex.org/insight/tx/{0}" : "https://testnet-mona.insight.monaco-ex.org/insight/tx/{0}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "monacoin",
|
||||
DefaultRateRules = new[]
|
||||
|
@ -17,7 +17,6 @@ namespace BTCPayServer
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Polis",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://insight.polispay.org/tx/{0}" : "https://insight.polispay.org/tx/{0}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "polis",
|
||||
DefaultRateRules = new[]
|
||||
|
@ -17,7 +17,6 @@ namespace BTCPayServer
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Ufo",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://chainz.cryptoid.info/ufo/tx.dws?{0}" : "https://chainz.cryptoid.info/ufo/tx.dws?{0}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "ufo",
|
||||
DefaultRateRules = new[]
|
||||
|
@ -17,7 +17,6 @@ namespace BTCPayServer
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Viacoin",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://explorer.viacoin.org/tx/{0}" : "https://explorer.viacoin.org/tx/{0}",
|
||||
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "viacoin",
|
||||
DefaultRateRules = new[]
|
||||
|
@ -47,6 +47,8 @@ namespace BTCPayServer
|
||||
_NBXplorerNetworkProvider = new NBXplorerNetworkProvider(networkType);
|
||||
NetworkType = networkType;
|
||||
InitBitcoin();
|
||||
InitLiquid();
|
||||
InitLiquidAssets();
|
||||
InitLitecoin();
|
||||
InitBitcore();
|
||||
InitDogecoin();
|
||||
@ -93,6 +95,12 @@ namespace BTCPayServer
|
||||
[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);
|
||||
@ -109,7 +117,7 @@ namespace BTCPayServer
|
||||
}
|
||||
public BTCPayNetworkBase GetNetwork(string cryptoCode)
|
||||
{
|
||||
return GetNetwork<BTCPayNetworkBase>(cryptoCode);
|
||||
return GetNetwork<BTCPayNetworkBase>(cryptoCode.ToUpperInvariant());
|
||||
}
|
||||
public T GetNetwork<T>(string cryptoCode) where T: BTCPayNetworkBase
|
||||
{
|
||||
|
@ -0,0 +1,54 @@
|
||||
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.svg",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
SupportRBF = true,
|
||||
//https://github.com/spesmilo/electrum/blob/11733d6bc271646a00b69ff07657119598874da4/electrum/constants.py
|
||||
ElectrumMapping = NetworkType == NetworkType.Mainnet
|
||||
? new Dictionary<uint, DerivationType>()
|
||||
{
|
||||
{0x0488b21eU, DerivationType.Legacy }, // xpub
|
||||
{0x049d7cb2U, DerivationType.SegwitP2SH }, // ypub
|
||||
{0x4b24746U, DerivationType.Segwit }, //zpub
|
||||
}
|
||||
: new Dictionary<uint, DerivationType>()
|
||||
{
|
||||
{0x043587cfU, DerivationType.Legacy},
|
||||
{0x044a5262U, DerivationType.SegwitP2SH},
|
||||
{0x045f1cf6U, DerivationType.Segwit}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public partial class BTCPayNetworkProvider
|
||||
{
|
||||
public void InitLiquidAssets()
|
||||
{
|
||||
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("LBTC");
|
||||
Add(new ElementsBTCPayNetwork()
|
||||
{
|
||||
CryptoCode = "USDt",
|
||||
NetworkCryptoCode = "LBTC",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"USDT_UST = 1",
|
||||
"USDT_X = USDT_BTC * BTC_X",
|
||||
"USDT_BTC = bitfinex(UST_BTC)",
|
||||
},
|
||||
AssetId = new uint256("ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2"),
|
||||
DisplayName = "Tether USD",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "liquidnetwork",
|
||||
CryptoImagePath = "imlegacy/liquid-tether.svg",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
SupportRBF = true,
|
||||
//https://github.com/spesmilo/electrum/blob/11733d6bc271646a00b69ff07657119598874da4/electrum/constants.py
|
||||
ElectrumMapping = NetworkType == NetworkType.Mainnet
|
||||
? new Dictionary<uint, DerivationType>()
|
||||
{
|
||||
{0x0488b21eU, DerivationType.Legacy }, // xpub
|
||||
{0x049d7cb2U, DerivationType.SegwitP2SH }, // ypub
|
||||
{0x4b24746U, DerivationType.Segwit }, //zpub
|
||||
}
|
||||
: new Dictionary<uint, DerivationType>()
|
||||
{
|
||||
{0x043587cfU, DerivationType.Legacy},
|
||||
{0x044a5262U, DerivationType.SegwitP2SH},
|
||||
{0x045f1cf6U, DerivationType.Segwit}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using NBXplorer.Models;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class ElementsBTCPayNetwork:BTCPayNetwork
|
||||
{
|
||||
public string NetworkCryptoCode { get; set; }
|
||||
public uint256 AssetId { get; set; }
|
||||
public override bool ReadonlyWallet { get; set; } = true;
|
||||
public int Divisibility { get; set; } = 8;
|
||||
|
||||
public override IEnumerable<(MatchedOutput matchedOutput, OutPoint outPoint)> GetValidOutputs(NewTransactionEvent evtOutputs)
|
||||
{
|
||||
return evtOutputs.Outputs.Where(output =>
|
||||
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId).Select(output =>
|
||||
{
|
||||
var outpoint = new OutPoint(evtOutputs.TransactionData.TransactionHash, output.Index);
|
||||
return (output, outpoint);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -14,6 +14,11 @@ namespace BTCPayServer
|
||||
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"
|
||||
});
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer
|
||||
@ -46,7 +47,7 @@ namespace BTCPayServer
|
||||
|
||||
public class BTCPayNetwork:BTCPayNetworkBase
|
||||
{
|
||||
public Network NBitcoinNetwork { get; set; }
|
||||
public Network NBitcoinNetwork { get { return NBXplorerNetwork?.NBitcoinNetwork; } }
|
||||
public NBXplorer.NBXplorerNetwork NBXplorerNetwork { get; set; }
|
||||
public bool SupportRBF { get; internal set; }
|
||||
public string LightningImagePath { get; set; }
|
||||
@ -55,6 +56,9 @@ namespace BTCPayServer
|
||||
|
||||
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)
|
||||
@ -100,6 +104,14 @@ namespace BTCPayServer
|
||||
{
|
||||
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 abstract class BTCPayNetworkBase
|
||||
|
@ -3,8 +3,7 @@
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.9" Condition="'$(TargetFramework)' == 'netcoreapp2.1'" />
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" Condition="'$(TargetFramework)' != 'netcoreapp2.1'" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="2.0.0.26" />
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="3.0.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -1,4 +1,3 @@
|
||||
#if !NETCOREAPP21
|
||||
// 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.
|
||||
|
||||
@ -235,25 +234,3 @@ namespace Microsoft.Extensions.Logging.Abstractions.Internal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#else
|
||||
using Microsoft.Extensions.Options;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides programmatic configuration for JSON formatters using Newtonsoft.JSON.
|
||||
/// </summary>
|
||||
public class MvcNewtonsoftJsonOptions
|
||||
{
|
||||
IOptions<MvcJsonOptions> jsonOptions;
|
||||
public MvcNewtonsoftJsonOptions(IOptions<MvcJsonOptions> jsonOptions)
|
||||
{
|
||||
this.jsonOptions = jsonOptions;
|
||||
}
|
||||
public JsonSerializerSettings SerializerSettings => this.jsonOptions.Value.SerializerSettings;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -1,19 +1,12 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" AllowExplicitVersion="true" Version="2.1.9" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.1.2" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.1.2" />
|
||||
<PackageReference Include="OpenIddict.EntityFrameworkCore" Version="3.0.0-alpha1.19515.63" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp2.1'">
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.0.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.0.1" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.0.0-rc1.final" />
|
||||
<PackageReference Include="OpenIddict.EntityFrameworkCore" Version="3.0.0-alpha1.19515.63" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.0" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.0" />
|
||||
<PackageReference Include="OpenIddict.EntityFrameworkCore" Version="3.0.0-alpha1.20058.15" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.0" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
@ -9,10 +9,23 @@ namespace BTCPayServer.Data
|
||||
{
|
||||
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
|
||||
{
|
||||
//public ApplicationDbContext(): base(CreateMySql())
|
||||
//{
|
||||
|
||||
//}
|
||||
|
||||
//private static DbContextOptions CreateMySql()
|
||||
//{
|
||||
// return new DbContextOptionsBuilder<ApplicationDbContext>()
|
||||
// .UseMySql("Server=myServerAddress;Database=myDataBase;Uid=myUsername;Pwd=myPassword;")
|
||||
// .Options;
|
||||
//}
|
||||
|
||||
public ApplicationDbContext()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
|
@ -45,11 +45,7 @@ namespace BTCPayServer.Data
|
||||
|
||||
class CustomNpgsqlMigrationsSqlGenerator : NpgsqlMigrationsSqlGenerator
|
||||
{
|
||||
#if NETCOREAPP21
|
||||
public CustomNpgsqlMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies) : base(dependencies)
|
||||
#else
|
||||
public CustomNpgsqlMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies, IMigrationsAnnotationProvider annotations, Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal.INpgsqlOptions opts) : base(dependencies, annotations, opts)
|
||||
#endif
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -12,11 +12,12 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AspNetRoles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(nullable: false),
|
||||
Id = table.Column<string>(nullable: false, maxLength: maxLength),
|
||||
ConcurrencyStamp = table.Column<string>(nullable: true),
|
||||
Name = table.Column<string>(maxLength: 256, nullable: true),
|
||||
NormalizedName = table.Column<string>(maxLength: 256, nullable: true)
|
||||
@ -30,7 +31,7 @@ namespace BTCPayServer.Migrations
|
||||
name: "AspNetUsers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(nullable: false),
|
||||
Id = table.Column<string>(nullable: false, maxLength: maxLength),
|
||||
AccessFailedCount = table.Column<int>(nullable: false),
|
||||
ConcurrencyStamp = table.Column<string>(nullable: true),
|
||||
Email = table.Column<string>(maxLength: 256, nullable: true),
|
||||
@ -55,7 +56,7 @@ namespace BTCPayServer.Migrations
|
||||
name: "Stores",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(nullable: false),
|
||||
Id = table.Column<string>(nullable: false, maxLength: maxLength),
|
||||
DerivationStrategy = table.Column<string>(nullable: true),
|
||||
SpeedPolicy = table.Column<int>(nullable: false),
|
||||
StoreCertificate = table.Column<byte[]>(nullable: true),
|
||||
@ -75,7 +76,7 @@ namespace BTCPayServer.Migrations
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
ClaimType = table.Column<string>(nullable: true),
|
||||
ClaimValue = table.Column<string>(nullable: true),
|
||||
RoleId = table.Column<string>(nullable: false)
|
||||
RoleId = table.Column<string>(nullable: false, maxLength: maxLength)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@ -96,7 +97,7 @@ namespace BTCPayServer.Migrations
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
ClaimType = table.Column<string>(nullable: true),
|
||||
ClaimValue = table.Column<string>(nullable: true),
|
||||
UserId = table.Column<string>(nullable: false)
|
||||
UserId = table.Column<string>(nullable: false, maxLength: maxLength)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@ -116,7 +117,7 @@ namespace BTCPayServer.Migrations
|
||||
LoginProvider = table.Column<string>(nullable: false),
|
||||
ProviderKey = table.Column<string>(nullable: false),
|
||||
ProviderDisplayName = table.Column<string>(nullable: true),
|
||||
UserId = table.Column<string>(nullable: false)
|
||||
UserId = table.Column<string>(nullable: false, maxLength: maxLength)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@ -133,8 +134,8 @@ namespace BTCPayServer.Migrations
|
||||
name: "AspNetUserRoles",
|
||||
columns: table => new
|
||||
{
|
||||
UserId = table.Column<string>(nullable: false),
|
||||
RoleId = table.Column<string>(nullable: false)
|
||||
UserId = table.Column<string>(nullable: false, maxLength: maxLength),
|
||||
RoleId = table.Column<string>(nullable: false, maxLength: maxLength)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@ -157,7 +158,7 @@ namespace BTCPayServer.Migrations
|
||||
name: "AspNetUserTokens",
|
||||
columns: table => new
|
||||
{
|
||||
UserId = table.Column<string>(nullable: false),
|
||||
UserId = table.Column<string>(nullable: false, maxLength: maxLength),
|
||||
LoginProvider = table.Column<string>(nullable: false),
|
||||
Name = table.Column<string>(nullable: false),
|
||||
Value = table.Column<string>(nullable: true)
|
||||
@ -177,7 +178,7 @@ namespace BTCPayServer.Migrations
|
||||
name: "Invoices",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(nullable: false),
|
||||
Id = table.Column<string>(nullable: false, maxLength: maxLength),
|
||||
Blob = table.Column<byte[]>(nullable: true),
|
||||
Created = table.Column<DateTimeOffset>(nullable: false),
|
||||
CustomerEmail = table.Column<string>(nullable: true),
|
||||
@ -185,7 +186,7 @@ namespace BTCPayServer.Migrations
|
||||
ItemCode = table.Column<string>(nullable: true),
|
||||
OrderId = table.Column<string>(nullable: true),
|
||||
Status = table.Column<string>(nullable: true),
|
||||
StoreDataId = table.Column<string>(nullable: true)
|
||||
StoreDataId = table.Column<string>(nullable: true, maxLength: maxLength)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@ -202,8 +203,8 @@ namespace BTCPayServer.Migrations
|
||||
name: "UserStore",
|
||||
columns: table => new
|
||||
{
|
||||
ApplicationUserId = table.Column<string>(nullable: false),
|
||||
StoreDataId = table.Column<string>(nullable: false),
|
||||
ApplicationUserId = table.Column<string>(nullable: false, maxLength: maxLength),
|
||||
StoreDataId = table.Column<string>(nullable: false, maxLength: maxLength),
|
||||
Role = table.Column<string>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
@ -227,9 +228,9 @@ namespace BTCPayServer.Migrations
|
||||
name: "Payments",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(nullable: false),
|
||||
Id = table.Column<string>(nullable: false, maxLength: maxLength),
|
||||
Blob = table.Column<byte[]>(nullable: true),
|
||||
InvoiceDataId = table.Column<string>(nullable: true)
|
||||
InvoiceDataId = table.Column<string>(nullable: true, maxLength: maxLength)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@ -246,9 +247,9 @@ namespace BTCPayServer.Migrations
|
||||
name: "RefundAddresses",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(nullable: false),
|
||||
Id = table.Column<string>(nullable: false, maxLength: maxLength),
|
||||
Blob = table.Column<byte[]>(nullable: true),
|
||||
InvoiceDataId = table.Column<string>(nullable: true)
|
||||
InvoiceDataId = table.Column<string>(nullable: true, maxLength: maxLength)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
|
@ -12,11 +12,12 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Settings",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(nullable: false),
|
||||
Id = table.Column<string>(nullable: false, maxLength: maxLength),
|
||||
Value = table.Column<string>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
|
@ -12,6 +12,7 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "RequiresEmailConfirmation",
|
||||
table: "AspNetUsers",
|
||||
|
@ -12,12 +12,13 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
migrationBuilder.CreateTable(
|
||||
name: "AddressInvoices",
|
||||
columns: table => new
|
||||
{
|
||||
Address = table.Column<string>(nullable: false),
|
||||
InvoiceDataId = table.Column<string>(nullable: true)
|
||||
Address = table.Column<string>(nullable: false, maxLength: this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)512 : null),
|
||||
InvoiceDataId = table.Column<string>(nullable: true, maxLength: maxLength)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
|
@ -12,17 +12,18 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
migrationBuilder.CreateTable(
|
||||
name: "PairedSINData",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(nullable: false),
|
||||
Id = table.Column<string>(nullable: false, maxLength: maxLength),
|
||||
Facade = table.Column<string>(nullable: true),
|
||||
Label = table.Column<string>(nullable: true),
|
||||
Name = table.Column<string>(nullable: true),
|
||||
PairingTime = table.Column<DateTimeOffset>(nullable: false),
|
||||
SIN = table.Column<string>(nullable: true),
|
||||
StoreDataId = table.Column<string>(nullable: true)
|
||||
StoreDataId = table.Column<string>(nullable: true, maxLength: maxLength)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@ -33,14 +34,14 @@ namespace BTCPayServer.Migrations
|
||||
name: "PairingCodes",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(nullable: false),
|
||||
Id = table.Column<string>(nullable: false, maxLength: maxLength),
|
||||
DateCreated = table.Column<DateTime>(nullable: false),
|
||||
Expiration = table.Column<DateTimeOffset>(nullable: false),
|
||||
Facade = table.Column<string>(nullable: true),
|
||||
Label = table.Column<string>(nullable: true),
|
||||
Name = table.Column<string>(nullable: true),
|
||||
SIN = table.Column<string>(nullable: true),
|
||||
StoreDataId = table.Column<string>(nullable: true),
|
||||
StoreDataId = table.Column<string>(nullable: true, maxLength: maxLength),
|
||||
TokenValue = table.Column<string>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
|
@ -12,6 +12,7 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
if (this.SupportDropColumn(migrationBuilder.ActiveProvider))
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
@ -26,7 +27,7 @@ namespace BTCPayServer.Migrations
|
||||
name: "PendingInvoices",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(nullable: false)
|
||||
Id = table.Column<string>(nullable: false, maxLength: maxLength)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
|
@ -12,6 +12,7 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
migrationBuilder.AddColumn<byte[]>(
|
||||
name: "StoreBlob",
|
||||
table: "Stores",
|
||||
|
@ -12,6 +12,7 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||
name: "CreatedTime",
|
||||
table: "AddressInvoices",
|
||||
@ -21,7 +22,7 @@ namespace BTCPayServer.Migrations
|
||||
name: "HistoricalAddressInvoices",
|
||||
columns: table => new
|
||||
{
|
||||
InvoiceDataId = table.Column<string>(nullable: false),
|
||||
InvoiceDataId = table.Column<string>(nullable: false, maxLength: maxLength),
|
||||
Address = table.Column<string>(nullable: false),
|
||||
Assigned = table.Column<DateTimeOffset>(nullable: false),
|
||||
UnAssigned = table.Column<DateTimeOffset>(nullable: true)
|
||||
|
@ -12,6 +12,7 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "Accounted",
|
||||
table: "Payments",
|
||||
|
@ -12,6 +12,7 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "CryptoCode",
|
||||
table: "HistoricalAddressInvoices",
|
||||
|
@ -12,6 +12,7 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "DerivationStrategies",
|
||||
table: "Stores",
|
||||
|
@ -12,6 +12,7 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "DefaultCrypto",
|
||||
table: "Stores",
|
||||
|
@ -12,12 +12,13 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
migrationBuilder.CreateTable(
|
||||
name: "InvoiceEvents",
|
||||
columns: table => new
|
||||
{
|
||||
InvoiceDataId = table.Column<string>(nullable: false),
|
||||
UniqueId = table.Column<string>(nullable: false),
|
||||
InvoiceDataId = table.Column<string>(nullable: false, maxLength: maxLength),
|
||||
UniqueId = table.Column<string>(nullable: false, maxLength: maxLength),
|
||||
Message = table.Column<string>(nullable: true),
|
||||
Timestamp = table.Column<DateTimeOffset>(nullable: false)
|
||||
},
|
||||
|
@ -12,16 +12,17 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Apps",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(nullable: false),
|
||||
Id = table.Column<string>(nullable: false, maxLength: maxLength),
|
||||
AppType = table.Column<string>(nullable: true),
|
||||
Created = table.Column<DateTimeOffset>(nullable: false),
|
||||
Name = table.Column<string>(nullable: true),
|
||||
Settings = table.Column<string>(nullable: true),
|
||||
StoreDataId = table.Column<string>(nullable: true)
|
||||
StoreDataId = table.Column<string>(nullable: true, maxLength: maxLength)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
|
@ -12,6 +12,7 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ApiKeys",
|
||||
columns: table => new
|
||||
|
@ -10,6 +10,7 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
if (this.SupportDropForeignKey(migrationBuilder.ActiveProvider))
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
|
@ -11,12 +11,13 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
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),
|
||||
StoreDataId = table.Column<string>(nullable: true),
|
||||
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)
|
||||
},
|
||||
|
@ -10,6 +10,7 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "TagAllInvoices",
|
||||
table: "Apps",
|
||||
|
@ -11,6 +11,7 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
migrationBuilder.CreateTable(
|
||||
name: "OpenIddictApplications",
|
||||
columns: table => new
|
||||
@ -20,13 +21,13 @@ namespace BTCPayServer.Migrations
|
||||
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),
|
||||
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)
|
||||
ApplicationUserId = table.Column<string>(nullable: true, maxLength: maxLength)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@ -46,7 +47,7 @@ namespace BTCPayServer.Migrations
|
||||
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),
|
||||
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)
|
||||
@ -60,9 +61,9 @@ namespace BTCPayServer.Migrations
|
||||
name: "OpenIddictAuthorizations",
|
||||
columns: table => new
|
||||
{
|
||||
ApplicationId = table.Column<string>(nullable: true),
|
||||
ApplicationId = table.Column<string>(nullable: true, maxLength: maxLength),
|
||||
ConcurrencyToken = table.Column<string>(maxLength: 50, nullable: true),
|
||||
Id = table.Column<string>(nullable: false),
|
||||
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),
|
||||
@ -84,12 +85,12 @@ namespace BTCPayServer.Migrations
|
||||
name: "OpenIddictTokens",
|
||||
columns: table => new
|
||||
{
|
||||
ApplicationId = table.Column<string>(nullable: true),
|
||||
AuthorizationId = table.Column<string>(nullable: true),
|
||||
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),
|
||||
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),
|
||||
|
@ -11,15 +11,16 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
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),
|
||||
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)
|
||||
ApplicationUserId = table.Column<string>(nullable: true, maxLength: maxLength)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
|
@ -11,6 +11,7 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
if (this.SupportDropColumn(migrationBuilder.ActiveProvider))
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
@ -22,13 +23,13 @@ namespace BTCPayServer.Migrations
|
||||
name: "U2FDevices",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<string>(nullable: false),
|
||||
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)
|
||||
ApplicationUserId = table.Column<string>(nullable: true, maxLength: maxLength)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
|
@ -11,6 +11,7 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||
name: "Created",
|
||||
table: "PaymentRequests",
|
||||
|
@ -11,11 +11,12 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
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),
|
||||
Id = table.Column<string>(nullable: false, maxLength: maxLength),
|
||||
Blob = table.Column<byte[]>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
@ -27,8 +28,8 @@ namespace BTCPayServer.Migrations
|
||||
name: "WalletTransactions",
|
||||
columns: table => new
|
||||
{
|
||||
WalletDataId = table.Column<string>(nullable: false),
|
||||
TransactionId = table.Column<string>(nullable: false),
|
||||
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)
|
||||
},
|
||||
|
@ -0,0 +1,60 @@
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Requirements",
|
||||
table: "OpenIddictApplications",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -20,5 +20,9 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
return facade.ProviderName != "Microsoft.EntityFrameworkCore.Sqlite";
|
||||
}
|
||||
public static bool IsMySql(this Microsoft.EntityFrameworkCore.Migrations.Migration migration, string activeProvider)
|
||||
{
|
||||
return activeProvider == "Pomelo.EntityFrameworkCore.MySql";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
24
BTCPayServer.Rating/AvailableRateProvider.cs
Normal file
24
BTCPayServer.Rating/AvailableRateProvider.cs
Normal file
@ -0,0 +1,24 @@
|
||||
namespace BTCPayServer.Rating
|
||||
{
|
||||
public enum RateSource
|
||||
{
|
||||
Coingecko,
|
||||
CoinAverage,
|
||||
Direct
|
||||
}
|
||||
public class AvailableRateProvider
|
||||
{
|
||||
public string Name { get; }
|
||||
public string Url { get; }
|
||||
public string Id { get; }
|
||||
public RateSource Source { get; }
|
||||
|
||||
public AvailableRateProvider(string id, string name, string url, RateSource source)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Url = url;
|
||||
Source = source;
|
||||
}
|
||||
}
|
||||
}
|
@ -7,11 +7,10 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.9" Condition="'$(TargetFramework)' == 'netcoreapp2.1'" />
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" Condition="'$(TargetFramework)' != 'netcoreapp2.1'" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.3.1" Condition="'$(TargetFramework)' != 'netcoreapp2.1'" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" Condition="'$(TargetFramework)' != 'netcoreapp2.1'" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<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>
|
||||
|
||||
|
@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Rating
|
||||
{
|
||||
|
@ -3,7 +3,6 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BTCPayServer.Rating
|
||||
{
|
||||
|
@ -8,16 +8,62 @@ 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 exchange
|
||||
/// </summary>
|
||||
public class BackgroundFetcherRateProvider : IRateProvider
|
||||
{
|
||||
public class LatestFetch
|
||||
{
|
||||
public ExchangeRates Latest;
|
||||
public DateTimeOffset NextRefresh;
|
||||
public TimeSpan Backoff = TimeSpan.FromSeconds(5.0);
|
||||
public TimeSpan Backoff = TimeSpan.FromSeconds(5.0);
|
||||
public DateTimeOffset Updated;
|
||||
public DateTimeOffset Expiration;
|
||||
public Exception Exception;
|
||||
public string ExchangeName;
|
||||
@ -39,12 +85,57 @@ namespace BTCPayServer.Services.Rates
|
||||
}
|
||||
|
||||
IRateProvider _Inner;
|
||||
public IRateProvider Inner => _Inner;
|
||||
|
||||
public BackgroundFetcherRateProvider(IRateProvider inner)
|
||||
public BackgroundFetcherRateProvider(string exchangeName, IRateProvider inner)
|
||||
{
|
||||
if (inner == null)
|
||||
throw new ArgumentNullException(nameof(inner));
|
||||
if (exchangeName == null)
|
||||
throw new ArgumentNullException(nameof(exchangeName));
|
||||
_Inner = inner;
|
||||
ExchangeName = exchangeName;
|
||||
}
|
||||
|
||||
public BackgroundFetcherState GetState()
|
||||
{
|
||||
var state = new BackgroundFetcherState()
|
||||
{
|
||||
ExchangeName = ExchangeName,
|
||||
LastRequested = LastRequested
|
||||
};
|
||||
if (_Latest is LatestFetch fetch)
|
||||
{
|
||||
state.LastUpdated = fetch.Updated;
|
||||
state.Rates = fetch.Latest
|
||||
.Where(e => e.Exchange == ExchangeName)
|
||||
.Select(r => new BackgroundFetcherRate()
|
||||
{
|
||||
Pair = r.CurrencyPair,
|
||||
BidAsk = r.BidAsk
|
||||
}).ToList();
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
public void LoadState(BackgroundFetcherState state)
|
||||
{
|
||||
if (ExchangeName != state.ExchangeName)
|
||||
throw new InvalidOperationException("The state does not belong to this fetcher");
|
||||
if (state.LastRequested is DateTimeOffset lastRequested)
|
||||
this.LastRequested = state.LastRequested;
|
||||
if (state.LastUpdated is DateTimeOffset updated && state.Rates is List<BackgroundFetcherRate> rates)
|
||||
{
|
||||
var fetch = new LatestFetch()
|
||||
{
|
||||
ExchangeName = state.ExchangeName,
|
||||
Latest = new ExchangeRates(rates.Select(r => new ExchangeRate(state.ExchangeName, r.Pair, r.BidAsk))),
|
||||
Updated = updated,
|
||||
NextRefresh = updated + RefreshRate,
|
||||
Expiration = updated + ValidatyTime
|
||||
};
|
||||
_Latest = fetch;
|
||||
}
|
||||
}
|
||||
|
||||
TimeSpan _RefreshRate = TimeSpan.FromSeconds(30);
|
||||
@ -112,33 +203,46 @@ namespace BTCPayServer.Services.Rates
|
||||
LatestFetch _Latest;
|
||||
public async Task<ExchangeRates> GetRatesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
LastRequested = DateTimeOffset.UtcNow;
|
||||
var latest = _Latest;
|
||||
if (!DoNotAutoFetchIfExpired && latest != null && latest.Expiration <= DateTimeOffset.UtcNow + TimeSpan.FromSeconds(1.0))
|
||||
{
|
||||
Logs.PayServer.LogWarning($"GetRatesAsync was called on {GetExchangeName()} when the rate is outdated. It should never happen, let BTCPayServer developers know about this.");
|
||||
latest = null;
|
||||
}
|
||||
return (latest ?? (await Fetch(cancellationToken))).GetResult();
|
||||
}
|
||||
|
||||
private string GetExchangeName()
|
||||
/// <summary>
|
||||
/// The last time this rate provider has been used
|
||||
/// </summary>
|
||||
public DateTimeOffset? LastRequested { get; set; }
|
||||
|
||||
public string ExchangeName { get; }
|
||||
public DateTimeOffset? Expiration
|
||||
{
|
||||
if (_Inner is IHasExchangeName exchangeName)
|
||||
return exchangeName.ExchangeName ?? "???";
|
||||
return "???";
|
||||
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();
|
||||
fetch.ExchangeName = GetExchangeName();
|
||||
fetch.ExchangeName = ExchangeName;
|
||||
try
|
||||
{
|
||||
var rates = await _Inner.GetRatesAsync(cancellationToken);
|
||||
fetch.Latest = rates;
|
||||
fetch.Expiration = DateTimeOffset.UtcNow + ValidatyTime;
|
||||
fetch.NextRefresh = DateTimeOffset.UtcNow + RefreshRate;
|
||||
fetch.Updated = DateTimeOffset.UtcNow;
|
||||
fetch.Expiration = fetch.Updated + ValidatyTime;
|
||||
fetch.NextRefresh = fetch.Updated + RefreshRate;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -1,13 +1,9 @@
|
||||
using Newtonsoft.Json;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.ComponentModel;
|
||||
using BTCPayServer.Rating;
|
||||
@ -23,19 +19,6 @@ namespace BTCPayServer.Services.Rates
|
||||
}
|
||||
}
|
||||
|
||||
public class GetExchangeTickersResponse
|
||||
{
|
||||
public class Exchange
|
||||
{
|
||||
public string Name { get; set; }
|
||||
[JsonProperty("display_name")]
|
||||
public string DisplayName { get; set; }
|
||||
public string[] Symbols { get; set; }
|
||||
}
|
||||
public bool Success { get; set; }
|
||||
public Exchange[] Exchanges { get; set; }
|
||||
}
|
||||
|
||||
public class RatesSetting
|
||||
{
|
||||
public string PublicKey { get; set; }
|
||||
@ -200,32 +183,6 @@ namespace BTCPayServer.Services.Rates
|
||||
response.RequestsPerPeriod = jobj["requests_per_period"].Value<int>();
|
||||
return response;
|
||||
}
|
||||
|
||||
public async Task<GetExchangeTickersResponse> GetExchangeTickersAsync()
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, "https://apiv2.bitcoinaverage.com/symbols/exchanges/ticker");
|
||||
var auth = Authenticator;
|
||||
if (auth != null)
|
||||
{
|
||||
await auth.AddHeader(request);
|
||||
}
|
||||
var resp = await HttpClient.SendAsync(request);
|
||||
resp.EnsureSuccessStatusCode();
|
||||
var jobj = JObject.Parse(await resp.Content.ReadAsStringAsync());
|
||||
var response = new GetExchangeTickersResponse();
|
||||
response.Success = jobj["success"].Value<bool>();
|
||||
var exchanges = (JObject)jobj["exchanges"];
|
||||
response.Exchanges = exchanges
|
||||
.Properties()
|
||||
.Select(p =>
|
||||
{
|
||||
var exchange = JsonConvert.DeserializeObject<GetExchangeTickersResponse.Exchange>(p.Value.ToString());
|
||||
exchange.Name = p.Name;
|
||||
return exchange;
|
||||
})
|
||||
.ToArray();
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
public class GetRateLimitsResponse
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
@ -20,125 +18,12 @@ namespace BTCPayServer.Services.Rates
|
||||
return _Settings.AddHeader(message);
|
||||
}
|
||||
}
|
||||
|
||||
public class CoinAverageExchange
|
||||
{
|
||||
public CoinAverageExchange(string name, string display, string url)
|
||||
{
|
||||
Name = name;
|
||||
Display = display;
|
||||
Url = url;
|
||||
}
|
||||
public string Name { get; set; }
|
||||
public string Display { get; set; }
|
||||
public string Url
|
||||
{
|
||||
get;
|
||||
set;
|
||||
}
|
||||
}
|
||||
public class CoinAverageExchanges : Dictionary<string, CoinAverageExchange>
|
||||
{
|
||||
public CoinAverageExchanges()
|
||||
{
|
||||
}
|
||||
|
||||
public void Add(CoinAverageExchange exchange)
|
||||
{
|
||||
if (!TryAdd(exchange.Name, exchange))
|
||||
{
|
||||
this.Remove(exchange.Name);
|
||||
this.Add(exchange.Name, exchange);
|
||||
}
|
||||
}
|
||||
}
|
||||
public class CoinAverageSettings : ICoinAverageAuthenticator
|
||||
{
|
||||
private static readonly DateTime _epochUtc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
|
||||
|
||||
public (String PublicKey, String PrivateKey)? KeyPair { get; set; }
|
||||
public CoinAverageExchanges AvailableExchanges { get; set; } = new CoinAverageExchanges();
|
||||
|
||||
public CoinAverageSettings()
|
||||
{
|
||||
//GENERATED BY:
|
||||
//StringBuilder b = new StringBuilder();
|
||||
//b.AppendLine("_coinAverageSettings.AvailableExchanges = new[] {");
|
||||
//foreach (var availableExchange in _coinAverageSettings.AvailableExchanges)
|
||||
//{
|
||||
// b.AppendLine($"(DisplayName: \"{availableExchange.DisplayName}\", Name: \"{availableExchange.Name}\"),");
|
||||
//}
|
||||
//b.AppendLine("}.ToArray()");
|
||||
AvailableExchanges = new CoinAverageExchanges();
|
||||
foreach (var item in
|
||||
new[] {
|
||||
(DisplayName: "Idex", Name: "idex"),
|
||||
(DisplayName: "Coinfloor", Name: "coinfloor"),
|
||||
(DisplayName: "Okex", Name: "okex"),
|
||||
(DisplayName: "Bitfinex", Name: "bitfinex"),
|
||||
(DisplayName: "Bittylicious", Name: "bittylicious"),
|
||||
(DisplayName: "BTC Markets", Name: "btcmarkets"),
|
||||
(DisplayName: "Kucoin", Name: "kucoin"),
|
||||
(DisplayName: "IDAX", Name: "idax"),
|
||||
(DisplayName: "Kraken", Name: "kraken"),
|
||||
(DisplayName: "Bit2C", Name: "bit2c"),
|
||||
(DisplayName: "Mercado Bitcoin", Name: "mercado"),
|
||||
(DisplayName: "CEX.IO", Name: "cex"),
|
||||
(DisplayName: "Bitex.la", Name: "bitex"),
|
||||
(DisplayName: "Quoine", Name: "quoine"),
|
||||
(DisplayName: "Stex", Name: "stex"),
|
||||
(DisplayName: "CoinTiger", Name: "cointiger"),
|
||||
(DisplayName: "Poloniex", Name: "poloniex"),
|
||||
(DisplayName: "Zaif", Name: "zaif"),
|
||||
(DisplayName: "Huobi", Name: "huobi"),
|
||||
(DisplayName: "QuickBitcoin", Name: "quickbitcoin"),
|
||||
(DisplayName: "Tidex", Name: "tidex"),
|
||||
(DisplayName: "Tokenomy", Name: "tokenomy"),
|
||||
(DisplayName: "Bitcoin.co.id", Name: "bitcoin_co_id"),
|
||||
(DisplayName: "Kryptono", Name: "kryptono"),
|
||||
(DisplayName: "Bitso", Name: "bitso"),
|
||||
(DisplayName: "Korbit", Name: "korbit"),
|
||||
(DisplayName: "Yobit", Name: "yobit"),
|
||||
(DisplayName: "BitBargain", Name: "bitbargain"),
|
||||
(DisplayName: "Livecoin", Name: "livecoin"),
|
||||
(DisplayName: "Hotbit", Name: "hotbit"),
|
||||
(DisplayName: "Coincheck", Name: "coincheck"),
|
||||
(DisplayName: "Binance", Name: "binance"),
|
||||
(DisplayName: "Bit-Z", Name: "bitz"),
|
||||
(DisplayName: "Coinbase Pro", Name: "coinbasepro"),
|
||||
(DisplayName: "Rock Trading", Name: "rocktrading"),
|
||||
(DisplayName: "Bittrex", Name: "bittrex"),
|
||||
(DisplayName: "BitBay", Name: "bitbay"),
|
||||
(DisplayName: "Tokenize", Name: "tokenize"),
|
||||
(DisplayName: "Hitbtc", Name: "hitbtc"),
|
||||
(DisplayName: "Upbit", Name: "upbit"),
|
||||
(DisplayName: "Bitstamp", Name: "bitstamp"),
|
||||
(DisplayName: "Luno", Name: "luno"),
|
||||
(DisplayName: "Trade.io", Name: "tradeio"),
|
||||
(DisplayName: "LocalBitcoins", Name: "localbitcoins"),
|
||||
(DisplayName: "Independent Reserve", Name: "independentreserve"),
|
||||
(DisplayName: "Coinsquare", Name: "coinsquare"),
|
||||
(DisplayName: "Exmoney", Name: "exmoney"),
|
||||
(DisplayName: "Coinegg", Name: "coinegg"),
|
||||
(DisplayName: "FYB-SG", Name: "fybsg"),
|
||||
(DisplayName: "Cryptonit", Name: "cryptonit"),
|
||||
(DisplayName: "BTCTurk", Name: "btcturk"),
|
||||
(DisplayName: "bitFlyer", Name: "bitflyer"),
|
||||
(DisplayName: "Negocie Coins", Name: "negociecoins"),
|
||||
(DisplayName: "OasisDEX", Name: "oasisdex"),
|
||||
(DisplayName: "CoinMate", Name: "coinmate"),
|
||||
(DisplayName: "BitForex", Name: "bitforex"),
|
||||
(DisplayName: "Bitsquare", Name: "bitsquare"),
|
||||
(DisplayName: "FYB-SE", Name: "fybse"),
|
||||
(DisplayName: "itBit", Name: "itbit"),
|
||||
})
|
||||
{
|
||||
AvailableExchanges.TryAdd(item.Name, new CoinAverageExchange(item.Name, item.DisplayName, $"https://apiv2.bitcoinaverage.com/exchanges/{item.Name}"));
|
||||
}
|
||||
// Keep back-compat
|
||||
AvailableExchanges.Add(new CoinAverageExchange("gdax", string.Empty, $"https://apiv2.bitcoinaverage.com/exchanges/coinbasepro"));
|
||||
}
|
||||
|
||||
|
||||
public Task AddHeader(HttpRequestMessage message)
|
||||
{
|
||||
var signature = GetCoinAverageSignature();
|
||||
|
84
BTCPayServer.Rating/Providers/CoinGeckoRateProvider.cs
Normal file
84
BTCPayServer.Rating/Providers/CoinGeckoRateProvider.cs
Normal file
File diff suppressed because one or more lines are too long
@ -2,7 +2,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
|
@ -9,6 +9,7 @@ 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
|
||||
@ -81,7 +82,7 @@ namespace BTCPayServer.Services.Rates
|
||||
provider.CacheSpan = CacheSpan;
|
||||
provider.MemoryCache = cache;
|
||||
}
|
||||
if (Providers.TryGetValue(CoinAverageRateProvider.CoinAverageName, out var coinAverage) && coinAverage is BackgroundFetcherRateProvider c)
|
||||
if (Providers.TryGetValue(CoinGeckoRateProvider.CoinGeckoName, out var coinAverage) && coinAverage is BackgroundFetcherRateProvider c)
|
||||
{
|
||||
c.RefreshRate = CacheSpan;
|
||||
c.ValidatyTime = CacheSpan + TimeSpan.FromMinutes(1.0);
|
||||
@ -97,8 +98,22 @@ namespace BTCPayServer.Services.Rates
|
||||
return _DirectProviders;
|
||||
}
|
||||
}
|
||||
internal IEnumerable<AvailableRateProvider> GetDirectlySupportedExchanges()
|
||||
{
|
||||
yield return new AvailableRateProvider("binance", "Binance", "https://api.binance.com/api/v1/ticker/24hr", RateSource.Direct);
|
||||
yield return new AvailableRateProvider("bittrex", "Bittrex", "https://bittrex.com/api/v1.1/public/getmarketsummaries", RateSource.Direct);
|
||||
yield return new AvailableRateProvider("poloniex", "Poloniex", "https://poloniex.com/public?command=returnTicker", RateSource.Direct);
|
||||
yield return new AvailableRateProvider("hitbtc", "HitBTC", "https://api.hitbtc.com/api/2/public/ticker", RateSource.Direct);
|
||||
yield return new AvailableRateProvider("ndax", "NDAX", "https://ndax.io/api/returnTicker", RateSource.Direct);
|
||||
|
||||
private void InitExchanges()
|
||||
yield return new AvailableRateProvider(CoinGeckoRateProvider.CoinGeckoName, "Coin Gecko", "https://api.coingecko.com/api/v3/exchange_rates", RateSource.Direct);
|
||||
yield return new AvailableRateProvider(CoinAverageRateProvider.CoinAverageName, "Coin Average", "https://apiv2.bitcoinaverage.com/indices/global/ticker/short", RateSource.Direct);
|
||||
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", RateSource.Direct);
|
||||
yield return new AvailableRateProvider("bylls", "Bylls", "https://bylls.com/api/price?from_currency=BTC&to_currency=CAD", RateSource.Direct);
|
||||
yield return new AvailableRateProvider("bitbank", "Bitbank", "https://public.bitbank.cc/prices", RateSource.Direct);
|
||||
yield return new AvailableRateProvider("bitpay", "Bitpay", "https://bitpay.com/rates", RateSource.Direct);
|
||||
}
|
||||
void InitExchanges()
|
||||
{
|
||||
// We need to be careful to only add exchanges which OnGetTickers implementation make only 1 request
|
||||
Providers.Add("binance", new ExchangeSharpRateProvider("binance", new ExchangeBinanceAPI(), true));
|
||||
@ -107,11 +122,8 @@ namespace BTCPayServer.Services.Rates
|
||||
Providers.Add("hitbtc", new ExchangeSharpRateProvider("hitbtc", new ExchangeHitBTCAPI(), true));
|
||||
Providers.Add("ndax", new ExchangeSharpRateProvider("ndax", new ExchangeNDAXAPI(), true));
|
||||
|
||||
// Cryptopia is often not available
|
||||
// Disabled because of https://twitter.com/Cryptopia_NZ/status/1085084168852291586
|
||||
// Providers.Add("cryptopia", new ExchangeSharpRateProvider("cryptopia", new ExchangeCryptopiaAPI(), false));
|
||||
|
||||
// Handmade providers
|
||||
Providers.Add(CoinGeckoRateProvider.CoinGeckoName, new CoinGeckoRateProvider(_httpClientFactory));
|
||||
Providers.Add(CoinAverageRateProvider.CoinAverageName, new CoinAverageRateProvider() { Exchange = CoinAverageRateProvider.CoinAverageName, HttpClient = _httpClientFactory?.CreateClient("EXCHANGE_COINAVERAGE"), Authenticator = _CoinAverageSettings });
|
||||
Providers.Add("kraken", new KrakenExchangeRateProvider() { HttpClient = _httpClientFactory?.CreateClient("EXCHANGE_KRAKEN") });
|
||||
Providers.Add("bylls", new ByllsRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BYLLS")));
|
||||
@ -128,8 +140,8 @@ namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
if (provider.Key == "cryptopia") // Shitty exchange, rate often unavailable, it spams the logs
|
||||
continue;
|
||||
var prov = new BackgroundFetcherRateProvider(Providers[provider.Key]);
|
||||
if(provider.Key == CoinAverageRateProvider.CoinAverageName)
|
||||
var prov = new BackgroundFetcherRateProvider(provider.Key, Providers[provider.Key]);
|
||||
if (provider.Key == CoinGeckoRateProvider.CoinGeckoName)
|
||||
{
|
||||
prov.RefreshRate = CacheSpan;
|
||||
prov.ValidatyTime = CacheSpan + TimeSpan.FromMinutes(1.0);
|
||||
@ -143,40 +155,146 @@ namespace BTCPayServer.Services.Rates
|
||||
}
|
||||
|
||||
var cache = new MemoryCache(_CacheOptions);
|
||||
foreach (var supportedExchange in GetSupportedExchanges())
|
||||
foreach (var supportedExchange in GetCoinGeckoSupportedExchanges())
|
||||
{
|
||||
if (!Providers.ContainsKey(supportedExchange.Key))
|
||||
if (!Providers.ContainsKey(supportedExchange.Id))
|
||||
{
|
||||
var coinAverage = new CoinAverageRateProvider()
|
||||
var coinAverage = new CoinGeckoRateProvider(_httpClientFactory)
|
||||
{
|
||||
Exchange = supportedExchange.Key,
|
||||
HttpClient = _httpClientFactory?.CreateClient(),
|
||||
Authenticator = _CoinAverageSettings
|
||||
Exchange = supportedExchange.Id
|
||||
};
|
||||
var cached = new CachedRateProvider(supportedExchange.Key, coinAverage, cache)
|
||||
var cached = new CachedRateProvider(supportedExchange.Id, coinAverage, cache)
|
||||
{
|
||||
CacheSpan = CacheSpan
|
||||
};
|
||||
Providers.Add(supportedExchange.Key, cached);
|
||||
Providers.Add(supportedExchange.Id, cached);
|
||||
}
|
||||
}
|
||||
foreach (var supportedExchange in GetCoinAverageSupportedExchanges())
|
||||
{
|
||||
if (!Providers.ContainsKey(supportedExchange.Id))
|
||||
{
|
||||
var coinAverage = new CoinGeckoRateProvider(_httpClientFactory)
|
||||
{
|
||||
Exchange = supportedExchange.Id
|
||||
};
|
||||
var cached = new CachedRateProvider(supportedExchange.Id, coinAverage, cache)
|
||||
{
|
||||
CacheSpan = CacheSpan
|
||||
};
|
||||
Providers.Add(supportedExchange.Id, cached);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public CoinAverageExchanges GetSupportedExchanges()
|
||||
IEnumerable<AvailableRateProvider> _AvailableRateProviders = null;
|
||||
public IEnumerable<AvailableRateProvider> GetSupportedExchanges()
|
||||
{
|
||||
CoinAverageExchanges exchanges = new CoinAverageExchanges();
|
||||
foreach (var exchange in _CoinAverageSettings.AvailableExchanges)
|
||||
if (_AvailableRateProviders == null)
|
||||
{
|
||||
exchanges.Add(exchange.Value);
|
||||
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);
|
||||
}
|
||||
foreach (var exchange in GetCoinAverageSupportedExchanges())
|
||||
{
|
||||
availableProviders.TryAdd(exchange.Id, exchange);
|
||||
}
|
||||
_AvailableRateProviders = availableProviders.Values.OrderBy(o => o.Name).ToArray();
|
||||
}
|
||||
return _AvailableRateProviders;
|
||||
}
|
||||
|
||||
// Add other exchanges supported here
|
||||
exchanges.Add(new CoinAverageExchange(CoinAverageRateProvider.CoinAverageName, "Coin Average", $"https://apiv2.bitcoinaverage.com/indices/global/ticker/short"));
|
||||
exchanges.Add(new CoinAverageExchange("bylls", "Bylls", "https://bylls.com/api/price?from_currency=BTC&to_currency=CAD"));
|
||||
exchanges.Add(new CoinAverageExchange("ndax", "NDAX", "https://ndax.io/api/returnTicker"));
|
||||
exchanges.Add(new CoinAverageExchange("bitbank", "Bitbank", "https://public.bitbank.cc/prices"));
|
||||
internal IEnumerable<AvailableRateProvider> GetCoinAverageSupportedExchanges()
|
||||
{
|
||||
foreach (var item in
|
||||
new[] {
|
||||
(DisplayName: "Idex", Name: "idex"),
|
||||
(DisplayName: "Coinfloor", Name: "coinfloor"),
|
||||
(DisplayName: "Okex", Name: "okex"),
|
||||
(DisplayName: "Bitfinex", Name: "bitfinex"),
|
||||
(DisplayName: "Bittylicious", Name: "bittylicious"),
|
||||
(DisplayName: "BTC Markets", Name: "btcmarkets"),
|
||||
(DisplayName: "Kucoin", Name: "kucoin"),
|
||||
(DisplayName: "IDAX", Name: "idax"),
|
||||
(DisplayName: "Kraken", Name: "kraken"),
|
||||
(DisplayName: "Bit2C", Name: "bit2c"),
|
||||
(DisplayName: "Mercado Bitcoin", Name: "mercado"),
|
||||
(DisplayName: "CEX.IO", Name: "cex"),
|
||||
(DisplayName: "Bitex.la", Name: "bitex"),
|
||||
(DisplayName: "Quoine", Name: "quoine"),
|
||||
(DisplayName: "Stex", Name: "stex"),
|
||||
(DisplayName: "CoinTiger", Name: "cointiger"),
|
||||
(DisplayName: "Poloniex", Name: "poloniex"),
|
||||
(DisplayName: "Zaif", Name: "zaif"),
|
||||
(DisplayName: "Huobi", Name: "huobi"),
|
||||
(DisplayName: "QuickBitcoin", Name: "quickbitcoin"),
|
||||
(DisplayName: "Tidex", Name: "tidex"),
|
||||
(DisplayName: "Tokenomy", Name: "tokenomy"),
|
||||
(DisplayName: "Bitcoin.co.id", Name: "bitcoin_co_id"),
|
||||
(DisplayName: "Kryptono", Name: "kryptono"),
|
||||
(DisplayName: "Bitso", Name: "bitso"),
|
||||
(DisplayName: "Korbit", Name: "korbit"),
|
||||
(DisplayName: "Yobit", Name: "yobit"),
|
||||
(DisplayName: "BitBargain", Name: "bitbargain"),
|
||||
(DisplayName: "Livecoin", Name: "livecoin"),
|
||||
(DisplayName: "Hotbit", Name: "hotbit"),
|
||||
(DisplayName: "Coincheck", Name: "coincheck"),
|
||||
(DisplayName: "Binance", Name: "binance"),
|
||||
(DisplayName: "Bit-Z", Name: "bitz"),
|
||||
(DisplayName: "Coinbase Pro", Name: "coinbasepro"),
|
||||
(DisplayName: "Rock Trading", Name: "rocktrading"),
|
||||
(DisplayName: "Bittrex", Name: "bittrex"),
|
||||
(DisplayName: "BitBay", Name: "bitbay"),
|
||||
(DisplayName: "Tokenize", Name: "tokenize"),
|
||||
(DisplayName: "Hitbtc", Name: "hitbtc"),
|
||||
(DisplayName: "Upbit", Name: "upbit"),
|
||||
(DisplayName: "Bitstamp", Name: "bitstamp"),
|
||||
(DisplayName: "Luno", Name: "luno"),
|
||||
(DisplayName: "Trade.io", Name: "tradeio"),
|
||||
(DisplayName: "LocalBitcoins", Name: "localbitcoins"),
|
||||
(DisplayName: "Independent Reserve", Name: "independentreserve"),
|
||||
(DisplayName: "Coinsquare", Name: "coinsquare"),
|
||||
(DisplayName: "Exmoney", Name: "exmoney"),
|
||||
(DisplayName: "Coinegg", Name: "coinegg"),
|
||||
(DisplayName: "FYB-SG", Name: "fybsg"),
|
||||
(DisplayName: "Cryptonit", Name: "cryptonit"),
|
||||
(DisplayName: "BTCTurk", Name: "btcturk"),
|
||||
(DisplayName: "bitFlyer", Name: "bitflyer"),
|
||||
(DisplayName: "Negocie Coins", Name: "negociecoins"),
|
||||
(DisplayName: "OasisDEX", Name: "oasisdex"),
|
||||
(DisplayName: "CoinMate", Name: "coinmate"),
|
||||
(DisplayName: "BitForex", Name: "bitforex"),
|
||||
(DisplayName: "Bitsquare", Name: "bitsquare"),
|
||||
(DisplayName: "FYB-SE", Name: "fybse"),
|
||||
(DisplayName: "itBit", Name: "itbit"),
|
||||
})
|
||||
{
|
||||
yield return new AvailableRateProvider(item.Name, item.DisplayName, $"https://apiv2.bitcoinaverage.com/exchanges/{item.Name}", RateSource.CoinAverage);
|
||||
}
|
||||
yield return new AvailableRateProvider("gdax", string.Empty, $"https://apiv2.bitcoinaverage.com/exchanges/gdax", RateSource.CoinAverage);
|
||||
}
|
||||
|
||||
return exchanges;
|
||||
internal IEnumerable<AvailableRateProvider> GetCoinGeckoSupportedExchanges()
|
||||
{
|
||||
return JArray.Parse(CoinGeckoRateProvider.SupportedExchanges).Select(token =>
|
||||
new AvailableRateProvider(Normalize(token["id"].ToString().ToLowerInvariant()), token["name"].ToString(),
|
||||
$"https://api.coingecko.com/api/v3/exchanges/{token["id"]}/tickers", RateSource.Coingecko))
|
||||
.Concat(new[] { new AvailableRateProvider("gdax", string.Empty, $"https://api.coingecko.com/api/v3/exchanges/gdax", RateSource.Coingecko) });
|
||||
}
|
||||
|
||||
private string Normalize(string name)
|
||||
{
|
||||
if (name == "oasis_trade")
|
||||
return "oasisdex";
|
||||
if (name == "gdax")
|
||||
return "coinbasepro";
|
||||
return name;
|
||||
}
|
||||
|
||||
public async Task<QueryRateResult> QueryRates(string exchangeName, CancellationToken cancellationToken)
|
||||
|
@ -6,8 +6,6 @@ using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using System.Security.Claims;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using System.Net.Http;
|
||||
@ -66,8 +64,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
var json = await streamToReadFrom.ReadToEndAsync();
|
||||
Assert.NotNull(json);
|
||||
var configuration = OpenIdConnectConfiguration.Create(json);
|
||||
Assert.NotNull(configuration);
|
||||
JObject.Parse(json); // Should do more tests but good enough
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -249,7 +246,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
var result = JObject.Parse(content).ToObject<OpenIddictResponse>();
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize<OpenIddictResponse>(content);
|
||||
|
||||
await TestApiAgainstAccessToken(result.AccessToken, tester, user);
|
||||
|
||||
@ -289,7 +286,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
var result = JObject.Parse(content).ToObject<OpenIddictResponse>();
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize<OpenIddictResponse>(content);
|
||||
Assert.NotEmpty(result.AccessToken);
|
||||
Assert.Null(result.Error);
|
||||
return result.AccessToken;
|
||||
@ -330,7 +327,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
var result = JObject.Parse(content).ToObject<OpenIddictResponse>();
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize<OpenIddictResponse>(content);
|
||||
Assert.NotEmpty(result.AccessToken);
|
||||
Assert.Null(result.Error);
|
||||
return result.AccessToken;
|
||||
@ -371,7 +368,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(response.IsSuccessStatusCode);
|
||||
|
||||
string content = await response.Content.ReadAsStringAsync();
|
||||
var result = JObject.Parse(content).ToObject<OpenIddictResponse>();
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize<OpenIddictResponse>(content);
|
||||
Assert.NotEmpty(result.AccessToken);
|
||||
Assert.Null(result.Error);
|
||||
return result.AccessToken;
|
||||
|
@ -1,24 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.1</TargetFramework>
|
||||
<TargetFramework>netcoreapp3.1</TargetFramework>
|
||||
<TargetFramework Condition="'$(TargetFrameworkOverride)' != ''">$(TargetFrameworkOverride)</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<NoWarn>NU1701,CA1816,CA1308,CA1810,CA2208</NoWarn>
|
||||
<LangVersion>7.2</LangVersion>
|
||||
<LangVersion>8.0</LangVersion>
|
||||
<UserSecretsId>AB0AC1DD-9D26-485B-9416-56A33F268117</UserSecretsId>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
|
||||
<DefineConstants>$(DefineConstants);NETCOREAPP21</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(CI_TESTS)' == 'true'">
|
||||
<DefineConstants>$(DefineConstants);SHORT_TIMEOUT</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="78.0.3904.7000" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
|
@ -63,6 +63,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
public Uri LTCNBXplorerUri { get; set; }
|
||||
public Uri LBTCNBXplorerUri { get; set; }
|
||||
|
||||
public Uri ServerUri
|
||||
{
|
||||
@ -93,6 +94,9 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public bool MockRates { get; set; } = true;
|
||||
|
||||
public HashSet<string> Chains { get; set; } = new HashSet<string>(){"BTC"};
|
||||
public bool UseLightning { get; set; }
|
||||
|
||||
public async Task StartAsync()
|
||||
{
|
||||
if (!Directory.Exists(_Directory))
|
||||
@ -109,20 +113,37 @@ namespace BTCPayServer.Tests
|
||||
config.AppendLine($"bind=0.0.0.0");
|
||||
}
|
||||
config.AppendLine($"port={Port}");
|
||||
config.AppendLine($"chains=btc,ltc");
|
||||
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($"btc.explorer.url={NBXplorerUri.AbsoluteUri}");
|
||||
config.AppendLine($"btc.explorer.cookiefile=0");
|
||||
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}");
|
||||
}
|
||||
|
||||
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($"ltc.explorer.url={LTCNBXplorerUri.AbsoluteUri}");
|
||||
config.AppendLine($"ltc.explorer.cookiefile=0");
|
||||
config.AppendLine($"btc.lightning={IntegratedLightning.AbsoluteUri}");
|
||||
|
||||
config.AppendLine($"torrcfile={TestUtils.GetTestDataFullPath("Tor/torrc")}");
|
||||
config.AppendLine($"debuglog=debug.log");
|
||||
|
||||
var localLndBackupFile = Path.Combine(_Directory, "walletunlock.json");
|
||||
File.Copy(TestUtils.GetTestDataFullPath("LndSeedBackup/walletunlock.json"), localLndBackupFile, true);
|
||||
config.AppendLine($"btc.external.lndseedbackup={localLndBackupFile}");
|
||||
|
||||
if (!string.IsNullOrEmpty(SSHPassword) && string.IsNullOrEmpty(SSHKeyFile))
|
||||
config.AppendLine($"sshpassword={SSHPassword}");
|
||||
if (!string.IsNullOrEmpty(SSHKeyFile))
|
||||
@ -181,29 +202,29 @@ namespace BTCPayServer.Tests
|
||||
var coinAverageMock = new MockRateProvider();
|
||||
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||
{
|
||||
Exchange = "coinaverage",
|
||||
Exchange = "coingecko",
|
||||
CurrencyPair = CurrencyPair.Parse("BTC_USD"),
|
||||
BidAsk = new BidAsk(5000m)
|
||||
});
|
||||
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||
{
|
||||
Exchange = "coinaverage",
|
||||
Exchange = "coingecko",
|
||||
CurrencyPair = CurrencyPair.Parse("BTC_CAD"),
|
||||
BidAsk = new BidAsk(4500m)
|
||||
});
|
||||
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||
{
|
||||
Exchange = "coinaverage",
|
||||
CurrencyPair = CurrencyPair.Parse("LTC_BTC"),
|
||||
BidAsk = new BidAsk(0.001m)
|
||||
Exchange = "coingecko",
|
||||
CurrencyPair = CurrencyPair.Parse("BTC_LTC"),
|
||||
BidAsk = new BidAsk(162m)
|
||||
});
|
||||
coinAverageMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||
{
|
||||
Exchange = "coinaverage",
|
||||
Exchange = "coingecko",
|
||||
CurrencyPair = CurrencyPair.Parse("LTC_USD"),
|
||||
BidAsk = new BidAsk(500m)
|
||||
});
|
||||
rateProvider.Providers.Add("coinaverage", coinAverageMock);
|
||||
rateProvider.Providers.Add("coingecko", coinAverageMock);
|
||||
|
||||
var bitflyerMock = new MockRateProvider();
|
||||
bitflyerMock.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||
@ -231,6 +252,16 @@ namespace BTCPayServer.Tests
|
||||
BidAsk = new BidAsk(0.004m)
|
||||
});
|
||||
rateProvider.Providers.Add("bittrex", bittrex);
|
||||
|
||||
|
||||
var bitfinex = new MockRateProvider();
|
||||
bitfinex.ExchangeRates.Add(new Rating.ExchangeRate()
|
||||
{
|
||||
Exchange = "bitfinex",
|
||||
CurrencyPair = CurrencyPair.Parse("UST_BTC"),
|
||||
BidAsk = new BidAsk(0.000136m)
|
||||
});
|
||||
rateProvider.Providers.Add("bitfinex", bitfinex);
|
||||
}
|
||||
|
||||
|
||||
|
@ -106,10 +106,14 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Altcoins", "Altcoins")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanUsePaymentMethodDropdown()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
s.Server.ActivateLTC();
|
||||
s.Server.ActivateLightning();
|
||||
await s.StartAsync();
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser();
|
||||
@ -141,6 +145,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
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);
|
||||
@ -150,10 +155,12 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
[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();
|
||||
|
@ -1,8 +1,7 @@
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:2.1.505-alpine3.7 AS builder
|
||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT false
|
||||
RUN apk add --no-cache icu-libs
|
||||
ENV LC_ALL en_US.UTF-8
|
||||
ENV LANG en_US.UTF-8
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1.100 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
|
||||
@ -11,15 +10,12 @@ 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
|
||||
COPY BTCPayServer.Tests/BTCPayServer.Tests.csproj BTCPayServer.Tests/BTCPayServer.Tests.csproj
|
||||
RUN dotnet restore BTCPayServer.Tests/BTCPayServer.Tests.csproj
|
||||
|
||||
ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT false
|
||||
|
||||
ENV LC_ALL en_US.UTF-8
|
||||
ENV LANG en_US.UTF-8
|
||||
|
||||
RUN apk add --no-cache chromium chromium-chromedriver icu-libs
|
||||
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
|
||||
|
126
BTCPayServer.Tests/ElementsTests.cs
Normal file
126
BTCPayServer.Tests/ElementsTests.cs
Normal file
@ -0,0 +1,126 @@
|
||||
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);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -11,6 +11,11 @@ namespace BTCPayServer.Tests.Mocks
|
||||
public class MockRateProvider : IRateProvider
|
||||
{
|
||||
public ExchangeRates ExchangeRates { get; set; } = new ExchangeRates();
|
||||
|
||||
public MockRateProvider()
|
||||
{
|
||||
|
||||
}
|
||||
public Task<ExchangeRates> GetRatesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(ExchangeRates);
|
||||
|
@ -13,6 +13,7 @@ using BTCPayServer.Rating;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
[Trait("Fast", "Fast")]
|
||||
public class PaymentHandlerTest
|
||||
{
|
||||
private BitcoinLikePaymentHandler handlerBTC;
|
||||
|
@ -39,18 +39,18 @@ namespace BTCPayServer.Tests
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task StartAsync()
|
||||
{
|
||||
await Server.StartAsync();
|
||||
ChromeOptions options = new ChromeOptions();
|
||||
var isDebug = !Server.PayTester.InContainer;
|
||||
|
||||
|
||||
if (!isDebug)
|
||||
{
|
||||
options.AddArguments("headless"); // Comment to view browser
|
||||
options.AddArguments("window-size=1200x1000"); // Comment to view browser
|
||||
}
|
||||
}
|
||||
options.AddArgument("shm-size=2g");
|
||||
if (Server.PayTester.InContainer)
|
||||
{
|
||||
@ -72,7 +72,16 @@ namespace BTCPayServer.Tests
|
||||
|
||||
internal void AssertHappyMessage()
|
||||
{
|
||||
Assert.Single(Driver.FindElements(By.ClassName("alert-success")).Where(el => el.Displayed));
|
||||
using var cts = new CancellationTokenSource(10_000);
|
||||
while (!cts.IsCancellationRequested)
|
||||
{
|
||||
var success = Driver.FindElements(By.ClassName("alert-success")).Where(el => el.Displayed).Any();
|
||||
if (success)
|
||||
return;
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
Logs.Tester.LogInformation(this.Driver.PageSource);
|
||||
Assert.True(false, "Should have shown happy message");
|
||||
}
|
||||
|
||||
public static readonly TimeSpan ImplicitWait = TimeSpan.FromSeconds(10);
|
||||
@ -105,10 +114,29 @@ namespace BTCPayServer.Tests
|
||||
Driver.FindElement(By.Id("CreateStore")).Click();
|
||||
Driver.FindElement(By.Id("Name")).SendKeys(usr);
|
||||
Driver.FindElement(By.Id("Create")).Click();
|
||||
|
||||
|
||||
return (usr, Driver.FindElement(By.Id("Id")).GetAttribute("value"));
|
||||
}
|
||||
|
||||
public string GenerateWallet(string cryptoCode = "BTC", string seed = "", bool importkeys = false, bool privkeys = false)
|
||||
{
|
||||
Driver.FindElement(By.Id($"Modify{cryptoCode}")).ForceClick();
|
||||
Driver.FindElement(By.Id("import-from-btn")).ForceClick();
|
||||
Driver.FindElement(By.Id("nbxplorergeneratewalletbtn")).ForceClick();
|
||||
Driver.FindElement(By.Id("ExistingMnemonic")).SendKeys(seed);
|
||||
SetCheckbox(Driver.FindElement(By.Id("SavePrivateKeys")), privkeys);
|
||||
SetCheckbox(Driver.FindElement(By.Id("ImportKeysToRPC")), importkeys);
|
||||
Driver.FindElement(By.Id("btn-generate")).ForceClick();
|
||||
AssertHappyMessage();
|
||||
if (string.IsNullOrEmpty(seed))
|
||||
{
|
||||
seed = Driver.FindElements(By.ClassName("alert-success")).First().FindElement(By.TagName("code")).Text;
|
||||
}
|
||||
Driver.FindElement(By.Id("Confirm")).ForceClick();
|
||||
AssertHappyMessage();
|
||||
return seed;
|
||||
}
|
||||
|
||||
public void AddDerivationScheme(string cryptoCode = "BTC", string derivationScheme = "xpub661MyMwAqRbcGABgHMUXDzPzH1tU7eZaAaJQXhDXsSxsqyQzQeU6kznNfSuAyqAK9UaWSaZaMFdNiY5BCF4zBPAzSnwfUAwUhwttuAKwfRX-[legacy]")
|
||||
{
|
||||
Driver.FindElement(By.Id($"Modify{cryptoCode}")).ForceClick();
|
||||
@ -117,7 +145,7 @@ namespace BTCPayServer.Tests
|
||||
Driver.FindElement(By.Id("Confirm")).ForceClick();
|
||||
AssertHappyMessage();
|
||||
}
|
||||
|
||||
|
||||
public void AddLightningNode(string cryptoCode, LightningConnectionType connectionType)
|
||||
{
|
||||
string connectionString = null;
|
||||
@ -129,12 +157,12 @@ namespace BTCPayServer.Tests
|
||||
connectionString = $"type=lnd-rest;server={Server.MerchantLnd.Swagger.BaseUrl};allowinsecure=true";
|
||||
else
|
||||
throw new NotSupportedException(connectionType.ToString());
|
||||
|
||||
|
||||
Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).ForceClick();
|
||||
Driver.FindElement(By.Name($"ConnectionString")).SendKeys(connectionString);
|
||||
Driver.FindElement(By.Id($"save")).ForceClick();
|
||||
}
|
||||
|
||||
|
||||
public void AddInternalLightningNode(string cryptoCode)
|
||||
{
|
||||
Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).ForceClick();
|
||||
@ -155,7 +183,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
@ -204,21 +232,26 @@ namespace BTCPayServer.Tests
|
||||
Driver.FindElement(By.Id(storeNavPage.ToString())).Click();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void GoToInvoiceCheckout(string invoiceId)
|
||||
{
|
||||
Driver.FindElement(By.Id("Invoices")).Click();
|
||||
Driver.FindElement(By.Id($"invoice-checkout-{invoiceId}")).Click();
|
||||
CheckForJSErrors();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public void SetCheckbox(IWebElement element, bool value)
|
||||
{
|
||||
if ((value && !element.Selected) || (!value && element.Selected))
|
||||
{
|
||||
element.Click();
|
||||
}
|
||||
|
||||
if (value != element.Selected)
|
||||
{
|
||||
SetCheckbox(element, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCheckbox(SeleniumTester s, string inputName, bool value)
|
||||
@ -263,30 +296,30 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private void CheckForJSErrors()
|
||||
{
|
||||
//wait for seleniun update: https://stackoverflow.com/questions/57520296/selenium-webdriver-3-141-0-driver-manage-logs-availablelogtypes-throwing-syste
|
||||
// var errorStrings = new List<string>
|
||||
// {
|
||||
// "SyntaxError",
|
||||
// "EvalError",
|
||||
// "ReferenceError",
|
||||
// "RangeError",
|
||||
// "TypeError",
|
||||
// "URIError"
|
||||
// };
|
||||
//
|
||||
// var jsErrors = Driver.Manage().Logs.GetLog(LogType.Browser).Where(x => errorStrings.Any(e => x.Message.Contains(e)));
|
||||
//
|
||||
// if (jsErrors.Any())
|
||||
// {
|
||||
// Logs.Tester.LogInformation("JavaScript error(s):" + Environment.NewLine + jsErrors.Aggregate("", (s, entry) => s + entry.Message + Environment.NewLine));
|
||||
// }
|
||||
// Assert.Empty(jsErrors);
|
||||
// var errorStrings = new List<string>
|
||||
// {
|
||||
// "SyntaxError",
|
||||
// "EvalError",
|
||||
// "ReferenceError",
|
||||
// "RangeError",
|
||||
// "TypeError",
|
||||
// "URIError"
|
||||
// };
|
||||
//
|
||||
// var jsErrors = Driver.Manage().Logs.GetLog(LogType.Browser).Where(x => errorStrings.Any(e => x.Message.Contains(e)));
|
||||
//
|
||||
// if (jsErrors.Any())
|
||||
// {
|
||||
// Logs.Tester.LogInformation("JavaScript error(s):" + Environment.NewLine + jsErrors.Aggregate("", (s, entry) => s + entry.Message + Environment.NewLine));
|
||||
// }
|
||||
// Assert.Empty(jsErrors);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,27 @@ namespace BTCPayServer.Tests
|
||||
s.ClickOnAllSideMenus();
|
||||
s.Driver.FindElement(By.LinkText("Services")).Click();
|
||||
|
||||
Logs.Tester.LogInformation("Let's check if we can access the logs");
|
||||
s.Driver.FindElement(By.LinkText("Logs")).Click();
|
||||
s.Driver.FindElement(By.PartialLinkText(".log")).Click();
|
||||
Assert.Contains("Starting listening NBXplorer", s.Driver.PageSource);
|
||||
s.Driver.Quit();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanUseLndSeedBackup()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
s.Server.ActivateLightning();
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser(true);
|
||||
s.Driver.FindElement(By.Id("ServerSettings")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
s.Driver.FindElement(By.LinkText("Services")).Click();
|
||||
|
||||
Logs.Tester.LogInformation("Let's if we can access LND's seed");
|
||||
Assert.Contains("server/services/lndseedbackup/BTC", s.Driver.PageSource);
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services/lndseedbackup/BTC"));
|
||||
@ -49,12 +70,6 @@ namespace BTCPayServer.Tests
|
||||
s.AssertHappyMessage();
|
||||
seedEl = s.Driver.FindElement(By.Id("SeedTextArea"));
|
||||
Assert.Contains("Seed removed", seedEl.Text, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
Logs.Tester.LogInformation("Let's check if we can access the logs");
|
||||
s.Driver.FindElement(By.LinkText("Logs")).Click();
|
||||
s.Driver.FindElement(By.PartialLinkText(".log")).Click();
|
||||
Assert.Contains("Starting listening NBXplorer", s.Driver.PageSource);
|
||||
s.Driver.Quit();
|
||||
}
|
||||
}
|
||||
|
||||
@ -408,14 +423,28 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser(true);
|
||||
s.CreateNewStore();
|
||||
var storeId = s.CreateNewStore();
|
||||
|
||||
// In this test, we try to spend from a manual seed. We import the xpub 49'/0'/0', then try to use the seed
|
||||
// to sign the transaction
|
||||
var mnemonic = "usage fever hen zero slide mammal silent heavy donate budget pulse say brain thank sausage brand craft about save attract muffin advance illegal cabbage";
|
||||
var mnemonic = s.GenerateWallet("BTC", "", true, false);
|
||||
|
||||
var invoiceId = s.CreateInvoice(storeId.storeId);
|
||||
var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
|
||||
var address = invoice.EntityToDTO().Addresses["BTC"];
|
||||
|
||||
var result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
|
||||
Assert.True(result.IsWatchOnly);
|
||||
s.GoToStore(storeId.storeId);
|
||||
mnemonic = s.GenerateWallet("BTC", "", true, true);
|
||||
|
||||
var root = new Mnemonic(mnemonic).DeriveExtKey();
|
||||
s.AddDerivationScheme("BTC", "ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD");
|
||||
var tx = s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create("bcrt1qmxg8fgnmkp354vhe78j6sr4ut64tyz2xyejel4", Network.RegTest), Money.Coins(3.0m));
|
||||
invoiceId = s.CreateInvoice(storeId.storeId);
|
||||
invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice( invoiceId);
|
||||
address = invoice.EntityToDTO().Addresses["BTC"];
|
||||
result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
|
||||
Assert.False(result.IsWatchOnly);
|
||||
var tx = s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create(address, Network.RegTest), Money.Coins(3.0m));
|
||||
s.Server.ExplorerNode.Generate(1);
|
||||
|
||||
s.Driver.FindElement(By.Id("Wallets")).Click();
|
||||
@ -429,8 +458,8 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// We setup the fingerprint and the account key path
|
||||
s.Driver.FindElement(By.Id("WalletSettings")).ForceClick();
|
||||
s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).SendKeys("8bafd160");
|
||||
s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).SendKeys("m/49'/0'/0'" + Keys.Enter);
|
||||
// s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).SendKeys("8bafd160");
|
||||
// s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).SendKeys("m/49'/0'/0'" + Keys.Enter);
|
||||
|
||||
// Check the tx sent earlier arrived
|
||||
s.Driver.FindElement(By.Id("WalletTransactions")).ForceClick();
|
||||
@ -471,8 +500,6 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
SignWith(mnemonic);
|
||||
var accountKey = root.Derive(new KeyPath("m/49'/0'/0'")).GetWif(Network.RegTest).ToString();
|
||||
SignWith(accountKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -46,27 +46,15 @@ namespace BTCPayServer.Tests
|
||||
NetworkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
|
||||
ExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_BTCRPCCONNECTION", "server=http://127.0.0.1:43782;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBitcoinNetwork);
|
||||
ExplorerNode.ScanRPCCapabilities();
|
||||
LTCExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_LTCRPCCONNECTION", "server=http://127.0.0.1:43783;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork<BTCPayNetwork>("LTC").NBitcoinNetwork);
|
||||
|
||||
ExplorerClient = new ExplorerClient(NetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBXplorerNetwork, new Uri(GetEnvironment("TESTS_BTCNBXPLORERURL", "http://127.0.0.1:32838/")));
|
||||
LTCExplorerClient = new ExplorerClient(NetworkProvider.GetNetwork<BTCPayNetwork>("LTC").NBXplorerNetwork, new Uri(GetEnvironment("TESTS_LTCNBXPLORERURL", "http://127.0.0.1:32838/")));
|
||||
|
||||
var btc = NetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBitcoinNetwork;
|
||||
CustomerLightningD = LightningClientFactory.CreateClient(GetEnvironment("TEST_CUSTOMERLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30992/"), btc);
|
||||
MerchantLightningD = LightningClientFactory.CreateClient(GetEnvironment("TEST_MERCHANTLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30993/"), btc);
|
||||
|
||||
MerchantCharge = new ChargeTester(this, "TEST_MERCHANTCHARGE", "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify", "merchant_lightningd", btc);
|
||||
|
||||
MerchantLnd = new LndMockTester(this, "TEST_MERCHANTLND", "https://lnd:lnd@127.0.0.1:53280/", "merchant_lnd", btc);
|
||||
|
||||
PayTester = new BTCPayServerTester(Path.Combine(_Directory, "pay"))
|
||||
{
|
||||
NBXplorerUri = ExplorerClient.Address,
|
||||
LTCNBXplorerUri = LTCExplorerClient.Address,
|
||||
TestDatabase = Enum.Parse<TestDatabases>(GetEnvironment("TESTS_DB", TestDatabases.Postgres.ToString()), true),
|
||||
Postgres = GetEnvironment("TESTS_POSTGRES", "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver"),
|
||||
MySQL = GetEnvironment("TESTS_MYSQL", "User ID=root;Host=127.0.0.1;Port=33036;Database=btcpayserver"),
|
||||
IntegratedLightning = MerchantCharge.Client.Uri
|
||||
MySQL = GetEnvironment("TESTS_MYSQL", "User ID=root;Host=127.0.0.1;Port=33036;Database=btcpayserver")
|
||||
};
|
||||
PayTester.Port = int.Parse(GetEnvironment("TESTS_PORT", Utils.FreeTcpPort().ToString(CultureInfo.InvariantCulture)), CultureInfo.InvariantCulture);
|
||||
PayTester.HostName = GetEnvironment("TESTS_HOSTNAME", "127.0.0.1");
|
||||
@ -77,6 +65,32 @@ namespace BTCPayServer.Tests
|
||||
PayTester.SSHConnection = GetEnvironment("TESTS_SSHCONNECTION", "root@127.0.0.1:21622");
|
||||
}
|
||||
|
||||
public void ActivateLTC()
|
||||
{
|
||||
LTCExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_LTCRPCCONNECTION", "server=http://127.0.0.1:43783;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork<BTCPayNetwork>("LTC").NBitcoinNetwork);
|
||||
LTCExplorerClient = new ExplorerClient(NetworkProvider.GetNetwork<BTCPayNetwork>("LTC").NBXplorerNetwork, new Uri(GetEnvironment("TESTS_LTCNBXPLORERURL", "http://127.0.0.1:32838/")));
|
||||
PayTester.Chains.Add("LTC");
|
||||
PayTester.LTCNBXplorerUri = LTCExplorerClient.Address;
|
||||
}
|
||||
public void ActivateLBTC()
|
||||
{
|
||||
LBTCExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_LBTCRPCCONNECTION", "server=http://127.0.0.1:19332;liquid:liquid")), NetworkProvider.GetNetwork<BTCPayNetwork>("LBTC").NBitcoinNetwork);
|
||||
LBTCExplorerClient = new ExplorerClient(NetworkProvider.GetNetwork<BTCPayNetwork>("LBTC").NBXplorerNetwork, new Uri(GetEnvironment("TESTS_LBTCNBXPLORERURL", "http://127.0.0.1:32838/")));
|
||||
PayTester.Chains.Add("LBTC");
|
||||
PayTester.LBTCNBXplorerUri = LBTCExplorerClient.Address;
|
||||
}
|
||||
|
||||
public void ActivateLightning()
|
||||
{
|
||||
var btc = NetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBitcoinNetwork;
|
||||
CustomerLightningD = LightningClientFactory.CreateClient(GetEnvironment("TEST_CUSTOMERLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30992/"), btc);
|
||||
MerchantLightningD = LightningClientFactory.CreateClient(GetEnvironment("TEST_MERCHANTLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30993/"), btc);
|
||||
MerchantCharge = new ChargeTester(this, "TEST_MERCHANTCHARGE", "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify", "merchant_lightningd", btc);
|
||||
MerchantLnd = new LndMockTester(this, "TEST_MERCHANTLND", "https://lnd:lnd@127.0.0.1:53280/", "merchant_lnd", btc);
|
||||
PayTester.UseLightning = true;
|
||||
PayTester.IntegratedLightning = MerchantCharge.Client.Uri;
|
||||
}
|
||||
|
||||
public bool Dockerized
|
||||
{
|
||||
get; set;
|
||||
@ -148,12 +162,15 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
public RPCClient LBTCExplorerNode { get; set; }
|
||||
|
||||
public ExplorerClient ExplorerClient
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
public ExplorerClient LTCExplorerClient { get; set; }
|
||||
public ExplorerClient LBTCExplorerClient { get; set; }
|
||||
|
||||
HttpClient _Http = new HttpClient();
|
||||
|
||||
|
@ -109,7 +109,7 @@ namespace BTCPayServer.Tests
|
||||
SupportedNetwork = parent.NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var store = parent.PayTester.GetController<StoresController>(UserId, StoreId);
|
||||
ExtKey = new ExtKey().GetWif(SupportedNetwork.NBitcoinNetwork);
|
||||
DerivationScheme = new DerivationStrategyFactory(SupportedNetwork.NBitcoinNetwork).Parse(ExtKey.Neuter().ToString() + (segwit ? "" : "-[legacy]"));
|
||||
DerivationScheme = SupportedNetwork.NBXplorerNetwork.DerivationStrategyFactory.Parse(ExtKey.Neuter().ToString() + (segwit ? "" : "-[legacy]"));
|
||||
await store.AddDerivationScheme(StoreId, new DerivationSchemeViewModel()
|
||||
{
|
||||
DerivationScheme = DerivationScheme.ToString(),
|
||||
|
@ -5,9 +5,6 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
#if NETCOREAPP21
|
||||
using Microsoft.AspNetCore.Http.Internal;
|
||||
#endif
|
||||
using Xunit.Sdk;
|
||||
using System.Linq;
|
||||
|
||||
|
@ -151,6 +151,7 @@ namespace BTCPayServer.Tests
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null),
|
||||
});
|
||||
InvoiceEntity invoiceEntity = new InvoiceEntity();
|
||||
invoiceEntity.Networks = networkProvider;
|
||||
invoiceEntity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||
invoiceEntity.ProductInformation = new ProductInformation() {Price = 100};
|
||||
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary();
|
||||
@ -172,12 +173,15 @@ namespace BTCPayServer.Tests
|
||||
invoiceEntity.Payments.Add(
|
||||
new PaymentEntity()
|
||||
{
|
||||
|
||||
Accounted = true,
|
||||
CryptoCode = "BTC",
|
||||
NetworkFee = 0.00000100m
|
||||
NetworkFee = 0.00000100m,
|
||||
Network = networkProvider.GetNetwork("BTC"),
|
||||
}
|
||||
.SetCryptoPaymentData(new BitcoinLikePaymentData()
|
||||
{
|
||||
Network = networkProvider.GetNetwork("BTC"),
|
||||
Output = new TxOut() {Value = Money.Coins(0.00151263m)}
|
||||
}));
|
||||
accounting = btc.Calculate();
|
||||
@ -186,10 +190,12 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Accounted = true,
|
||||
CryptoCode = "BTC",
|
||||
NetworkFee = 0.00000100m
|
||||
NetworkFee = 0.00000100m,
|
||||
Network = networkProvider.GetNetwork("BTC")
|
||||
}
|
||||
.SetCryptoPaymentData(new BitcoinLikePaymentData()
|
||||
{
|
||||
Network = networkProvider.GetNetwork("BTC"),
|
||||
Output = new TxOut() {Value = accounting.Due}
|
||||
}));
|
||||
accounting = btc.Calculate();
|
||||
@ -258,6 +264,7 @@ namespace BTCPayServer.Tests
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null),
|
||||
});
|
||||
var entity = new InvoiceEntity();
|
||||
entity.Networks = networkProvider;
|
||||
#pragma warning disable CS0618
|
||||
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
|
||||
entity.SetPaymentMethod(new PaymentMethod()
|
||||
@ -317,6 +324,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(Money.Coins(1.3m), accounting.TotalDue);
|
||||
|
||||
entity = new InvoiceEntity();
|
||||
entity.Networks = networkProvider;
|
||||
entity.ProductInformation = new ProductInformation() {Price = 5000};
|
||||
PaymentMethodDictionary paymentMethods = new PaymentMethodDictionary();
|
||||
paymentMethods.Add(
|
||||
@ -445,6 +453,7 @@ namespace BTCPayServer.Tests
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null),
|
||||
});
|
||||
var entity = new InvoiceEntity();
|
||||
entity.Networks = networkProvider;
|
||||
#pragma warning disable CS0618
|
||||
entity.Payments = new List<PaymentEntity>();
|
||||
entity.SetPaymentMethod(new PaymentMethod()
|
||||
@ -551,10 +560,12 @@ namespace BTCPayServer.Tests
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanSetLightningServer()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.ActivateLightning();
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
@ -590,6 +601,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanSendLightningPaymentCLightning()
|
||||
{
|
||||
await ProcessLightningPayment(LightningConnectionType.CLightning);
|
||||
@ -597,6 +609,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanSendLightningPaymentCharge()
|
||||
{
|
||||
await ProcessLightningPayment(LightningConnectionType.Charge);
|
||||
@ -604,6 +617,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanSendLightningPaymentLnd()
|
||||
{
|
||||
await ProcessLightningPayment(LightningConnectionType.LndREST);
|
||||
@ -616,6 +630,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.ActivateLightning();
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
@ -649,7 +664,8 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
var localInvoice = await user.BitPay.GetInvoiceAsync(invoice.Id);
|
||||
Assert.Equal("complete", localInvoice.Status);
|
||||
Assert.Equal("False", localInvoice.ExceptionStatus.ToString());
|
||||
// C-Lightning may overpay for privacy
|
||||
Assert.Contains(localInvoice.ExceptionStatus.ToString(), new[] { "False", "paidOver" });
|
||||
});
|
||||
}
|
||||
|
||||
@ -797,21 +813,14 @@ namespace BTCPayServer.Tests
|
||||
acc.GrantAccess();
|
||||
acc.RegisterDerivationScheme("BTC", true);
|
||||
var btcDerivationScheme = acc.DerivationScheme;
|
||||
acc.RegisterDerivationScheme("LTC", true);
|
||||
|
||||
var walletController = acc.GetController<WalletsController>();
|
||||
WalletId walletId = new WalletId(acc.StoreId, "LTC");
|
||||
var rescan = Assert.IsType<RescanWalletModel>(Assert.IsType<ViewResult>(walletController.WalletRescan(walletId).Result).Model);
|
||||
Assert.False(rescan.Ok);
|
||||
Assert.True(rescan.IsFullySync);
|
||||
Assert.False(rescan.IsSupportedByCurrency);
|
||||
Assert.False(rescan.IsServerAdmin);
|
||||
|
||||
walletId = new WalletId(acc.StoreId, "BTC");
|
||||
var walletId = new WalletId(acc.StoreId, "BTC");
|
||||
acc.IsAdmin = true;
|
||||
walletController = acc.GetController<WalletsController>();
|
||||
|
||||
rescan = Assert.IsType<RescanWalletModel>(Assert.IsType<ViewResult>(walletController.WalletRescan(walletId).Result).Model);
|
||||
var rescan = Assert.IsType<RescanWalletModel>(Assert.IsType<ViewResult>(walletController.WalletRescan(walletId).Result).Model);
|
||||
Assert.True(rescan.Ok);
|
||||
Assert.True(rescan.IsFullySync);
|
||||
Assert.True(rescan.IsSupportedByCurrency);
|
||||
@ -938,15 +947,14 @@ namespace BTCPayServer.Tests
|
||||
var acc = tester.NewAccount();
|
||||
acc.GrantAccess();
|
||||
acc.RegisterDerivationScheme("BTC");
|
||||
acc.RegisterDerivationScheme("LTC");
|
||||
|
||||
var rateController = acc.GetController<RateController>();
|
||||
var GetBaseCurrencyRatesResult = JObject.Parse(((JsonResult)rateController.GetBaseCurrencyRates("BTC", default)
|
||||
.GetAwaiter().GetResult()).Value.ToJson()).ToObject<DataWrapper<Rate[]>>();
|
||||
Assert.NotNull(GetBaseCurrencyRatesResult);
|
||||
Assert.NotNull(GetBaseCurrencyRatesResult.Data);
|
||||
Assert.Equal(2, GetBaseCurrencyRatesResult.Data.Length);
|
||||
Assert.Single(GetBaseCurrencyRatesResult.Data.Where(o => o.Code == "LTC"));
|
||||
var rate = Assert.Single(GetBaseCurrencyRatesResult.Data);
|
||||
Assert.Equal("BTC", rate.Code);
|
||||
|
||||
var GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, default)
|
||||
.GetAwaiter().GetResult()).Value.ToJson()).ToObject<DataWrapper<Rate[]>>();
|
||||
@ -956,7 +964,7 @@ namespace BTCPayServer.Tests
|
||||
var store = acc.GetController<StoresController>();
|
||||
var ratesVM = (RatesViewModel)(Assert.IsType<ViewResult>(store.Rates()).Model);
|
||||
ratesVM.DefaultCurrencyPairs = "BTC_USD,LTC_USD";
|
||||
store.Rates(ratesVM).Wait();
|
||||
await store.Rates(ratesVM);
|
||||
store = acc.GetController<StoresController>();
|
||||
rateController = acc.GetController<RateController>();
|
||||
GetRatesResult = JObject.Parse(((JsonResult)rateController.GetRates(null, default)
|
||||
@ -1232,9 +1240,9 @@ namespace BTCPayServer.Tests
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
List<decimal> rates = new List<decimal>();
|
||||
rates.Add(CreateInvoice(tester, user, "coinaverage"));
|
||||
var bitflyer = CreateInvoice(tester, user, "bitflyer", "JPY");
|
||||
var bitflyer2 = CreateInvoice(tester, user, "bitflyer", "JPY");
|
||||
rates.Add(await CreateInvoice(tester, user, "coingecko"));
|
||||
var bitflyer = await CreateInvoice(tester, user, "bitflyer", "JPY");
|
||||
var bitflyer2 = await CreateInvoice(tester, user, "bitflyer", "JPY");
|
||||
Assert.Equal(bitflyer, bitflyer2); // Should be equal because cache
|
||||
rates.Add(bitflyer);
|
||||
|
||||
@ -1245,13 +1253,13 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private static decimal CreateInvoice(ServerTester tester, TestAccount user, string exchange, string currency = "USD")
|
||||
private static async Task<decimal> CreateInvoice(ServerTester tester, TestAccount user, string exchange, string currency = "USD")
|
||||
{
|
||||
var storeController = user.GetController<StoresController>();
|
||||
var vm = (RatesViewModel)((ViewResult)storeController.Rates()).Model;
|
||||
vm.PreferredExchange = exchange;
|
||||
storeController.Rates(vm).Wait();
|
||||
var invoice2 = user.BitPay.CreateInvoice(new Invoice()
|
||||
await storeController.Rates(vm);
|
||||
var invoice2 = await user.BitPay.CreateInvoiceAsync(new Invoice()
|
||||
{
|
||||
Price = 5000.0m,
|
||||
Currency = currency,
|
||||
@ -1332,7 +1340,7 @@ namespace BTCPayServer.Tests
|
||||
var vm = (RatesViewModel)((ViewResult)storeController.Rates()).Model;
|
||||
Assert.Equal(0.0, vm.Spread);
|
||||
vm.Spread = 40;
|
||||
storeController.Rates(vm).Wait();
|
||||
await storeController.Rates(vm);
|
||||
|
||||
|
||||
var invoice2 = user.BitPay.CreateInvoice(new Invoice()
|
||||
@ -1353,10 +1361,12 @@ namespace BTCPayServer.Tests
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Altcoins", "Altcoins")]
|
||||
public async Task CanHaveLTCOnlyStore()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.ActivateLTC();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
@ -1430,46 +1440,46 @@ namespace BTCPayServer.Tests
|
||||
var store = user.GetController<StoresController>();
|
||||
var rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates()).Model);
|
||||
Assert.False(rateVm.ShowScripting);
|
||||
Assert.Equal("coinaverage", rateVm.PreferredExchange);
|
||||
Assert.Equal(CoinGeckoRateProvider.CoinGeckoName, rateVm.PreferredExchange);
|
||||
Assert.Equal(0.0, rateVm.Spread);
|
||||
Assert.Null(rateVm.TestRateRules);
|
||||
|
||||
rateVm.PreferredExchange = "bitflyer";
|
||||
Assert.IsType<RedirectToActionResult>(store.Rates(rateVm, "Save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(await store.Rates(rateVm, "Save"));
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates()).Model);
|
||||
Assert.Equal("bitflyer", rateVm.PreferredExchange);
|
||||
|
||||
rateVm.ScriptTest = "BTC_JPY,BTC_CAD";
|
||||
rateVm.Spread = 10;
|
||||
store = user.GetController<StoresController>();
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates(rateVm, "Test").Result).Model);
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(await store.Rates(rateVm, "Test")).Model);
|
||||
Assert.NotNull(rateVm.TestRateRules);
|
||||
Assert.Equal(2, rateVm.TestRateRules.Count);
|
||||
Assert.False(rateVm.TestRateRules[0].Error);
|
||||
Assert.StartsWith("(bitflyer(BTC_JPY)) * (0.9, 1.1) =", rateVm.TestRateRules[0].Rule, StringComparison.OrdinalIgnoreCase);
|
||||
Assert.True(rateVm.TestRateRules[1].Error);
|
||||
Assert.IsType<RedirectToActionResult>(store.Rates(rateVm, "Save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(await store.Rates(rateVm, "Save"));
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(store.ShowRateRulesPost(true).Result);
|
||||
Assert.IsType<RedirectToActionResult>(store.Rates(rateVm, "Save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(await store.Rates(rateVm, "Save"));
|
||||
store = user.GetController<StoresController>();
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates()).Model);
|
||||
Assert.Equal(rateVm.StoreId, user.StoreId);
|
||||
Assert.Equal(rateVm.DefaultScript, rateVm.Script);
|
||||
Assert.True(rateVm.ShowScripting);
|
||||
rateVm.ScriptTest = "BTC_JPY";
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates(rateVm, "Test").Result).Model);
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(await store.Rates(rateVm, "Test")).Model);
|
||||
Assert.True(rateVm.ShowScripting);
|
||||
Assert.Contains("(bitflyer(BTC_JPY)) * (0.9, 1.1) = ", rateVm.TestRateRules[0].Rule, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
rateVm.ScriptTest = "BTC_USD,BTC_CAD,DOGE_USD,DOGE_CAD";
|
||||
rateVm.Script = "DOGE_X = bittrex(DOGE_BTC) * BTC_X;\n" +
|
||||
"X_CAD = quadrigacx(X_CAD);\n" +
|
||||
"X_X = coinaverage(X_X);";
|
||||
"X_X = coingecko(X_X);";
|
||||
rateVm.Spread = 50;
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates(rateVm, "Test").Result).Model);
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(await store.Rates(rateVm, "Test")).Model);
|
||||
Assert.True(rateVm.TestRateRules.All(t => !t.Error));
|
||||
Assert.IsType<RedirectToActionResult>(store.Rates(rateVm, "Save").Result);
|
||||
Assert.IsType<RedirectToActionResult>(await store.Rates(rateVm, "Save"));
|
||||
store = user.GetController<StoresController>();
|
||||
rateVm = Assert.IsType<RatesViewModel>(Assert.IsType<ViewResult>(store.Rates()).Model);
|
||||
Assert.Equal(50, rateVm.Spread);
|
||||
@ -1480,10 +1490,12 @@ namespace BTCPayServer.Tests
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Altcoins", "Altcoins")]
|
||||
public async Task CanPayWithTwoCurrencies()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.ActivateLTC();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
@ -1557,7 +1569,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.NotNull(ltcCryptoInfo);
|
||||
invoiceAddress = BitcoinAddress.Create(ltcCryptoInfo.Address, cashCow.Network);
|
||||
var secondPayment = Money.Coins(decimal.Parse(ltcCryptoInfo.Due, CultureInfo.InvariantCulture));
|
||||
cashCow.Generate(2); // LTC is not worth a lot, so just to make sure we have money...
|
||||
cashCow.Generate(4); // LTC is not worth a lot, so just to make sure we have money...
|
||||
cashCow.SendToAddress(invoiceAddress, secondPayment);
|
||||
Logs.Tester.LogInformation("Second payment sent to " + invoiceAddress);
|
||||
TestUtils.Eventually(() =>
|
||||
@ -1697,10 +1709,14 @@ namespace BTCPayServer.Tests
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Altcoins", "Altcoins")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanAddDerivationSchemes()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.ActivateLTC();
|
||||
tester.ActivateLightning();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
@ -1729,15 +1745,15 @@ namespace BTCPayServer.Tests
|
||||
Assert.False(lightningVM.Enabled);
|
||||
|
||||
// Only Enabling/Disabling the payment method must redirect to store page
|
||||
var derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
var derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
Assert.True(derivationVM.Enabled);
|
||||
derivationVM.Enabled = false;
|
||||
Assert.IsType<RedirectToActionResult>(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult());
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
Assert.False(derivationVM.Enabled);
|
||||
|
||||
// Clicking next without changing anything should send to the confirmation screen
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
|
||||
Assert.True(derivationVM.Confirmation);
|
||||
|
||||
@ -1756,18 +1772,18 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// Removing the derivation scheme, should redirect to store page
|
||||
var oldScheme = derivationVM.DerivationScheme;
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
derivationVM.DerivationScheme = null;
|
||||
Assert.IsType<RedirectToActionResult>(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult());
|
||||
|
||||
// Setting it again should redirect to the confirmation page
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
derivationVM.DerivationScheme = oldScheme;
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
|
||||
Assert.True(derivationVM.Confirmation);
|
||||
|
||||
// Can we upload coldcard settings? (Should fail, we are giving a mainnet file to a testnet network)
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
string content = "{\"keystore\": {\"ckcc_xpub\": \"xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw\", \"xpub\": \"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}";
|
||||
derivationVM.ColdcardPublicFile = TestUtils.GetFormFile("wallet.json", content);
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
|
||||
@ -1775,7 +1791,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// And with a good file? (upub)
|
||||
content = "{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"upub5DBYp1qGgsTrkzCptMGZc2x18pquLwGrBw6nS59T4NViZ4cni1mGowQzziy85K8vzkp1jVtWrSkLhqk9KDfvrGeB369wGNYf39kX8rQfiLn\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}";
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
derivationVM.ColdcardPublicFile = TestUtils.GetFormFile("wallet2.json", content);
|
||||
derivationVM.Enabled = true;
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
|
||||
@ -1835,21 +1851,50 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanSetPaymentMethodLimits()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
var vm = Assert.IsType<CheckoutExperienceViewModel>(Assert.IsType<ViewResult>(user.GetController<StoresController>().CheckoutExperience()).Model);
|
||||
vm.OnChainMinValue = "5 USD";
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<StoresController>().CheckoutExperience(vm).Result);
|
||||
|
||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Price = 5.5m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
Assert.Single(invoice.CryptoInfo);
|
||||
Assert.Equal(PaymentTypes.BTCLike.ToString(), invoice.CryptoInfo[0].PaymentType);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanSetPaymentMethodLimitsLightning()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.ActivateLightning();
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.Charge);
|
||||
var vm = Assert.IsType<CheckoutExperienceViewModel>(Assert.IsType<ViewResult>(user.GetController<StoresController>().CheckoutExperience()).Model);
|
||||
vm.LightningMaxValue = "2 USD";
|
||||
vm.OnChainMinValue = "5 USD";
|
||||
Assert.IsType<RedirectToActionResult>(user.GetController<StoresController>().CheckoutExperience(vm).Result);
|
||||
|
||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
@ -1864,19 +1909,6 @@ namespace BTCPayServer.Tests
|
||||
|
||||
Assert.Single(invoice.CryptoInfo);
|
||||
Assert.Equal(PaymentTypes.LightningLike.ToString(), invoice.CryptoInfo[0].PaymentType);
|
||||
|
||||
invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Price = 5.5m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
Assert.Single(invoice.CryptoInfo);
|
||||
Assert.Equal(PaymentTypes.BTCLike.ToString(), invoice.CryptoInfo[0].PaymentType);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2648,16 +2680,19 @@ noninventoryitem:
|
||||
public void CanQueryDirectProviders()
|
||||
{
|
||||
var factory = CreateBTCPayRateFactory();
|
||||
|
||||
var all = string.Join("\r\n", factory.GetSupportedExchanges().Select(e => e.Id).ToArray());
|
||||
foreach (var result in factory
|
||||
.Providers
|
||||
.Where(p => p.Value is BackgroundFetcherRateProvider)
|
||||
.Select(p => (ExpectedName: p.Key, ResultAsync: p.Value.GetRatesAsync(default), Fetcher: (BackgroundFetcherRateProvider)p.Value))
|
||||
.ToList())
|
||||
{
|
||||
|
||||
Logs.Tester.LogInformation($"Testing {result.ExpectedName}");
|
||||
if (result.ExpectedName == "quadrigacx")
|
||||
continue; // 29 january, the exchange is down
|
||||
if (result.ExpectedName == "coinaverage")
|
||||
continue; // no more free plan
|
||||
result.Fetcher.InvalidateCache();
|
||||
var exchangeRates = result.ResultAsync.Result;
|
||||
result.Fetcher.InvalidateCache();
|
||||
@ -2685,6 +2720,47 @@ noninventoryitem:
|
||||
factory.Providers["kraken"].GetRatesAsync(default).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanExportBackgroundFetcherState()
|
||||
{
|
||||
var factory = CreateBTCPayRateFactory();
|
||||
var provider = (BackgroundFetcherRateProvider)factory.Providers["kraken"];
|
||||
await provider.GetRatesAsync(default);
|
||||
var state = provider.GetState();
|
||||
Assert.Single(state.Rates, r => r.Pair == new CurrencyPair("BTC", "EUR"));
|
||||
var provider2 = new BackgroundFetcherRateProvider("kraken", provider.Inner)
|
||||
{
|
||||
RefreshRate = provider.RefreshRate,
|
||||
ValidatyTime = provider.ValidatyTime
|
||||
};
|
||||
using (var cts = new CancellationTokenSource())
|
||||
{
|
||||
cts.Cancel();
|
||||
// Should throw
|
||||
await Assert.ThrowsAsync<OperationCanceledException>(async () => await provider2.GetRatesAsync(cts.Token));
|
||||
}
|
||||
provider2.LoadState(state);
|
||||
Assert.Equal(provider.LastRequested, provider2.LastRequested);
|
||||
using (var cts = new CancellationTokenSource())
|
||||
{
|
||||
cts.Cancel();
|
||||
// Should not throw, as things should be cached
|
||||
await provider2.GetRatesAsync(cts.Token);
|
||||
}
|
||||
Assert.Equal(provider.ExchangeName, provider2.ExchangeName);
|
||||
Assert.Equal(provider.NextUpdate, provider2.NextUpdate);
|
||||
Assert.NotEqual(provider.LastRequested, provider2.LastRequested);
|
||||
Assert.Equal(provider.Expiration, provider2.Expiration);
|
||||
|
||||
var str = JsonConvert.SerializeObject(state);
|
||||
var state2 = JsonConvert.DeserializeObject<BackgroundFetcherState>(str);
|
||||
var str2 = JsonConvert.SerializeObject(state2);
|
||||
Assert.Equal(str, str2);
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public void CanGetRateCryptoCurrenciesByDefault()
|
||||
@ -2709,7 +2785,7 @@ noninventoryitem:
|
||||
|
||||
public static RateProviderFactory CreateBTCPayRateFactory()
|
||||
{
|
||||
return new RateProviderFactory(CreateMemoryCache(), null, new CoinAverageSettings());
|
||||
return new RateProviderFactory(CreateMemoryCache(), new MockHttpClientFactory(), new CoinAverageSettings());
|
||||
}
|
||||
|
||||
private static MemoryCacheOptions CreateMemoryCache()
|
||||
@ -2850,7 +2926,7 @@ noninventoryitem:
|
||||
spy.AssertHit();
|
||||
|
||||
factory.Providers.Clear();
|
||||
var fetch = new BackgroundFetcherRateProvider(spy);
|
||||
var fetch = new BackgroundFetcherRateProvider("spy", spy);
|
||||
fetch.DoNotAutoFetchIfExpired = true;
|
||||
factory.Providers.Add("bittrex", fetch);
|
||||
fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default).GetAwaiter().GetResult();
|
||||
@ -2902,10 +2978,14 @@ noninventoryitem:
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanCreateInvoiceWithSpecificPaymentMethods()
|
||||
[Trait("Altcoins", "Altcoins")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanCreateInvoiceWithSpecificPaymentMethods()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.ActivateLightning();
|
||||
tester.ActivateLTC();
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
|
@ -76,7 +76,7 @@ services:
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.0.0.66
|
||||
image: nicolasdorier/nbxplorer:2.1.5
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
@ -84,7 +84,7 @@ services:
|
||||
- "32838"
|
||||
environment:
|
||||
NBXPLORER_NETWORK: regtest
|
||||
NBXPLORER_CHAINS: "btc,ltc"
|
||||
NBXPLORER_CHAINS: "btc,ltc,lbtc"
|
||||
NBXPLORER_BTCRPCURL: http://bitcoind:43782/
|
||||
NBXPLORER_BTCNODEENDPOINT: bitcoind:39388
|
||||
NBXPLORER_BTCRPCUSER: ceiwHEbqWI83
|
||||
@ -93,12 +93,17 @@ services:
|
||||
NBXPLORER_LTCNODEENDPOINT: litecoind:39388
|
||||
NBXPLORER_LTCRPCUSER: ceiwHEbqWI83
|
||||
NBXPLORER_LTCRPCPASSWORD: DwubwWsoo3
|
||||
NBXPLORER_LBTCRPCURL: "http://elementsd-liquid:19332/"
|
||||
NBXPLORER_LBTCNODEENDPOINT: "elementsd-liquid:19444"
|
||||
NBXPLORER_LBTCRPCUSER: "liquid"
|
||||
NBXPLORER_LBTCRPCPASSWORD: "liquid"
|
||||
NBXPLORER_BIND: 0.0.0.0:32838
|
||||
NBXPLORER_VERBOSE: 1
|
||||
NBXPLORER_NOAUTH: 1
|
||||
links:
|
||||
- bitcoind
|
||||
- litecoind
|
||||
- elementsd-liquid
|
||||
|
||||
|
||||
bitcoind:
|
||||
@ -127,7 +132,7 @@ services:
|
||||
- "bitcoin_datadir:/data"
|
||||
|
||||
customer_lightningd:
|
||||
image: btcpayserver/lightning:v0.7.3-dev
|
||||
image: btcpayserver/lightning:v0.8.0-dev
|
||||
stop_signal: SIGKILL
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
@ -174,7 +179,7 @@ services:
|
||||
- merchant_lightningd
|
||||
|
||||
merchant_lightningd:
|
||||
image: btcpayserver/lightning:v0.7.3-dev
|
||||
image: btcpayserver/lightning:v0.8.0-dev
|
||||
stop_signal: SIGKILL
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
@ -215,6 +220,34 @@ services:
|
||||
expose:
|
||||
- "43782" # RPC
|
||||
- "39388" # P2P
|
||||
|
||||
elementsd-liquid:
|
||||
restart: always
|
||||
container_name: btcpayserver_elementsd_liquid
|
||||
image: btcpayserver/elements:0.18.1.1-1
|
||||
environment:
|
||||
ELEMENTS_CHAIN: elementsregtest
|
||||
ELEMENTS_EXTRA_ARGS: |
|
||||
mainchainrpcport=43782
|
||||
mainchainrpchost=bitcoind
|
||||
mainchainrpcuser=liquid
|
||||
mainchainrpcpassword=liquid
|
||||
rpcport=19332
|
||||
rpcbind=0.0.0.0:19332
|
||||
rpcauth=liquid:c8bf1a8961d97f224cb21224aaa8235d$$402f4a8907683d057b8c58a42940b6e54d1638322a42986ae28ebb844e603ae6
|
||||
port=19444
|
||||
whitelist=0.0.0.0/0
|
||||
validatepegin=0
|
||||
initialfreecoins=210000000000000
|
||||
con_dyna_deploy_start=99999999999
|
||||
expose:
|
||||
- "19332"
|
||||
- "19444"
|
||||
ports:
|
||||
- "19332:19332"
|
||||
- "19444:19444"
|
||||
volumes:
|
||||
- "elementsd_liquid_datadir:/data"
|
||||
|
||||
postgres:
|
||||
image: postgres:9.6.5
|
||||
@ -287,6 +320,7 @@ services:
|
||||
volumes:
|
||||
sshd_datadir:
|
||||
bitcoin_datadir:
|
||||
elementsd_liquid_datadir:
|
||||
customer_lightningd_datadir:
|
||||
merchant_lightningd_datadir:
|
||||
lightning_charge_datadir:
|
||||
|
@ -2,8 +2,8 @@
|
||||
set -e
|
||||
|
||||
FILTERS=" "
|
||||
if [[ "$TEST_FILTERS" ]]; then
|
||||
if [ ! -z "$TEST_FILTERS" ]; then
|
||||
FILTERS="--filter $TEST_FILTERS"
|
||||
fi
|
||||
|
||||
dotnet test $FILTERS --no-build -v n
|
||||
dotnet test $FILTERS --no-build -v n < /dev/null
|
||||
|
@ -6,7 +6,6 @@
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Compile Remove="Build\**" />
|
||||
<Compile Remove="Storage\Services\Providers\GoogleCloudStorage\**" Condition="'$(TargetFramework)' != 'netcoreapp2.1'" />
|
||||
<Compile Remove="wwwroot\bundles\jqueryvalidate\**" />
|
||||
<Compile Remove="wwwroot\vendor\jquery-nice-select\**" />
|
||||
<Content Remove="Build\**" />
|
||||
@ -27,52 +26,48 @@
|
||||
<EmbeddedResource Include="Currencies.txt" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.5" />
|
||||
<PackageReference Include="BuildBundlerMinifier" Version="3.1.430" />
|
||||
<PackageReference Include="BundlerMinifier.Core" Version="3.1.430" />
|
||||
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.1.430" />
|
||||
<PackageReference Include="BTCPayServer.Hwi" Version="1.1.3" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.8" />
|
||||
<PackageReference Include="BuildBundlerMinifier" Version="3.2.435" />
|
||||
<PackageReference Include="BundlerMinifier.Core" Version="3.2.435" />
|
||||
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
|
||||
<PackageReference Include="HtmlSanitizer" Version="4.0.217" />
|
||||
<PackageReference Include="LedgerWallet" Version="2.0.0.3" />
|
||||
<PackageReference Include="LedgerWallet" Version="2.0.0.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
|
||||
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.6.2">
|
||||
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.9.8">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.34" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.35" />
|
||||
<PackageReference Include="DBriize" Version="1.0.1.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.2" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.3" />
|
||||
<PackageReference Include="NicolasDorier.RateLimits" Version="1.0.0.9" />
|
||||
<PackageReference Include="NicolasDorier.RateLimits" Version="1.1.0" />
|
||||
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.18" />
|
||||
<PackageReference Include="Serilog" Version="2.7.1" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="2.1.1" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="4.0.0" />
|
||||
<PackageReference Include="Serilog" Version="2.9.0" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
|
||||
<PackageReference Include="SSH.NET" Version="2016.1.0" />
|
||||
<PackageReference Include="Text.Analyzers" Version="2.6.2">
|
||||
<PackageReference Include="Text.Analyzers" Version="2.6.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.9" AllowExplicitVersion="true" Condition="'$(TargetFramework)' == 'netcoreapp2.1'" />
|
||||
<PackageReference Include="TwentyTwenty.Storage" Version="2.11.2" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Amazon" Version="2.11.2" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Azure" Version="2.11.2" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Google" Version="2.11.2" Condition="'$(TargetFramework)' == 'netcoreapp2.1'" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Local" Version="2.11.2" />
|
||||
<PackageReference Include="TwentyTwenty.Storage" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Amazon" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Azure" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Google" Version="2.12.1" />
|
||||
<PackageReference Include="TwentyTwenty.Storage.Local" Version="2.12.1" />
|
||||
<PackageReference Include="U2F.Core" Version="1.0.4" />
|
||||
<PackageReference Include="YamlDotNet" Version="5.2.1" />
|
||||
<PackageReference Include="OpenIddict" Version="3.0.0-alpha1.19515.63" />
|
||||
<PackageReference Include="OpenIddict.Server.AspNetCore" Version="3.0.0-alpha1.19515.63"></PackageReference>
|
||||
<PackageReference Include="OpenIddict.Validation.AspNetCore" Version="3.0.0-alpha1.19515.63"></PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp2.1'">
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.0.0"></PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="3.0.0" />
|
||||
<PackageReference Include="YamlDotNet" Version="8.0.0" />
|
||||
<PackageReference Include="OpenIddict" Version="3.0.0-alpha1.20058.15" />
|
||||
<PackageReference Include="OpenIddict.Server.AspNetCore" Version="3.0.0-alpha1.20058.15"></PackageReference>
|
||||
<PackageReference Include="OpenIddict.Validation.AspNetCore" Version="3.0.0-alpha1.20058.15"></PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="3.1.0" Condition="'$(Configuration)' == 'Debug'" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@ -201,13 +196,16 @@
|
||||
<Content Update="Views\Wallets\WalletRescan.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletSendVault.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletSendLedger.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletTransactions.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Remove="Views\Server\EditGoogleCloudStorageStorageProvider.cshtml" Condition="'$(TargetFramework)' != 'netcoreapp2.1'">
|
||||
<Content Remove="Views\Server\EditGoogleCloudStorageStorageProvider.cshtml">
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\_Nav.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
|
@ -85,9 +85,17 @@ namespace BTCPayServer.Configuration
|
||||
throw new ConfigException($"You need to run BTCPayServer with the run.sh or run.ps1 script");
|
||||
|
||||
var supportedChains = conf.GetOrDefault<string>("chains", "btc")
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(t => t.ToUpperInvariant());
|
||||
NetworkProvider = new BTCPayNetworkProvider(NetworkType).Filter(supportedChains.ToArray());
|
||||
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||
.Select(t => t.ToUpperInvariant()).ToHashSet();
|
||||
|
||||
var networkProvider = new BTCPayNetworkProvider(NetworkType);
|
||||
var filtered = networkProvider.Filter(supportedChains.ToArray());
|
||||
var elementsBased = filtered.GetAll().OfType<ElementsBTCPayNetwork>();
|
||||
var parentChains = elementsBased.Select(network => network.NetworkCryptoCode.ToUpperInvariant()).Distinct();
|
||||
var allSubChains = networkProvider.GetAll().OfType<ElementsBTCPayNetwork>()
|
||||
.Where(network => parentChains.Contains(network.NetworkCryptoCode)).Select(network => network.CryptoCode.ToUpperInvariant());
|
||||
supportedChains.AddRange(allSubChains);
|
||||
NetworkProvider = networkProvider.Filter(supportedChains.ToArray());
|
||||
foreach (var chain in supportedChains)
|
||||
{
|
||||
if (NetworkProvider.GetNetwork<BTCPayNetworkBase>(chain) == null)
|
||||
|
@ -22,7 +22,6 @@ using OpenIddict.Abstractions;
|
||||
using OpenIddict.Core;
|
||||
using OpenIddict.Server;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using OpenIddict.Server.AspNetCore;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
@ -126,7 +125,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var application = await _applicationManager.FindByClientIdAsync(request.ClientId);
|
||||
var authorization = await _authorizationManager.CreateAsync(User, user.Id, application.Id,
|
||||
type, principal.GetScopes().ToImmutableArray());
|
||||
type, principal.GetScopes());
|
||||
principal.SetInternalAuthorizationId(authorization.Id);
|
||||
}
|
||||
|
||||
|
@ -19,10 +19,10 @@ namespace BTCPayServer.Controllers
|
||||
private InvoiceRepository _InvoiceRepository;
|
||||
|
||||
public InvoiceControllerAPI(InvoiceController invoiceController,
|
||||
InvoiceRepository invoceRepository)
|
||||
InvoiceRepository invoiceRepository)
|
||||
{
|
||||
_InvoiceController = invoiceController;
|
||||
_InvoiceRepository = invoceRepository;
|
||||
_InvoiceRepository = invoiceRepository;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
|
@ -294,6 +294,10 @@ namespace BTCPayServer.Controllers
|
||||
};
|
||||
|
||||
paymentMethodHandler.PreparePaymentModel(model, dto, storeBlob);
|
||||
if (model.IsLightning && storeBlob.LightningAmountInSatoshi && model.CryptoCode == "Sats")
|
||||
{
|
||||
model.Rate = _CurrencyNameTable.DisplayFormatCurrency(paymentMethod.Rate / 100_000_000, paymentMethod.ParentEntity.ProductInformation.Currency);
|
||||
}
|
||||
model.UISettings = paymentMethodHandler.GetCheckoutUISettings();
|
||||
model.PaymentMethodId = paymentMethodId.ToString();
|
||||
var expiration = TimeSpan.FromSeconds(model.ExpirationSeconds);
|
||||
@ -370,7 +374,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (invoiceId != expectedId || webSocket.State != WebSocketState.Open)
|
||||
return;
|
||||
CancellationTokenSource cts = new CancellationTokenSource();
|
||||
using CancellationTokenSource cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(5000);
|
||||
try
|
||||
{
|
||||
@ -496,7 +500,7 @@ namespace BTCPayServer.Controllers
|
||||
public async Task<IActionResult> CreateInvoice()
|
||||
{
|
||||
var stores = new SelectList(await _StoreRepository.GetStoresByUserId(GetUserId()), nameof(StoreData.Id), nameof(StoreData.StoreName), null);
|
||||
if (stores.Count() == 0)
|
||||
if (!stores.Any())
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = "You need to create at least one store before creating a transaction";
|
||||
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
|
||||
@ -520,7 +524,7 @@ namespace BTCPayServer.Controllers
|
||||
return View(model);
|
||||
}
|
||||
|
||||
if (store.GetSupportedPaymentMethods(_NetworkProvider).Count() == 0)
|
||||
if (!store.GetSupportedPaymentMethods(_NetworkProvider).Any())
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.StoreId), "You need to configure the derivation scheme in order to create an invoice");
|
||||
return View(model);
|
||||
@ -620,7 +624,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
case JTokenType.Array:
|
||||
var items = item.Value.AsEnumerable().ToList();
|
||||
for (var i = 0; i < items.Count(); i++)
|
||||
for (var i = 0; i < items.Count; i++)
|
||||
{
|
||||
result.Add($"{item.Key}[{i}]", ParsePosData(items[i].ToString()));
|
||||
}
|
||||
|
@ -19,9 +19,6 @@ using System.Globalization;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.U2F;
|
||||
using BTCPayServer.Data;
|
||||
#if NETCOREAPP21
|
||||
using IWebHostEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment;
|
||||
#endif
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
|
@ -10,10 +10,8 @@ using BTCPayServer.Storage.Services.Providers.AzureBlobStorage;
|
||||
using BTCPayServer.Storage.Services.Providers.AzureBlobStorage.Configuration;
|
||||
using BTCPayServer.Storage.Services.Providers.FileSystemStorage;
|
||||
using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration;
|
||||
#if NETCOREAPP21
|
||||
using BTCPayServer.Storage.Services.Providers.GoogleCloudStorage;
|
||||
using BTCPayServer.Storage.Services.Providers.GoogleCloudStorage.Configuration;
|
||||
#endif
|
||||
using BTCPayServer.Storage.Services.Providers.Models;
|
||||
using BTCPayServer.Storage.ViewModels;
|
||||
using BTCPayServer.Views;
|
||||
@ -221,11 +219,9 @@ namespace BTCPayServer.Controllers
|
||||
case AmazonS3FileProviderService fileProviderService:
|
||||
return View(nameof(EditAmazonS3StorageProvider),
|
||||
fileProviderService.GetProviderConfiguration(data));
|
||||
#if NETCOREAPP21
|
||||
case GoogleCloudStorageFileProviderService fileProviderService:
|
||||
return View(nameof(EditGoogleCloudStorageStorageProvider),
|
||||
fileProviderService.GetProviderConfiguration(data));
|
||||
#endif
|
||||
case FileSystemFileProviderService fileProviderService:
|
||||
if (data.Provider != BTCPayServer.Storage.Models.StorageProvider.FileSystem)
|
||||
{
|
||||
@ -252,14 +248,14 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
return await SaveStorageProvider(viewModel, BTCPayServer.Storage.Models.StorageProvider.AmazonS3);
|
||||
}
|
||||
#if NETCOREAPP21
|
||||
|
||||
[HttpPost("server/storage/GoogleCloudStorage")]
|
||||
public async Task<IActionResult> EditGoogleCloudStorageStorageProvider(
|
||||
GoogleCloudStorageConfiguration viewModel)
|
||||
{
|
||||
return await SaveStorageProvider(viewModel, BTCPayServer.Storage.Models.StorageProvider.GoogleCloudStorage);
|
||||
}
|
||||
#endif
|
||||
|
||||
[HttpPost("server/storage/FileSystem")]
|
||||
public async Task<IActionResult> EditFileSystemStorageProvider(FileSystemStorageConfiguration viewModel)
|
||||
{
|
||||
|
@ -1121,9 +1121,11 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
try
|
||||
{
|
||||
var client = model.Settings.CreateSmtpClient();
|
||||
var message = model.Settings.CreateMailMessage(new MailAddress(model.TestEmail), "BTCPay test", "BTCPay test");
|
||||
await client.SendMailAsync(message);
|
||||
using (var client = model.Settings.CreateSmtpClient())
|
||||
using (var message = model.Settings.CreateMailMessage(new MailAddress(model.TestEmail), "BTCPay test", "BTCPay test"))
|
||||
{
|
||||
await client.SendMailAsync(message);
|
||||
}
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Email sent to " + model.TestEmail + ", please, verify you received it";
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
@ -17,7 +18,9 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -25,7 +28,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
[HttpGet]
|
||||
[Route("{storeId}/derivations/{cryptoCode}")]
|
||||
public IActionResult AddDerivationScheme(string storeId, string cryptoCode)
|
||||
public async Task<IActionResult> AddDerivationScheme(string storeId, string cryptoCode)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
@ -40,7 +43,14 @@ namespace BTCPayServer.Controllers
|
||||
vm.CryptoCode = cryptoCode;
|
||||
vm.RootKeyPath = network.GetRootKeyPath();
|
||||
vm.Network = network;
|
||||
SetExistingValues(store, vm);
|
||||
var derivation = GetExistingDerivationStrategy(vm.CryptoCode, store);
|
||||
if (derivation != null)
|
||||
{
|
||||
vm.DerivationScheme = derivation.AccountDerivation.ToString();
|
||||
vm.Config = derivation.ToJson();
|
||||
}
|
||||
vm.Enabled = !store.GetStoreBlob().IsExcluded(new PaymentMethodId(vm.CryptoCode, PaymentTypes.BTCLike));
|
||||
vm.CanUseHotWallet = await CanUseHotWallet();
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
@ -86,7 +96,7 @@ namespace BTCPayServer.Controllers
|
||||
var getxpubResult = new GetXPubs();
|
||||
getxpubResult.ExtPubKey = await hw.GetExtPubKey(network, k, normalOperationTimeout.Token);
|
||||
var segwit = network.NBitcoinNetwork.Consensus.SupportSegwit;
|
||||
var derivation = new DerivationStrategyFactory(network.NBitcoinNetwork).CreateDirectDerivationStrategy(getxpubResult.ExtPubKey, new DerivationStrategyOptions()
|
||||
var derivation = network.NBXplorerNetwork.DerivationStrategyFactory.CreateDirectDerivationStrategy(getxpubResult.ExtPubKey, new DerivationStrategyOptions()
|
||||
{
|
||||
ScriptPubKeyType = segwit ? ScriptPubKeyType.SegwitP2SH : ScriptPubKeyType.Legacy
|
||||
});
|
||||
@ -107,7 +117,8 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
UTF8Encoding UTF8NOBOM = new UTF8Encoding(false);
|
||||
var bytes = UTF8NOBOM.GetBytes(JsonConvert.SerializeObject(result, network.NBXplorerNetwork.JsonSerializerSettings));
|
||||
await webSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, new CancellationTokenSource(2000).Token);
|
||||
using var cts = new CancellationTokenSource(2000);
|
||||
await webSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, cts.Token);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
@ -119,19 +130,6 @@ namespace BTCPayServer.Controllers
|
||||
return new EmptyResult();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void SetExistingValues(StoreData store, DerivationSchemeViewModel vm)
|
||||
{
|
||||
var derivation = GetExistingDerivationStrategy(vm.CryptoCode, store);
|
||||
if (derivation != null)
|
||||
{
|
||||
vm.DerivationScheme = derivation.AccountDerivation.ToString();
|
||||
vm.Config = derivation.ToJson();
|
||||
}
|
||||
vm.Enabled = !store.GetStoreBlob().IsExcluded(new PaymentMethodId(vm.CryptoCode, PaymentTypes.BTCLike));
|
||||
}
|
||||
|
||||
private DerivationSchemeSettings GetExistingDerivationStrategy(string cryptoCode, StoreData store)
|
||||
{
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
|
||||
@ -140,8 +138,6 @@ namespace BTCPayServer.Controllers
|
||||
.FirstOrDefault(d => d.PaymentId == id);
|
||||
return existing;
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}/derivations/{cryptoCode}")]
|
||||
@ -162,7 +158,7 @@ namespace BTCPayServer.Controllers
|
||||
vm.Network = network;
|
||||
vm.RootKeyPath = network.GetRootKeyPath();
|
||||
DerivationSchemeSettings strategy = null;
|
||||
|
||||
|
||||
var wallet = _WalletProvider.GetWallet(network);
|
||||
if (wallet == null)
|
||||
{
|
||||
@ -179,7 +175,7 @@ namespace BTCPayServer.Controllers
|
||||
Message = "Config file was not in the correct format"
|
||||
});
|
||||
vm.Confirmation = false;
|
||||
return View(vm);
|
||||
return View(nameof(AddDerivationScheme),vm);
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,7 +189,7 @@ namespace BTCPayServer.Controllers
|
||||
Message = "Coldcard public file was not in the correct format"
|
||||
});
|
||||
vm.Confirmation = false;
|
||||
return View(vm);
|
||||
return View(nameof(AddDerivationScheme),vm);
|
||||
}
|
||||
}
|
||||
else
|
||||
@ -229,7 +225,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid Derivation Scheme");
|
||||
vm.Confirmation = false;
|
||||
return View(vm);
|
||||
return View(nameof(AddDerivationScheme),vm);
|
||||
}
|
||||
}
|
||||
|
||||
@ -246,7 +242,7 @@ namespace BTCPayServer.Controllers
|
||||
var willBeExcluded = !vm.Enabled;
|
||||
|
||||
var showAddress = // Show addresses if:
|
||||
// - If the user is testing the hint address in confirmation screen
|
||||
// - If the user is testing the hint address in confirmation screen
|
||||
(vm.Confirmation && !string.IsNullOrWhiteSpace(vm.HintAddress)) ||
|
||||
// - The user is clicking on continue after changing the config
|
||||
(!vm.Confirmation && oldConfig != vm.Config) ||
|
||||
@ -280,7 +276,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"Derivation settings for {network.CryptoCode} has been modified.";
|
||||
}
|
||||
return RedirectToAction(nameof(UpdateStore), new {storeId = storeId});
|
||||
return RedirectToAction(nameof(UpdateStore), new { storeId = storeId });
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(vm.HintAddress))
|
||||
{
|
||||
@ -320,6 +316,65 @@ namespace BTCPayServer.Controllers
|
||||
return ShowAddresses(vm, strategy);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}/derivations/{cryptoCode}/generatenbxwallet")]
|
||||
public async Task<IActionResult> GenerateNBXWallet(string storeId, string cryptoCode,
|
||||
GenerateWalletRequest request)
|
||||
{
|
||||
if (!await CanUseHotWallet())
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var network = _NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var client = _ExplorerProvider.GetExplorerClient(cryptoCode);
|
||||
var response = await client.GenerateWalletAsync(request);
|
||||
if (response == null)
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Html = "There was an error generating your wallet. Is your node available?"
|
||||
});
|
||||
return RedirectToAction("AddDerivationScheme", new {storeId, cryptoCode});
|
||||
}
|
||||
var store = HttpContext.GetStoreData();
|
||||
var result = await AddDerivationScheme(storeId,
|
||||
new DerivationSchemeViewModel()
|
||||
{
|
||||
Confirmation = false,
|
||||
Network = network,
|
||||
RootFingerprint = response.AccountKeyPath.MasterFingerprint.ToString(),
|
||||
RootKeyPath = network.GetRootKeyPath(),
|
||||
CryptoCode = cryptoCode,
|
||||
DerivationScheme = response.DerivationScheme.ToString(),
|
||||
Source = "NBXplorer",
|
||||
AccountKey = response.AccountHDKey.Neuter().ToWif(),
|
||||
DerivationSchemeFormat = "BTCPay",
|
||||
KeyPath = response.AccountKeyPath.KeyPath.ToString(),
|
||||
Enabled = !store.GetStoreBlob()
|
||||
.IsExcluded(new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike))
|
||||
}, cryptoCode);
|
||||
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
Html = !string.IsNullOrEmpty(request.ExistingMnemonic)
|
||||
? "Your wallet has been imported."
|
||||
: $"Your wallet has been generated. Please store your seed securely! <br/><code>{response.Mnemonic}</code>"
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<bool> CanUseHotWallet()
|
||||
{
|
||||
var isAdmin = (await _authorizationService.AuthorizeAsync(User, BTCPayServer.Security.Policies.CanModifyServerSettings.Key)).Succeeded;
|
||||
if (isAdmin)
|
||||
return true;
|
||||
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>();
|
||||
return policies?.AllowHotWalletForAll is true;
|
||||
}
|
||||
|
||||
private async Task<string> ReadAllText(IFormFile file)
|
||||
{
|
||||
using (var stream = new StreamReader(file.OpenReadStream()))
|
||||
@ -328,7 +383,8 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
private IActionResult ShowAddresses(DerivationSchemeViewModel vm, DerivationSchemeSettings strategy)
|
||||
private IActionResult
|
||||
ShowAddresses(DerivationSchemeViewModel vm, DerivationSchemeSettings strategy)
|
||||
{
|
||||
vm.DerivationScheme = strategy.AccountDerivation.ToString();
|
||||
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
|
||||
@ -338,13 +394,18 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
var address = line.Derive((uint)i);
|
||||
vm.AddressSamples.Add((deposit.GetKeyPath((uint)i).ToString(), address.ScriptPubKey.GetDestinationAddress(strategy.Network.NBitcoinNetwork).ToString()));
|
||||
var keyPath = deposit.GetKeyPath((uint)i);
|
||||
var rootedKeyPath = vm.GetAccountKeypath()?.Derive(keyPath);
|
||||
var derivation = line.Derive((uint)i);
|
||||
var address = strategy.Network.NBXplorerNetwork.CreateAddress(strategy.AccountDerivation,
|
||||
line.KeyPathTemplate.GetKeyPath((uint)i),
|
||||
derivation.ScriptPubKey).ToString();
|
||||
vm.AddressSamples.Add((keyPath.ToString(), address, rootedKeyPath));
|
||||
}
|
||||
}
|
||||
vm.Confirmation = true;
|
||||
ModelState.Remove(nameof(vm.Config)); // Remove the cached value
|
||||
return View(vm);
|
||||
return View(nameof(AddDerivationScheme),vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Changelly;
|
||||
@ -23,18 +22,12 @@ using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using Newtonsoft.Json;
|
||||
#if NETCOREAPP21
|
||||
using IWebHostEnvironment = Microsoft.AspNetCore.Hosting.IHostingEnvironment;
|
||||
#endif
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -63,6 +56,8 @@ namespace BTCPayServer.Controllers
|
||||
ChangellyClientProvider changellyClientProvider,
|
||||
IWebHostEnvironment env, IHttpClientFactory httpClientFactory,
|
||||
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
|
||||
SettingsRepository settingsRepository,
|
||||
IAuthorizationService authorizationService,
|
||||
CssThemeManager cssThemeManager)
|
||||
{
|
||||
_RateFactory = rateFactory;
|
||||
@ -76,6 +71,8 @@ namespace BTCPayServer.Controllers
|
||||
_Env = env;
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
|
||||
_settingsRepository = settingsRepository;
|
||||
_authorizationService = authorizationService;
|
||||
_CssThemeManager = cssThemeManager;
|
||||
_NetworkProvider = networkProvider;
|
||||
_ExplorerProvider = explorerProvider;
|
||||
@ -100,6 +97,8 @@ namespace BTCPayServer.Controllers
|
||||
IWebHostEnvironment _Env;
|
||||
private IHttpClientFactory _httpClientFactory;
|
||||
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
|
||||
private readonly SettingsRepository _settingsRepository;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly CssThemeManager _CssThemeManager;
|
||||
|
||||
[TempData]
|
||||
@ -196,14 +195,15 @@ namespace BTCPayServer.Controllers
|
||||
[Route("{storeId}/rates")]
|
||||
public IActionResult Rates()
|
||||
{
|
||||
var exchanges = GetSupportedExchanges();
|
||||
var storeBlob = CurrentStore.GetStoreBlob();
|
||||
var vm = new RatesViewModel();
|
||||
vm.SetExchangeRates(GetSupportedExchanges(), storeBlob.PreferredExchange ?? CoinAverageRateProvider.CoinAverageName);
|
||||
vm.SetExchangeRates(exchanges, storeBlob.PreferredExchange ?? CoinGeckoRateProvider.CoinGeckoName);
|
||||
vm.Spread = (double)(storeBlob.Spread * 100m);
|
||||
vm.StoreId = CurrentStore.Id;
|
||||
vm.Script = storeBlob.GetRateRules(_NetworkProvider).ToString();
|
||||
vm.DefaultScript = storeBlob.GetDefaultRateRules(_NetworkProvider).ToString();
|
||||
vm.AvailableExchanges = GetSupportedExchanges();
|
||||
vm.AvailableExchanges = exchanges;
|
||||
vm.DefaultCurrencyPairs = storeBlob.GetDefaultCurrencyPairString();
|
||||
vm.ShowScripting = storeBlob.RateScripting;
|
||||
return View(vm);
|
||||
@ -213,7 +213,16 @@ namespace BTCPayServer.Controllers
|
||||
[Route("{storeId}/rates")]
|
||||
public async Task<IActionResult> Rates(RatesViewModel model, string command = null, string storeId = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
model.SetExchangeRates(GetSupportedExchanges(), model.PreferredExchange);
|
||||
if (command == "scripting-on")
|
||||
{
|
||||
return RedirectToAction(nameof(ShowRateRules), new {scripting = true,storeId = model.StoreId});
|
||||
}else if (command == "scripting-off")
|
||||
{
|
||||
return RedirectToAction(nameof(ShowRateRules), new {scripting = false, storeId = model.StoreId});
|
||||
}
|
||||
|
||||
var exchanges = GetSupportedExchanges();
|
||||
model.SetExchangeRates(exchanges, model.PreferredExchange);
|
||||
model.StoreId = storeId ?? model.StoreId;
|
||||
CurrencyPair[] currencyPairs = null;
|
||||
try
|
||||
@ -236,14 +245,14 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
var blob = CurrentStore.GetStoreBlob();
|
||||
model.DefaultScript = blob.GetDefaultRateRules(_NetworkProvider).ToString();
|
||||
model.AvailableExchanges = GetSupportedExchanges();
|
||||
model.AvailableExchanges = exchanges;
|
||||
|
||||
blob.PreferredExchange = model.PreferredExchange;
|
||||
blob.Spread = (decimal)model.Spread / 100.0m;
|
||||
blob.DefaultCurrencyPairs = currencyPairs;
|
||||
if (!model.ShowScripting)
|
||||
{
|
||||
if (!GetSupportedExchanges().Select(c => c.Name).Contains(blob.PreferredExchange, StringComparer.OrdinalIgnoreCase))
|
||||
if (!exchanges.Any(provider => provider.Id.Equals(model.PreferredExchange, StringComparison.InvariantCultureIgnoreCase)))
|
||||
{
|
||||
ModelState.AddModelError(nameof(model.PreferredExchange), $"Unsupported exchange ({model.RateSource})");
|
||||
return View(model);
|
||||
@ -329,7 +338,7 @@ namespace BTCPayServer.Controllers
|
||||
Description = scripting ?
|
||||
"This action will modify your current rate sources. Are you sure to turn on rate rules scripting? (Advanced users)"
|
||||
: "This action will delete your rate script. Are you sure to turn off rate rules scripting?",
|
||||
ButtonClass = "btn-primary"
|
||||
ButtonClass = scripting ? "btn-primary" : "btn-danger"
|
||||
});
|
||||
}
|
||||
|
||||
@ -473,12 +482,12 @@ namespace BTCPayServer.Controllers
|
||||
store
|
||||
.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.ToDictionary(c => c.Network.CryptoCode);
|
||||
.ToDictionary(c => c.Network.CryptoCode.ToUpperInvariant());
|
||||
|
||||
var lightningByCryptoCode = store
|
||||
.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.OfType<LightningSupportedPaymentMethod>()
|
||||
.ToDictionary(c => c.CryptoCode);
|
||||
.ToDictionary(c => c.CryptoCode.ToUpperInvariant());
|
||||
|
||||
foreach (var paymentMethodId in _paymentMethodHandlerDictionary.Distinct().SelectMany(handler => handler.GetSupportedPaymentMethods()))
|
||||
{
|
||||
@ -486,9 +495,11 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
case BitcoinPaymentType _:
|
||||
var strategy = derivationByCryptoCode.TryGet(paymentMethodId.CryptoCode);
|
||||
var network = _NetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
vm.DerivationSchemes.Add(new StoreViewModel.DerivationScheme()
|
||||
{
|
||||
Crypto = paymentMethodId.CryptoCode,
|
||||
WalletSupported = network.WalletSupported,
|
||||
Value = strategy?.ToPrettyString() ?? string.Empty,
|
||||
WalletId = new WalletId(store.Id, paymentMethodId.CryptoCode),
|
||||
Enabled = !excludeFilters.Match(paymentMethodId) && strategy != null
|
||||
@ -592,13 +603,13 @@ namespace BTCPayServer.Controllers
|
||||
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
|
||||
}
|
||||
|
||||
private CoinAverageExchange[] GetSupportedExchanges()
|
||||
private IEnumerable<AvailableRateProvider> GetSupportedExchanges()
|
||||
{
|
||||
return _RateFactory.RateProviderFactory.GetSupportedExchanges()
|
||||
.Where(r => !string.IsNullOrWhiteSpace(r.Value.Display))
|
||||
.Select(c => c.Value)
|
||||
.OrderBy(s => s.Name, StringComparer.OrdinalIgnoreCase)
|
||||
.ToArray();
|
||||
var exchanges = _RateFactory.RateProviderFactory.GetSupportedExchanges();
|
||||
return exchanges
|
||||
.Where(r => !string.IsNullOrWhiteSpace(r.Name))
|
||||
.OrderBy(s => s.Id, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
}
|
||||
|
||||
private DerivationSchemeSettings ParseDerivationStrategy(string derivationScheme, Script hint, BTCPayNetwork network)
|
||||
@ -745,7 +756,7 @@ namespace BTCPayServer.Controllers
|
||||
ViewBag.ShowMenu = false;
|
||||
var stores = await _Repo.GetStoresByUserId(userId);
|
||||
model.Stores = new SelectList(stores.Where(s => s.Role == StoreRoles.Owner), nameof(CurrentStore.Id), nameof(CurrentStore.StoreName));
|
||||
if (model.Stores.Count() == 0)
|
||||
if (!model.Stores.Any())
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = "You need to be owner of at least one store before pairing";
|
||||
return RedirectToAction(nameof(UserStoresController.ListStores), "UserStores");
|
||||
@ -822,9 +833,9 @@ namespace BTCPayServer.Controllers
|
||||
if (pairingResult == PairingResult.Complete || pairingResult == PairingResult.Partial)
|
||||
{
|
||||
var excludeFilter = store.GetStoreBlob().GetExcludedPaymentMethods();
|
||||
StoreNotConfigured = store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
StoreNotConfigured = !store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.Where(p => !excludeFilter.Match(p.PaymentId))
|
||||
.Count() == 0;
|
||||
.Any();
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Pairing is successful";
|
||||
if (pairingResult == PairingResult.Partial)
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Server initiated pairing code: " + pairingCode;
|
||||
|
385
BTCPayServer/Controllers/VaultController.cs
Normal file
385
BTCPayServer/Controllers/VaultController.cs
Normal file
@ -0,0 +1,385 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Hwi;
|
||||
using BTCPayServer.ModelBinders;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using LedgerWallet;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
[Route("vault")]
|
||||
public class VaultController : Controller
|
||||
{
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
|
||||
public VaultController(BTCPayNetworkProvider networks, IAuthorizationService authorizationService)
|
||||
{
|
||||
Networks = networks;
|
||||
_authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
public BTCPayNetworkProvider Networks { get; }
|
||||
|
||||
[HttpGet]
|
||||
[Route("{cryptoCode}/xpub")]
|
||||
[Route("wallets/{walletId}/xpub")]
|
||||
public async Task<IActionResult> VaultBridgeConnection(string cryptoCode = null,
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId = null)
|
||||
{
|
||||
if (!HttpContext.WebSockets.IsWebSocketRequest)
|
||||
return NotFound();
|
||||
cryptoCode = cryptoCode ?? walletId.CryptoCode;
|
||||
using (var cts = new CancellationTokenSource(TimeSpan.FromMinutes(10)))
|
||||
{
|
||||
var cancellationToken = cts.Token;
|
||||
var network = Networks.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
if (network == null)
|
||||
return NotFound();
|
||||
var websocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
||||
var hwi = new Hwi.HwiClient(network.NBitcoinNetwork)
|
||||
{
|
||||
Transport = new HwiWebSocketTransport(websocket)
|
||||
};
|
||||
Hwi.HwiDeviceClient device = null;
|
||||
HwiEnumerateEntry deviceEntry = null;
|
||||
HDFingerprint? fingerprint = null;
|
||||
string password = null;
|
||||
var websocketHelper = new WebSocketHelper(websocket);
|
||||
|
||||
async Task<bool> RequireDeviceUnlocking()
|
||||
{
|
||||
if (deviceEntry == null)
|
||||
{
|
||||
await websocketHelper.Send("{ \"error\": \"need-device\"}", cancellationToken);
|
||||
return true;
|
||||
}
|
||||
if (deviceEntry.Code is HwiErrorCode.DeviceNotInitialized)
|
||||
{
|
||||
await websocketHelper.Send("{ \"error\": \"need-initialized\"}", cancellationToken);
|
||||
return true;
|
||||
}
|
||||
if (deviceEntry.Code is HwiErrorCode.DeviceNotReady)
|
||||
{
|
||||
if (IsTrezorT(deviceEntry))
|
||||
{
|
||||
await websocketHelper.Send("{ \"error\": \"need-passphrase-on-device\"}", cancellationToken);
|
||||
return true;
|
||||
}
|
||||
else if (deviceEntry.NeedsPinSent is true)
|
||||
{
|
||||
await websocketHelper.Send("{ \"error\": \"need-pin\"}", cancellationToken);
|
||||
return true;
|
||||
}
|
||||
else if (deviceEntry.NeedsPassphraseSent is true && password is null)
|
||||
{
|
||||
await websocketHelper.Send("{ \"error\": \"need-passphrase\"}", cancellationToken);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
JObject o = null;
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
var command = await websocketHelper.NextMessageAsync(cancellationToken);
|
||||
switch (command)
|
||||
{
|
||||
case "set-passphrase":
|
||||
device.Password = await websocketHelper.NextMessageAsync(cancellationToken);
|
||||
password = device.Password;
|
||||
break;
|
||||
case "ask-sign":
|
||||
if (await RequireDeviceUnlocking())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (walletId == null)
|
||||
{
|
||||
await websocketHelper.Send("{ \"error\": \"invalid-walletId\"}", cancellationToken);
|
||||
continue;
|
||||
}
|
||||
if (fingerprint is null)
|
||||
{
|
||||
fingerprint = (await device.GetXPubAsync(new KeyPath("44'"), cancellationToken)).ExtPubKey.ParentFingerprint;
|
||||
}
|
||||
await websocketHelper.Send("{ \"info\": \"ready\"}", cancellationToken);
|
||||
o = JObject.Parse(await websocketHelper.NextMessageAsync(cancellationToken));
|
||||
var authorization = await _authorizationService.AuthorizeAsync(User, Policies.CanModifyStoreSettings.Key);
|
||||
if (!authorization.Succeeded)
|
||||
{
|
||||
await websocketHelper.Send("{ \"error\": \"not-authorized\"}", cancellationToken);
|
||||
continue;
|
||||
}
|
||||
var psbt = PSBT.Parse(o["psbt"].Value<string>(), network.NBitcoinNetwork);
|
||||
var derivationSettings = GetDerivationSchemeSettings(walletId);
|
||||
derivationSettings.RebaseKeyPaths(psbt);
|
||||
var signing = derivationSettings.GetSigningAccountKeySettings();
|
||||
if (signing.GetRootedKeyPath()?.MasterFingerprint != fingerprint)
|
||||
{
|
||||
await websocketHelper.Send("{ \"error\": \"wrong-wallet\"}", cancellationToken);
|
||||
continue;
|
||||
}
|
||||
var signableInputs = psbt.Inputs
|
||||
.SelectMany(i => i.HDKeyPaths)
|
||||
.Where(i => i.Value.MasterFingerprint == fingerprint)
|
||||
.ToArray();
|
||||
if (signableInputs.Length > 0)
|
||||
{
|
||||
var actualPubKey = (await device.GetXPubAsync(signableInputs[0].Value.KeyPath)).GetPublicKey();
|
||||
if (actualPubKey != signableInputs[0].Key)
|
||||
{
|
||||
await websocketHelper.Send("{ \"error\": \"wrong-keypath\"}", cancellationToken);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
try
|
||||
{
|
||||
psbt = await device.SignPSBTAsync(psbt, cancellationToken);
|
||||
}
|
||||
catch (Hwi.HwiException)
|
||||
{
|
||||
await websocketHelper.Send("{ \"error\": \"user-reject\"}", cancellationToken);
|
||||
continue;
|
||||
}
|
||||
o = new JObject();
|
||||
o.Add("psbt", psbt.ToBase64());
|
||||
await websocketHelper.Send(o.ToString(), cancellationToken);
|
||||
break;
|
||||
case "display-address":
|
||||
if (await RequireDeviceUnlocking())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var k = RootedKeyPath.Parse(await websocketHelper.NextMessageAsync(cancellationToken));
|
||||
await device.DisplayAddressAsync(GetScriptPubKeyType(k), k.KeyPath, cancellationToken);
|
||||
await websocketHelper.Send("{ \"info\": \"ok\"}", cancellationToken);
|
||||
break;
|
||||
case "ask-pin":
|
||||
if (device == null)
|
||||
{
|
||||
await websocketHelper.Send("{ \"error\": \"need-device\"}", cancellationToken);
|
||||
continue;
|
||||
}
|
||||
try
|
||||
{
|
||||
await device.PromptPinAsync(cancellationToken);
|
||||
}
|
||||
catch (HwiException ex) when (ex.ErrorCode == HwiErrorCode.DeviceAlreadyUnlocked)
|
||||
{
|
||||
await websocketHelper.Send("{ \"error\": \"device-already-unlocked\"}", cancellationToken);
|
||||
continue;
|
||||
}
|
||||
await websocketHelper.Send("{ \"info\": \"prompted, please input the pin\"}", cancellationToken);
|
||||
var pin = int.Parse(await websocketHelper.NextMessageAsync(cancellationToken), CultureInfo.InvariantCulture);
|
||||
if (await device.SendPinAsync(pin, cancellationToken))
|
||||
{
|
||||
goto askdevice;
|
||||
}
|
||||
else
|
||||
{
|
||||
await websocketHelper.Send("{ \"error\": \"incorrect-pin\"}", cancellationToken);
|
||||
continue;
|
||||
}
|
||||
case "ask-xpub":
|
||||
if (await RequireDeviceUnlocking())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
await websocketHelper.Send("{ \"info\": \"ok\"}", cancellationToken);
|
||||
var askedXpub = JObject.Parse(await websocketHelper.NextMessageAsync(cancellationToken));
|
||||
var addressType = askedXpub["addressType"].Value<string>();
|
||||
var accountNumber = askedXpub["accountNumber"].Value<int>();
|
||||
JObject result = new JObject();
|
||||
var factory = network.NBXplorerNetwork.DerivationStrategyFactory;
|
||||
if (fingerprint is null)
|
||||
{
|
||||
fingerprint = (await device.GetXPubAsync(new KeyPath("44'"), cancellationToken)).ExtPubKey.ParentFingerprint;
|
||||
}
|
||||
result["fingerprint"] = fingerprint.Value.ToString();
|
||||
|
||||
DerivationStrategyBase strategy = null;
|
||||
KeyPath keyPath = null;
|
||||
BitcoinExtPubKey xpub = null;
|
||||
|
||||
if (!network.NBitcoinNetwork.Consensus.SupportSegwit && addressType != "legacy")
|
||||
{
|
||||
await websocketHelper.Send("{ \"error\": \"segwit-notsupported\"}", cancellationToken);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (addressType == "segwit")
|
||||
{
|
||||
keyPath = new KeyPath("84'").Derive(network.CoinType).Derive(accountNumber, true);
|
||||
xpub = await device.GetXPubAsync(keyPath);
|
||||
strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions()
|
||||
{
|
||||
ScriptPubKeyType = ScriptPubKeyType.Segwit
|
||||
});
|
||||
}
|
||||
else if (addressType == "segwitWrapped")
|
||||
{
|
||||
keyPath = new KeyPath("49'").Derive(network.CoinType).Derive(accountNumber, true);
|
||||
xpub = await device.GetXPubAsync(keyPath);
|
||||
strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions()
|
||||
{
|
||||
ScriptPubKeyType = ScriptPubKeyType.SegwitP2SH
|
||||
});
|
||||
}
|
||||
else if (addressType == "legacy")
|
||||
{
|
||||
keyPath = new KeyPath("44'").Derive(network.CoinType).Derive(accountNumber, true);
|
||||
xpub = await device.GetXPubAsync(keyPath);
|
||||
strategy = factory.CreateDirectDerivationStrategy(xpub, new DerivationStrategyOptions()
|
||||
{
|
||||
ScriptPubKeyType = ScriptPubKeyType.Legacy
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
await websocketHelper.Send("{ \"error\": \"invalid-addresstype\"}", cancellationToken);
|
||||
continue;
|
||||
}
|
||||
result.Add(new JProperty("strategy", strategy.ToString()));
|
||||
result.Add(new JProperty("accountKey", xpub.ToString()));
|
||||
result.Add(new JProperty("keyPath", keyPath.ToString()));
|
||||
await websocketHelper.Send(result.ToString(), cancellationToken);
|
||||
break;
|
||||
case "ask-passphrase":
|
||||
if (command == "ask-passphrase")
|
||||
{
|
||||
if (deviceEntry == null)
|
||||
{
|
||||
await websocketHelper.Send("{ \"error\": \"need-device\"}", cancellationToken);
|
||||
continue;
|
||||
}
|
||||
// The make the trezor T ask for password
|
||||
await device.GetXPubAsync(new KeyPath("44'"), cancellationToken);
|
||||
}
|
||||
goto askdevice;
|
||||
case "ask-device":
|
||||
askdevice:
|
||||
password = null;
|
||||
deviceEntry = null;
|
||||
device = null;
|
||||
var entries = (await hwi.EnumerateEntriesAsync(cancellationToken)).ToList();
|
||||
deviceEntry = entries.FirstOrDefault();
|
||||
if (deviceEntry == null)
|
||||
{
|
||||
await websocketHelper.Send("{ \"error\": \"no-device\"}", cancellationToken);
|
||||
continue;
|
||||
}
|
||||
device = new HwiDeviceClient(hwi, deviceEntry.DeviceSelector, deviceEntry.Model, deviceEntry.Fingerprint);
|
||||
fingerprint = device.Fingerprint;
|
||||
JObject json = new JObject();
|
||||
json.Add("model", device.Model.ToString());
|
||||
json.Add("fingerprint", device.Fingerprint?.ToString());
|
||||
await websocketHelper.Send(json.ToString(), cancellationToken);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (FormatException ex)
|
||||
{
|
||||
JObject obj = new JObject();
|
||||
obj.Add("error", "invalid-network");
|
||||
obj.Add("details", ex.ToString());
|
||||
try
|
||||
{
|
||||
await websocketHelper.Send(obj.ToString(), cancellationToken);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
JObject obj = new JObject();
|
||||
obj.Add("error", "unknown-error");
|
||||
obj.Add("message", ex.Message);
|
||||
obj.Add("details", ex.ToString());
|
||||
try
|
||||
{
|
||||
await websocketHelper.Send(obj.ToString(), cancellationToken);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
finally
|
||||
{
|
||||
await websocketHelper.DisposeAsync(cancellationToken);
|
||||
}
|
||||
}
|
||||
return new EmptyResult();
|
||||
}
|
||||
|
||||
private ScriptPubKeyType GetScriptPubKeyType(RootedKeyPath keyPath)
|
||||
{
|
||||
var path = keyPath.KeyPath.ToString();
|
||||
if (path.StartsWith("84'", StringComparison.OrdinalIgnoreCase))
|
||||
return ScriptPubKeyType.Segwit;
|
||||
if (path.StartsWith("49'", StringComparison.OrdinalIgnoreCase))
|
||||
return ScriptPubKeyType.SegwitP2SH;
|
||||
if (path.StartsWith("44'", StringComparison.OrdinalIgnoreCase))
|
||||
return ScriptPubKeyType.Legacy;
|
||||
throw new NotSupportedException("Unsupported keypath");
|
||||
}
|
||||
|
||||
private bool SameSelector(DeviceSelector a, DeviceSelector b)
|
||||
{
|
||||
var aargs = new List<string>();
|
||||
a.AddArgs(aargs);
|
||||
var bargs = new List<string>();
|
||||
b.AddArgs(bargs);
|
||||
if (aargs.Count != bargs.Count)
|
||||
return false;
|
||||
for (int i = 0; i < aargs.Count; i++)
|
||||
{
|
||||
if (aargs[i] != bargs[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool IsTrezorT(HwiEnumerateEntry deviceEntry)
|
||||
{
|
||||
return (deviceEntry.Model == HardwareWalletModels.Trezor_T || deviceEntry.Model == HardwareWalletModels.Trezor_T_Simulator);
|
||||
}
|
||||
|
||||
public StoreData CurrentStore
|
||||
{
|
||||
get
|
||||
{
|
||||
return HttpContext.GetStoreData();
|
||||
}
|
||||
}
|
||||
|
||||
private DerivationSchemeSettings GetDerivationSchemeSettings(WalletId walletId)
|
||||
{
|
||||
var paymentMethod = CurrentStore
|
||||
.GetSupportedPaymentMethods(Networks)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.FirstOrDefault(p => p.PaymentId.PaymentType == Payments.PaymentTypes.BTCLike && p.PaymentId.CryptoCode == walletId.CryptoCode);
|
||||
return paymentMethod;
|
||||
}
|
||||
}
|
||||
}
|
@ -55,12 +55,13 @@ namespace BTCPayServer.Controllers
|
||||
WalletId walletId, WalletPSBTViewModel vm)
|
||||
{
|
||||
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
|
||||
vm.CryptoCode = network.CryptoCode;
|
||||
if (await vm.GetPSBT(network.NBitcoinNetwork) is PSBT psbt)
|
||||
{
|
||||
vm.Decoded = psbt.ToString();
|
||||
vm.PSBT = psbt.ToBase64();
|
||||
}
|
||||
return View(vm ?? new WalletPSBTViewModel());
|
||||
return View(vm ?? new WalletPSBTViewModel() { CryptoCode = walletId.CryptoCode });
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("{walletId}/psbt")]
|
||||
@ -72,6 +73,7 @@ namespace BTCPayServer.Controllers
|
||||
if (command == null)
|
||||
return await WalletPSBT(walletId, vm);
|
||||
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
|
||||
vm.CryptoCode = network.CryptoCode;
|
||||
var psbt = await vm.GetPSBT(network.NBitcoinNetwork);
|
||||
if (psbt == null)
|
||||
{
|
||||
@ -88,6 +90,8 @@ namespace BTCPayServer.Controllers
|
||||
vm.PSBT = psbt.ToBase64();
|
||||
vm.FileName = vm.UploadedPSBTFile?.FileName;
|
||||
return View(vm);
|
||||
case "vault":
|
||||
return ViewVault(walletId, psbt);
|
||||
case "ledger":
|
||||
return ViewWalletSendLedger(psbt);
|
||||
case "update":
|
||||
@ -156,7 +160,8 @@ namespace BTCPayServer.Controllers
|
||||
private async Task FetchTransactionDetails(DerivationSchemeSettings derivationSchemeSettings, WalletPSBTReadyViewModel vm, BTCPayNetwork network)
|
||||
{
|
||||
var psbtObject = PSBT.Parse(vm.PSBT, network.NBitcoinNetwork);
|
||||
psbtObject = await UpdatePSBT(derivationSchemeSettings, psbtObject, network) ?? psbtObject;
|
||||
if (!psbtObject.IsAllFinalized())
|
||||
psbtObject = await UpdatePSBT(derivationSchemeSettings, psbtObject, network) ?? psbtObject;
|
||||
IHDKey signingKey = null;
|
||||
RootedKeyPath signingKeyPath = null;
|
||||
try
|
||||
|
@ -202,7 +202,7 @@ namespace BTCPayServer.Controllers
|
||||
.Select(d => ((Wallet: _walletProvider.GetWallet(d.Network),
|
||||
DerivationStrategy: d.AccountDerivation,
|
||||
Network: d.Network)))
|
||||
.Where(_ => _.Wallet != null)
|
||||
.Where(_ => _.Wallet != null && _.Network.WalletSupported)
|
||||
.Select(_ => (Wallet: _.Wallet,
|
||||
Store: s,
|
||||
Balance: GetBalanceString(_.Wallet, _.DerivationStrategy),
|
||||
@ -246,28 +246,46 @@ namespace BTCPayServer.Controllers
|
||||
var walletBlob = await walletBlobAsync;
|
||||
var walletTransactionsInfo = await walletTransactionsInfoAsync;
|
||||
var model = new ListTransactionsViewModel();
|
||||
foreach (var tx in transactions.UnconfirmedTransactions.Transactions.Concat(transactions.ConfirmedTransactions.Transactions).ToArray())
|
||||
if (transactions == null)
|
||||
{
|
||||
var vm = new ListTransactionsViewModel.TransactionViewModel();
|
||||
vm.Id = tx.TransactionId.ToString();
|
||||
vm.Link = string.Format(CultureInfo.InvariantCulture, paymentMethod.Network.BlockExplorerLink, vm.Id);
|
||||
vm.Timestamp = tx.Timestamp;
|
||||
vm.Positive = tx.BalanceChange >= Money.Zero;
|
||||
vm.Balance = tx.BalanceChange.ToString();
|
||||
vm.IsConfirmed = tx.Confirmations != 0;
|
||||
|
||||
if (walletTransactionsInfo.TryGetValue(tx.TransactionId.ToString(), out var transactionInfo))
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
var labels = walletBlob.GetLabels(transactionInfo);
|
||||
vm.Labels.AddRange(labels);
|
||||
model.Labels.AddRange(labels);
|
||||
vm.Comment = transactionInfo.Comment;
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message =
|
||||
"There was an error retrieving the transactions list. Is NBXplorer configured correctly?"
|
||||
});
|
||||
model.Transactions = new List<ListTransactionsViewModel.TransactionViewModel>();
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var tx in transactions.UnconfirmedTransactions.Transactions
|
||||
.Concat(transactions.ConfirmedTransactions.Transactions).ToArray())
|
||||
{
|
||||
var vm = new ListTransactionsViewModel.TransactionViewModel();
|
||||
vm.Id = tx.TransactionId.ToString();
|
||||
vm.Link = string.Format(CultureInfo.InvariantCulture, paymentMethod.Network.BlockExplorerLink,
|
||||
vm.Id);
|
||||
vm.Timestamp = tx.Timestamp;
|
||||
vm.Positive = tx.BalanceChange.GetValue(wallet.Network) >= 0;
|
||||
vm.Balance = tx.BalanceChange.ToString();
|
||||
vm.IsConfirmed = tx.Confirmations != 0;
|
||||
|
||||
if (walletTransactionsInfo.TryGetValue(tx.TransactionId.ToString(), out var transactionInfo))
|
||||
{
|
||||
var labels = walletBlob.GetLabels(transactionInfo);
|
||||
vm.Labels.AddRange(labels);
|
||||
model.Labels.AddRange(labels);
|
||||
vm.Comment = transactionInfo.Comment;
|
||||
}
|
||||
|
||||
if (labelFilter == null ||
|
||||
vm.Labels.Any(l => l.Value.Equals(labelFilter, StringComparison.OrdinalIgnoreCase)))
|
||||
model.Transactions.Add(vm);
|
||||
}
|
||||
|
||||
if (labelFilter == null || vm.Labels.Any(l => l.Value.Equals(labelFilter, StringComparison.OrdinalIgnoreCase)))
|
||||
model.Transactions.Add(vm);
|
||||
model.Transactions = model.Transactions.OrderByDescending(t => t.Timestamp).ToList();
|
||||
}
|
||||
model.Transactions = model.Transactions.OrderByDescending(t => t.Timestamp).ToList();
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
||||
@ -289,7 +307,7 @@ namespace BTCPayServer.Controllers
|
||||
if (paymentMethod == null)
|
||||
return NotFound();
|
||||
var network = this.NetworkProvider.GetNetwork<BTCPayNetwork>(walletId?.CryptoCode);
|
||||
if (network == null)
|
||||
if (network == null || network.ReadonlyWallet)
|
||||
return NotFound();
|
||||
var storeData = store.GetStoreBlob();
|
||||
var rateRules = store.GetStoreBlob().GetRateRules(NetworkProvider);
|
||||
@ -313,7 +331,7 @@ namespace BTCPayServer.Controllers
|
||||
var feeProvider = _feeRateProvider.CreateFeeProvider(network);
|
||||
var recommendedFees = feeProvider.GetFeeRateAsync();
|
||||
var balance = _walletProvider.GetWallet(network).GetBalance(paymentMethod.AccountDerivation);
|
||||
model.CurrentBalance = (await balance).ToDecimal(MoneyUnit.BTC);
|
||||
model.CurrentBalance = await balance;
|
||||
model.RecommendedSatoshiPerByte = (int)(await recommendedFees).GetFee(1).Satoshi;
|
||||
model.FeeSatoshiPerByte = model.RecommendedSatoshiPerByte;
|
||||
model.SupportRBF = network.SupportRBF;
|
||||
@ -351,7 +369,7 @@ namespace BTCPayServer.Controllers
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
var network = this.NetworkProvider.GetNetwork<BTCPayNetwork>(walletId?.CryptoCode);
|
||||
if (network == null)
|
||||
if (network == null || network.ReadonlyWallet)
|
||||
return NotFound();
|
||||
vm.SupportRBF = network.SupportRBF;
|
||||
decimal transactionAmountSum = 0;
|
||||
@ -387,9 +405,20 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
subtractFeesOutputsCount.Add(i);
|
||||
}
|
||||
var destination = ParseDestination(transactionOutput.DestinationAddress, network.NBitcoinNetwork);
|
||||
if (destination == null)
|
||||
ModelState.AddModelError(nameof(transactionOutput.DestinationAddress), "Invalid address");
|
||||
transactionOutput.DestinationAddress = transactionOutput.DestinationAddress?.Trim() ?? string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
BitcoinAddress.Create(transactionOutput.DestinationAddress, network.NBitcoinNetwork);
|
||||
}
|
||||
catch
|
||||
{
|
||||
var inputName =
|
||||
string.Format(CultureInfo.InvariantCulture, "Outputs[{0}].", i.ToString(CultureInfo.InvariantCulture)) +
|
||||
nameof(transactionOutput.DestinationAddress);
|
||||
|
||||
ModelState.AddModelError(inputName, "Invalid address");
|
||||
}
|
||||
|
||||
if (transactionOutput.Amount.HasValue)
|
||||
{
|
||||
@ -425,7 +454,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
if (!ModelState.IsValid)
|
||||
return View(vm);
|
||||
|
||||
DerivationSchemeSettings derivationScheme = GetDerivationSchemeSettings(walletId);
|
||||
@ -449,6 +478,8 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case "vault":
|
||||
return ViewVault(walletId, psbt.PSBT);
|
||||
case "ledger":
|
||||
return ViewWalletSendLedger(psbt.PSBT, psbt.ChangeAddress);
|
||||
case "seed":
|
||||
@ -463,6 +494,16 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
}
|
||||
|
||||
private IActionResult ViewVault(WalletId walletId, PSBT psbt)
|
||||
{
|
||||
return View("WalletSendVault", new WalletSendVaultModel()
|
||||
{
|
||||
WalletId = walletId.ToString(),
|
||||
PSBT = psbt.ToBase64(),
|
||||
WebsocketPath = this.Url.Action(nameof(VaultController.VaultBridgeConnection), "Vault", new { walletId = walletId.ToString() })
|
||||
});
|
||||
}
|
||||
|
||||
private IActionResult RedirectToWalletPSBT(WalletId walletId, PSBT psbt, string fileName = null)
|
||||
{
|
||||
var vm = new PostRedirectViewModel()
|
||||
@ -561,46 +602,46 @@ namespace BTCPayServer.Controllers
|
||||
signingKeySettings.RootFingerprint = extKey.GetPublicKey().GetHDFingerPrint();
|
||||
|
||||
RootedKeyPath rootedKeyPath = signingKeySettings.GetRootedKeyPath();
|
||||
if (rootedKeyPath == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.SeedOrKey), "The master fingerprint and/or account key path of your seed are not set in the wallet settings.");
|
||||
return View(viewModel);
|
||||
}
|
||||
// The user gave the root key, let's try to rebase the PSBT, and derive the account private key
|
||||
if (rootedKeyPath?.MasterFingerprint == extKey.GetPublicKey().GetHDFingerPrint())
|
||||
if (rootedKeyPath.MasterFingerprint == extKey.GetPublicKey().GetHDFingerPrint())
|
||||
{
|
||||
psbt.RebaseKeyPaths(signingKeySettings.AccountKey, rootedKeyPath);
|
||||
signingKey = extKey.Derive(rootedKeyPath.KeyPath);
|
||||
}
|
||||
// The user maybe gave the account key, let's try to sign with it
|
||||
else
|
||||
{
|
||||
signingKey = extKey;
|
||||
}
|
||||
var balanceChange = psbt.GetBalance(settings.AccountDerivation, signingKey, rootedKeyPath);
|
||||
if (balanceChange == Money.Zero)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.SeedOrKey), "This seed is unable to sign this transaction. Either the seed is incorrect, or the account path has not been properly configured in the Wallet Settings.");
|
||||
ModelState.AddModelError(nameof(viewModel.SeedOrKey), "The master fingerprint does not match the one set in your wallet settings. Probable cause are: wrong seed, wrong passphrase or wrong fingerprint in your wallet settings.");
|
||||
return View(viewModel);
|
||||
}
|
||||
|
||||
var changed = PSBTChanged(psbt, () => psbt.SignAll(settings.AccountDerivation, signingKey, rootedKeyPath));
|
||||
if (!changed)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.SeedOrKey), "Impossible to sign the transaction. Probable cause: Incorrect account key path in wallet settings, PSBT already signed.");
|
||||
return View(viewModel);
|
||||
}
|
||||
psbt.SignAll(settings.AccountDerivation, signingKey, rootedKeyPath);
|
||||
ModelState.Remove(nameof(viewModel.PSBT));
|
||||
return await WalletPSBTReady(walletId, psbt.ToBase64(), signingKey.GetWif(network.NBitcoinNetwork).ToString(), rootedKeyPath?.ToString());
|
||||
}
|
||||
|
||||
private bool PSBTChanged(PSBT psbt, Action act)
|
||||
{
|
||||
var before = psbt.ToBase64();
|
||||
act();
|
||||
var after = psbt.ToBase64();
|
||||
return before != after;
|
||||
}
|
||||
|
||||
private string ValueToString(Money v, BTCPayNetworkBase network)
|
||||
{
|
||||
return v.ToString() + " " + network.CryptoCode;
|
||||
}
|
||||
|
||||
private IDestination[] ParseDestination(string destination, Network network)
|
||||
{
|
||||
try
|
||||
{
|
||||
destination = destination?.Trim();
|
||||
return new IDestination[] { BitcoinAddress.Create(destination, network) };
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private IActionResult RedirectToWalletTransaction(WalletId walletId, Transaction transaction)
|
||||
{
|
||||
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
|
||||
@ -716,7 +757,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
try
|
||||
{
|
||||
return (await wallet.GetBalance(derivationStrategy, cts.Token)).ToString();
|
||||
return (await wallet.GetBalance(derivationStrategy, cts.Token)).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@ -849,7 +890,7 @@ namespace BTCPayServer.Controllers
|
||||
WalletId walletId)
|
||||
{
|
||||
var derivationSchemeSettings = GetDerivationSchemeSettings(walletId);
|
||||
if (derivationSchemeSettings == null)
|
||||
if (derivationSchemeSettings == null || derivationSchemeSettings.Network.ReadonlyWallet)
|
||||
return NotFound();
|
||||
var store = (await Repository.FindStore(walletId.StoreId, GetUserId()));
|
||||
var vm = new WalletSettingsViewModel()
|
||||
@ -878,7 +919,7 @@ namespace BTCPayServer.Controllers
|
||||
if (!ModelState.IsValid)
|
||||
return View(vm);
|
||||
var derivationScheme = GetDerivationSchemeSettings(walletId);
|
||||
if (derivationScheme == null)
|
||||
if (derivationScheme == null || derivationScheme.Network.ReadonlyWallet)
|
||||
return NotFound();
|
||||
|
||||
if (command == "save")
|
||||
@ -900,7 +941,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
else if (command == "prune")
|
||||
{
|
||||
var result = await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode).PruneAsync(derivationScheme.AccountDerivation, cancellationToken);
|
||||
var result = await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode).PruneAsync(derivationScheme.AccountDerivation, new PruneRequest(), cancellationToken);
|
||||
if (result.TotalPruned == 0)
|
||||
{
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"The wallet is already pruned";
|
||||
|
@ -1,9 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NBitcoin;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using static BTCPayServer.Data.PaymentRequestData;
|
||||
|
||||
|
@ -10,6 +10,7 @@ using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services.Mails;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text;
|
||||
using BTCPayServer.Services.Rates;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
{
|
||||
@ -156,7 +157,7 @@ namespace BTCPayServer.Data
|
||||
}
|
||||
}
|
||||
|
||||
var preferredExchange = string.IsNullOrEmpty(PreferredExchange) ? "coinaverage" : PreferredExchange;
|
||||
var preferredExchange = string.IsNullOrEmpty(PreferredExchange) ? CoinGeckoRateProvider.CoinGeckoName : PreferredExchange;
|
||||
builder.AppendLine($"X_X = {preferredExchange}(X_X);");
|
||||
|
||||
BTCPayServer.Rating.RateRules.TryParse(builder.ToString(), out var rules);
|
||||
|
@ -9,6 +9,7 @@ using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Data
|
||||
@ -50,7 +51,7 @@ namespace BTCPayServer.Data
|
||||
{
|
||||
var result = storeData.StoreBlob == null ? new StoreBlob() : new Serializer(null).ToObject<StoreBlob>(Encoding.UTF8.GetString(storeData.StoreBlob));
|
||||
if (result.PreferredExchange == null)
|
||||
result.PreferredExchange = CoinAverageRateProvider.CoinAverageName;
|
||||
result.PreferredExchange = CoinGeckoRateProvider.CoinGeckoName;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -52,7 +52,7 @@ namespace BTCPayServer
|
||||
if (type == DerivationType.Legacy)
|
||||
return new DirectDerivationStrategy(extPubKey) { Segwit = false };
|
||||
if (type == DerivationType.SegwitP2SH)
|
||||
return new DerivationStrategyFactory(Network).Parse(extPubKey.ToString() + "-[p2sh]");
|
||||
return BtcPayNetwork.NBXplorerNetwork.DerivationStrategyFactory.Parse(extPubKey.ToString() + "-[p2sh]");
|
||||
throw new FormatException();
|
||||
}
|
||||
|
||||
@ -83,7 +83,7 @@ namespace BTCPayServer
|
||||
|
||||
try
|
||||
{
|
||||
var result = new DerivationStrategyFactory(Network).Parse(str);
|
||||
var result = BtcPayNetwork.NBXplorerNetwork.DerivationStrategyFactory.Parse(str);
|
||||
return FindMatch(hintedLabels, result);
|
||||
}
|
||||
catch
|
||||
@ -150,12 +150,11 @@ namespace BTCPayServer
|
||||
str = $"{str}-[{label}]";
|
||||
}
|
||||
|
||||
return FindMatch(hintedLabels, new DerivationStrategyFactory(Network).Parse(str));
|
||||
return FindMatch(hintedLabels, BtcPayNetwork.NBXplorerNetwork.DerivationStrategyFactory.Parse(str));
|
||||
}
|
||||
|
||||
private DerivationStrategyBase FindMatch(HashSet<string> hintLabels, DerivationStrategyBase result)
|
||||
{
|
||||
var facto = new DerivationStrategyFactory(Network);
|
||||
var firstKeyPath = new KeyPath("0/0");
|
||||
if (HintScriptPubKey == null)
|
||||
return result;
|
||||
@ -169,7 +168,7 @@ namespace BTCPayServer
|
||||
resultNoLabels = string.Join('-', resultNoLabels.Split('-').Where(p => !IsLabel(p)));
|
||||
foreach (var labels in ItemCombinations(hintLabels.ToList()))
|
||||
{
|
||||
var hinted = facto.Parse(resultNoLabels + '-' + string.Join('-', labels.Select(l => $"[{l}]").ToArray()));
|
||||
var hinted = BtcPayNetwork.NBXplorerNetwork.DerivationStrategyFactory.Parse(resultNoLabels + '-' + string.Join('-', labels.Select(l => $"[{l}]").ToArray()));
|
||||
if (HintScriptPubKey == hinted.GetDerivation(firstKeyPath).ScriptPubKey)
|
||||
return hinted;
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ namespace BTCPayServer
|
||||
throw new ArgumentNullException(nameof(network));
|
||||
if (derivationStrategy == null)
|
||||
throw new ArgumentNullException(nameof(derivationStrategy));
|
||||
var result = new NBXplorer.DerivationStrategy.DerivationStrategyFactory(network.NBitcoinNetwork).Parse(derivationStrategy);
|
||||
var result = network.NBXplorerNetwork.DerivationStrategyFactory.Parse(derivationStrategy);
|
||||
return new DerivationSchemeSettings(result, network) { AccountOriginal = derivationStrategy.Trim() };
|
||||
}
|
||||
|
||||
|
@ -63,8 +63,8 @@ namespace BTCPayServer
|
||||
public async Task<T> WaitNext<T>(Func<T, bool> predicate, CancellationToken cancellation = default(CancellationToken))
|
||||
{
|
||||
TaskCompletionSource<T> tcs = new TaskCompletionSource<T>();
|
||||
var subscription = Subscribe<T>((a, b) => { if (predicate(b)) { tcs.TrySetResult(b); a.Unsubscribe(); } });
|
||||
using (cancellation.Register(() => { tcs.TrySetCanceled(); subscription.Unsubscribe(); }))
|
||||
using var subscription = Subscribe<T>((a, b) => { if (predicate(b)) { tcs.TrySetResult(b); a.Unsubscribe(); } });
|
||||
using (cancellation.Register(() => { tcs.TrySetCanceled(); }))
|
||||
{
|
||||
return await tcs.Task.ConfigureAwait(false);
|
||||
}
|
||||
|
@ -33,23 +33,24 @@ namespace BTCPayServer
|
||||
Logs.Configuration.LogInformation($"{setting.CryptoCode}: Cookie file is {(setting.CookieFile ?? "not set")}");
|
||||
if (setting.ExplorerUri != null)
|
||||
{
|
||||
_Clients.TryAdd(setting.CryptoCode, CreateExplorerClient(httpClientFactory.CreateClient(nameof(ExplorerClientProvider)), _NetworkProviders.GetNetwork<BTCPayNetwork>(setting.CryptoCode), setting.ExplorerUri, setting.CookieFile));
|
||||
_Clients.TryAdd(setting.CryptoCode.ToUpperInvariant(), CreateExplorerClient(httpClientFactory.CreateClient(nameof(ExplorerClientProvider)), _NetworkProviders.GetNetwork<BTCPayNetwork>(setting.CryptoCode), setting.ExplorerUri, setting.CookieFile));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static ExplorerClient CreateExplorerClient(HttpClient httpClient, BTCPayNetwork n, Uri uri, string cookieFile)
|
||||
{
|
||||
var explorer = new ExplorerClient(n.NBXplorerNetwork, uri);
|
||||
|
||||
var explorer = n.NBXplorerNetwork.CreateExplorerClient(uri);
|
||||
explorer.SetClient(httpClient);
|
||||
if (cookieFile == null)
|
||||
{
|
||||
Logs.Configuration.LogWarning($"{n.CryptoCode}: Not using cookie authentication");
|
||||
Logs.Configuration.LogWarning($"{explorer.CryptoCode}: Not using cookie authentication");
|
||||
explorer.SetNoAuth();
|
||||
}
|
||||
if(!explorer.SetCookieAuth(cookieFile))
|
||||
{
|
||||
Logs.Configuration.LogWarning($"{n.CryptoCode}: Using cookie auth against NBXplorer, but {cookieFile} is not found");
|
||||
Logs.Configuration.LogWarning($"{explorer.CryptoCode}: Using cookie auth against NBXplorer, but {cookieFile} is not found");
|
||||
}
|
||||
return explorer;
|
||||
}
|
||||
@ -61,7 +62,7 @@ namespace BTCPayServer
|
||||
var network = _NetworkProviders.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
if (network == null)
|
||||
return null;
|
||||
_Clients.TryGetValue(network.CryptoCode, out ExplorerClient client);
|
||||
_Clients.TryGetValue(network.NBXplorerNetwork.CryptoCode, out ExplorerClient client);
|
||||
return client;
|
||||
}
|
||||
|
||||
@ -79,6 +80,7 @@ namespace BTCPayServer
|
||||
|
||||
public bool IsAvailable(string cryptoCode)
|
||||
{
|
||||
cryptoCode = cryptoCode.ToUpperInvariant();
|
||||
return _Clients.ContainsKey(cryptoCode) && _Dashboard.IsFullySynched(cryptoCode, out var unused);
|
||||
}
|
||||
|
||||
@ -87,7 +89,7 @@ namespace BTCPayServer
|
||||
var network = _NetworkProviders.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
if (network == null)
|
||||
return null;
|
||||
if (_Clients.ContainsKey(network.CryptoCode))
|
||||
if (_Clients.ContainsKey(network.NBXplorerNetwork.CryptoCode))
|
||||
return network;
|
||||
return null;
|
||||
}
|
||||
@ -96,7 +98,7 @@ namespace BTCPayServer
|
||||
{
|
||||
foreach (var net in _NetworkProviders.GetAll().OfType<BTCPayNetwork>())
|
||||
{
|
||||
if (_Clients.TryGetValue(net.CryptoCode, out ExplorerClient explorer))
|
||||
if (_Clients.TryGetValue(net.NBXplorerNetwork.CryptoCode, out ExplorerClient explorer))
|
||||
{
|
||||
yield return (net, explorer);
|
||||
}
|
||||
|
@ -41,12 +41,10 @@ namespace BTCPayServer
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
#if !NETCOREAPP21
|
||||
public static IQueryable<TEntity> Where<TEntity>(this Microsoft.EntityFrameworkCore.DbSet<TEntity> obj, System.Linq.Expressions.Expression<Func<TEntity, bool>> predicate) where TEntity : class
|
||||
{
|
||||
return System.Linq.Queryable.Where(obj, predicate);
|
||||
}
|
||||
#endif
|
||||
|
||||
public static string Truncate(this string value, int maxLength)
|
||||
{
|
||||
@ -128,9 +126,11 @@ namespace BTCPayServer
|
||||
{
|
||||
if (webSocket.State == WebSocketState.Open)
|
||||
{
|
||||
CancellationTokenSource cts = new CancellationTokenSource();
|
||||
cts.CancelAfter(5000);
|
||||
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", cts.Token);
|
||||
using (CancellationTokenSource cts = new CancellationTokenSource())
|
||||
{
|
||||
cts.CancelAfter(5000);
|
||||
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", cts.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
@ -1,11 +1,7 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
#if NETCOREAPP21
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
|
||||
#else
|
||||
using Microsoft.AspNetCore.Mvc.ViewFeatures;
|
||||
#endif
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
@ -17,12 +13,8 @@ namespace BTCPayServer
|
||||
string message,
|
||||
Controller controller)
|
||||
{
|
||||
#if NETCOREAPP21
|
||||
var key = ExpressionHelper.GetExpressionText(ex);
|
||||
#else
|
||||
var provider = (ModelExpressionProvider)controller.HttpContext.RequestServices.GetService(typeof(ModelExpressionProvider));
|
||||
var key = provider.GetExpressionText(ex);
|
||||
#endif
|
||||
controller.ModelState.AddModelError(key, message);
|
||||
}
|
||||
}
|
||||
|
40
BTCPayServer/Extensions/MoneyExtensions.cs
Normal file
40
BTCPayServer/Extensions/MoneyExtensions.cs
Normal file
@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public static class MoneyExtensions
|
||||
{
|
||||
public static decimal GetValue(this IMoney m, BTCPayNetwork network = null)
|
||||
{
|
||||
switch (m)
|
||||
{
|
||||
case Money money:
|
||||
return money.ToDecimal(MoneyUnit.BTC);
|
||||
case MoneyBag mb:
|
||||
return mb.Select(money => money.GetValue(network)).Sum();
|
||||
case AssetMoney assetMoney:
|
||||
if (network is ElementsBTCPayNetwork elementsBTCPayNetwork)
|
||||
{
|
||||
return elementsBTCPayNetwork.AssetId == assetMoney.AssetId
|
||||
? Convert(assetMoney.Quantity, elementsBTCPayNetwork.Divisibility)
|
||||
: 0;
|
||||
}
|
||||
throw new NotSupportedException("IMoney type not supported");
|
||||
default:
|
||||
throw new NotSupportedException("IMoney type not supported");
|
||||
}
|
||||
}
|
||||
|
||||
public static decimal Convert(long sats, int divisibility = 8)
|
||||
{
|
||||
var amt = sats.ToString(CultureInfo.InvariantCulture).PadLeft(divisibility, '0');
|
||||
amt = amt.Length == divisibility ? $"0.{amt}" : amt.Insert(amt.Length - divisibility, ".");
|
||||
|
||||
return decimal.Parse(amt, CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user