Compare commits
75 Commits
migration-
...
v1.0.3.158
Author | SHA1 | Date | |
---|---|---|---|
c03dc48fe9 | |||
143c909812 | |||
821b904163 | |||
6015eb337a | |||
5d817a0483 | |||
ee9905e85a | |||
ff4c7c364e | |||
a2d657f5cb | |||
db6a4687d2 | |||
07f0d95f56 | |||
1a409a441d | |||
445e184154 | |||
9a10f55a85 | |||
ae33b1d0a8 | |||
4ed2db83a5 | |||
500aa85142 | |||
3b6cc84a93 | |||
5ce29d2bb8 | |||
3184d2b2df | |||
f5e65ec2a6 | |||
66488d813b | |||
4853cfe41a | |||
dc7733abcd | |||
771c8e2758 | |||
24664b60af | |||
82393eb8bb | |||
b432d8903f | |||
ea9169f607 | |||
496a6f0f55 | |||
fb2a0fb7fb | |||
ef503fa907 | |||
fe2eca4fda | |||
88835b5b55 | |||
876c940032 | |||
a08d5be35c | |||
0074790684 | |||
23aaf794ef | |||
bb12d37416 | |||
e058903450 | |||
06f1c17a5f | |||
e00136de93 | |||
56d8c033d7 | |||
666682677c | |||
652b958d4f | |||
c7c0db612a | |||
a83edce4dc | |||
f99058a9fa | |||
a907143d81 | |||
4ae173bb69 | |||
1436420a93 | |||
086cbaa231 | |||
5dd3112e0d | |||
b42e4f240a | |||
7076692069 | |||
dcb3601791 | |||
54c7c0d696 | |||
f324185d82 | |||
a63502873c | |||
f5cbf6672a | |||
a78dff5931 | |||
f8139a9156 | |||
27a61b7afd | |||
71671b9e16 | |||
c68bf5220e | |||
80ee03d897 | |||
d0bfa67495 | |||
bdb2edba12 | |||
78d8f4e011 | |||
1bfe9dda97 | |||
8e6f43cd3a | |||
6848482999 | |||
43967ee86e | |||
61b99f6630 | |||
7e073fb7e1 | |||
1ceb5cb576 |
.gitignore
BTCPayServer.Common
BTCPayServer.Rating
BTCPayServer.Tests
BTCPayServer
Configuration
Controllers
AppsController.Crowdfund.csAppsController.PointOfSale.csErrorController.csInvoiceController.UI.csServerController.csStoresController.csWalletsController.PSBT.csWalletsController.cs
HostedServices
Hosting
Models
Payments
Properties
Services
Views
Account
Apps
AppsPublic
Error
Invoice
Server
Shared
Stores
AddDerivationScheme.cshtmlAddDerivationSchemes_HardwareWalletDialogs.cshtmlAddDerivationSchemes_NBXWalletGenerate.cshtmlRates.cshtmlUpdateStore.cshtml
Wallets
wwwroot
img/errorpages
imlegacy
js
Build
README.md
1
.gitignore
vendored
1
.gitignore
vendored
@ -293,3 +293,4 @@ BTCPayServer/wwwroot/bundles/*
|
||||
!BTCPayServer/wwwroot/bundles/.gitignore
|
||||
|
||||
.vscode
|
||||
BTCPayServer/testpwd
|
||||
|
@ -16,13 +16,13 @@ namespace BTCPayServer
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Polis",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://insight.polispay.org/tx/{0}" : "https://insight.polispay.org/tx/{0}",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockbook.polispay.org/tx/{0}" : "https://blockbook.polispay.org/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "polis",
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"POLIS_X = POLIS_BTC * BTC_X",
|
||||
"POLIS_BTC = cryptopia(POLIS_BTC)"
|
||||
"POLIS_BTC = polispay(POLIS_BTC)"
|
||||
},
|
||||
CryptoImagePath = "imlegacy/polis.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
|
@ -59,7 +59,8 @@ namespace BTCPayServer
|
||||
InitGroestlcoin();
|
||||
InitViacoin();
|
||||
InitMonero();
|
||||
|
||||
InitPolis();
|
||||
|
||||
// Assume that electrum mappings are same as BTC if not specified
|
||||
foreach (var network in _Networks.Values.OfType<BTCPayNetwork>())
|
||||
{
|
||||
@ -77,7 +78,6 @@ namespace BTCPayServer
|
||||
}
|
||||
|
||||
// Disabled because of https://twitter.com/Cryptopia_NZ/status/1085084168852291586
|
||||
//InitPolis();
|
||||
//InitBitcoinplus();
|
||||
//InitUfo();
|
||||
}
|
||||
|
@ -31,21 +31,7 @@ namespace BTCPayServer
|
||||
CryptoImagePath = "imlegacy/liquid.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
SupportRBF = true,
|
||||
//https://github.com/spesmilo/electrum/blob/11733d6bc271646a00b69ff07657119598874da4/electrum/constants.py
|
||||
ElectrumMapping = NetworkType == NetworkType.Mainnet
|
||||
? new Dictionary<uint, DerivationType>()
|
||||
{
|
||||
{0x0488b21eU, DerivationType.Legacy }, // xpub
|
||||
{0x049d7cb2U, DerivationType.SegwitP2SH }, // ypub
|
||||
{0x4b24746U, DerivationType.Segwit }, //zpub
|
||||
}
|
||||
: new Dictionary<uint, DerivationType>()
|
||||
{
|
||||
{0x043587cfU, DerivationType.Legacy},
|
||||
{0x044a5262U, DerivationType.SegwitP2SH},
|
||||
{0x045f1cf6U, DerivationType.Segwit}
|
||||
},
|
||||
SupportRBF = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
@ -16,6 +11,7 @@ namespace BTCPayServer
|
||||
{
|
||||
CryptoCode = "USDt",
|
||||
NetworkCryptoCode = "LBTC",
|
||||
ShowSyncSummary = false,
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"USDT_UST = 1",
|
||||
@ -30,21 +26,30 @@ namespace BTCPayServer
|
||||
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}
|
||||
}
|
||||
SupportRBF = true
|
||||
});
|
||||
|
||||
Add(new ElementsBTCPayNetwork()
|
||||
{
|
||||
CryptoCode = "ETB",
|
||||
NetworkCryptoCode = "LBTC",
|
||||
ShowSyncSummary = false,
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
|
||||
"ETB_X = ETB_BTC * BTC_X",
|
||||
"ETB_BTC = bitpay(ETB_BTC)"
|
||||
},
|
||||
Divisibility = 2,
|
||||
AssetId = new uint256("aa775044c32a7df391902b3659f46dfe004ccb2644ce2ddc7dba31e889391caf"),
|
||||
DisplayName = "Ethiopian Birr",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "liquidnetwork",
|
||||
CryptoImagePath = "imlegacy/etb.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
SupportRBF = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -6,16 +6,16 @@ using NBXplorer.Models;
|
||||
|
||||
namespace BTCPayServer
|
||||
{
|
||||
public class ElementsBTCPayNetwork:BTCPayNetwork
|
||||
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)
|
||||
public override IEnumerable<(MatchedOutput matchedOutput, OutPoint outPoint)> GetValidOutputs(
|
||||
NewTransactionEvent evtOutputs)
|
||||
{
|
||||
return evtOutputs.Outputs.Where(output =>
|
||||
return evtOutputs.Outputs.Where(output =>
|
||||
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId).Select(output =>
|
||||
{
|
||||
var outpoint = new OutPoint(evtOutputs.TransactionData.TransactionHash, output.Index);
|
||||
@ -23,7 +23,7 @@ namespace BTCPayServer
|
||||
});
|
||||
}
|
||||
|
||||
public override string GenerateBIP21(string cryptoInfoAddress, string cryptoInfoDue)
|
||||
public override string GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue)
|
||||
{
|
||||
return $"{base.GenerateBIP21(cryptoInfoAddress, cryptoInfoDue)}&assetid={AssetId}";
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ namespace BTCPayServer
|
||||
{
|
||||
CryptoCode = "XMR",
|
||||
DisplayName = "Monero",
|
||||
Divisibility = 12,
|
||||
BlockExplorerLink =
|
||||
NetworkType == NetworkType.Mainnet
|
||||
? "https://www.exploremonero.com/transaction/{0}"
|
||||
|
@ -113,18 +113,19 @@ namespace BTCPayServer
|
||||
});
|
||||
}
|
||||
|
||||
public virtual string GenerateBIP21(string cryptoInfoAddress, string cryptoInfoDue)
|
||||
public virtual string GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue)
|
||||
{
|
||||
return $"{UriScheme}:{cryptoInfoAddress}?amount={cryptoInfoDue}";
|
||||
return $"{UriScheme}:{cryptoInfoAddress}?amount={cryptoInfoDue.ToString(false, true)}";
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class BTCPayNetworkBase
|
||||
{
|
||||
public bool ShowSyncSummary { get; set; } = true;
|
||||
public string CryptoCode { get; internal set; }
|
||||
public string BlockExplorerLink { get; internal set; }
|
||||
public string DisplayName { get; set; }
|
||||
|
||||
public int Divisibility { get; set; } = 8;
|
||||
[Obsolete("Should not be needed")]
|
||||
public bool IsBTC
|
||||
{
|
||||
|
@ -196,10 +196,12 @@ namespace BTCPayServer.Services.Rates
|
||||
throw new APIException(text);
|
||||
}
|
||||
api.ProcessResponse(new InternalHttpWebResponse(webHttpResponse));
|
||||
Action<IAPIRequestMaker, RequestMakerState, object>? requestStateChanged = RequestStateChanged;
|
||||
// local reference to handle delegate becoming null, extended discussion here:
|
||||
// https://github.com/btcpayserver/btcpayserver/commit/00747906849f093712c3907c99404c55b3defa66#r37022103
|
||||
var requestStateChanged = RequestStateChanged;
|
||||
if (requestStateChanged != null)
|
||||
{
|
||||
requestStateChanged!(this, RequestMakerState.Finished, text);
|
||||
requestStateChanged(this, RequestMakerState.Finished, text);
|
||||
return text;
|
||||
}
|
||||
return text;
|
||||
|
26
BTCPayServer.Rating/Providers/PolisRateProvider.cs
Normal file
26
BTCPayServer.Rating/Providers/PolisRateProvider.cs
Normal file
@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Rating;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Services.Rates
|
||||
{
|
||||
public class PolisRateProvider : IRateProvider
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
public PolisRateProvider(HttpClient httpClient)
|
||||
{
|
||||
_httpClient = httpClient ?? new HttpClient();
|
||||
}
|
||||
|
||||
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var response = await _httpClient.GetAsync("https://obol.polispay.com/complex/btc/polis", cancellationToken); //Returns complex rate from BTC to POLIS
|
||||
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
|
||||
var value = jobj["data"].Value<decimal>();
|
||||
return new[] { new PairRate(new CurrencyPair("BTC", "POLIS"), new BidAsk(value)) };
|
||||
}
|
||||
}
|
||||
}
|
@ -99,6 +99,8 @@ namespace BTCPayServer.Rating
|
||||
RuleList ruleList;
|
||||
|
||||
decimal _Spread;
|
||||
private const string ImplicitSatsRule = "SATS_X = SATS_BTC * BTC_X;\nSATS_BTC = 0.00000001;\n";
|
||||
|
||||
public decimal Spread
|
||||
{
|
||||
get
|
||||
@ -126,6 +128,7 @@ namespace BTCPayServer.Rating
|
||||
}
|
||||
public static bool TryParse(string str, out RateRules rules, out List<RateRulesErrors> errors)
|
||||
{
|
||||
str = ImplicitSatsRule + str;
|
||||
rules = null;
|
||||
errors = null;
|
||||
var expression = CSharpSyntaxTree.ParseText(str, new CSharpParseOptions(LanguageVersion.Default).WithKind(SourceCodeKind.Script));
|
||||
@ -195,6 +198,7 @@ namespace BTCPayServer.Rating
|
||||
{
|
||||
return root.NormalizeWhitespace("", "\n")
|
||||
.ToFullString()
|
||||
.Replace(ImplicitSatsRule, string.Empty, StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("{\n", string.Empty, StringComparison.OrdinalIgnoreCase)
|
||||
.Replace("\n}", string.Empty, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
@ -79,6 +79,8 @@ namespace BTCPayServer.Services.Rates
|
||||
yield return new AvailableRateProvider("bitbank", "Bitbank", "https://public.bitbank.cc/prices");
|
||||
yield return new AvailableRateProvider("bitpay", "Bitpay", "https://bitpay.com/rates");
|
||||
|
||||
yield return new AvailableRateProvider("polispay", "PolisPay", "https://obol.polispay.com/complex/btc/polis");
|
||||
|
||||
yield return new AvailableRateProvider("bitfinex", "Bitfinex", "https://api.bitfinex.com/v2/tickers?symbols=tBTCUSD,tLTCUSD,tLTCBTC,tETHUSD,tETHBTC,tETCBTC,tETCUSD,tRRTUSD,tRRTBTC,tZECUSD,tZECBTC,tXMRUSD,tXMRBTC,tDSHUSD,tDSHBTC,tBTCEUR,tBTCJPY,tXRPUSD,tXRPBTC,tIOTUSD,tIOTBTC,tIOTETH,tEOSUSD,tEOSBTC,tEOSETH,tSANUSD,tSANBTC,tSANETH,tOMGUSD,tOMGBTC,tOMGETH,tNEOUSD,tNEOBTC,tNEOETH,tETPUSD,tETPBTC,tETPETH,tQTMUSD,tQTMBTC,tQTMETH,tAVTUSD,tAVTBTC,tAVTETH,tEDOUSD,tEDOBTC,tEDOETH,tBTGUSD,tBTGBTC,tDATUSD,tDATBTC,tDATETH,tQSHUSD,tQSHBTC,tQSHETH,tYYWUSD,tYYWBTC,tYYWETH,tGNTUSD,tGNTBTC,tGNTETH,tSNTUSD,tSNTBTC,tSNTETH,tIOTEUR,tBATUSD,tBATBTC,tBATETH,tMNAUSD,tMNABTC,tMNAETH,tFUNUSD,tFUNBTC,tFUNETH,tZRXUSD,tZRXBTC,tZRXETH,tTNBUSD,tTNBBTC,tTNBETH,tSPKUSD,tSPKBTC,tSPKETH,tTRXUSD,tTRXBTC,tTRXETH,tRCNUSD,tRCNBTC,tRCNETH,tRLCUSD,tRLCBTC,tRLCETH,tAIDUSD,tAIDBTC,tAIDETH,tSNGUSD,tSNGBTC,tSNGETH,tREPUSD,tREPBTC,tREPETH,tELFUSD,tELFBTC,tELFETH,tNECUSD,tNECBTC,tNECETH,tBTCGBP,tETHEUR,tETHJPY,tETHGBP,tNEOEUR,tNEOJPY,tNEOGBP,tEOSEUR,tEOSJPY,tEOSGBP,tIOTJPY,tIOTGBP,tIOSUSD,tIOSBTC,tIOSETH,tAIOUSD,tAIOBTC,tAIOETH,tREQUSD,tREQBTC,tREQETH,tRDNUSD,tRDNBTC,tRDNETH,tLRCUSD,tLRCBTC,tLRCETH,tWAXUSD,tWAXBTC,tWAXETH,tDAIUSD,tDAIBTC,tDAIETH,tAGIUSD,tAGIBTC,tAGIETH,tBFTUSD,tBFTBTC,tBFTETH,tMTNUSD,tMTNBTC,tMTNETH,tODEUSD,tODEBTC,tODEETH,tANTUSD,tANTBTC,tANTETH,tDTHUSD,tDTHBTC,tDTHETH,tMITUSD,tMITBTC,tMITETH,tSTJUSD,tSTJBTC,tSTJETH,tXLMUSD,tXLMEUR,tXLMJPY,tXLMGBP,tXLMBTC,tXLMETH,tXVGUSD,tXVGEUR,tXVGJPY,tXVGGBP,tXVGBTC,tXVGETH,tBCIUSD,tBCIBTC,tMKRUSD,tMKRBTC,tMKRETH,tKNCUSD,tKNCBTC,tKNCETH,tPOAUSD,tPOABTC,tPOAETH,tEVTUSD,tLYMUSD,tLYMBTC,tLYMETH,tUTKUSD,tUTKBTC,tUTKETH,tVEEUSD,tVEEBTC,tVEEETH,tDADUSD,tDADBTC,tDADETH,tORSUSD,tORSBTC,tORSETH,tAUCUSD,tAUCBTC,tAUCETH,tPOYUSD,tPOYBTC,tPOYETH,tFSNUSD,tFSNBTC,tFSNETH,tCBTUSD,tCBTBTC,tCBTETH,tZCNUSD,tZCNBTC,tZCNETH,tSENUSD,tSENBTC,tSENETH,tNCAUSD,tNCABTC,tNCAETH,tCNDUSD,tCNDBTC,tCNDETH,tCTXUSD,tCTXBTC,tCTXETH,tPAIUSD,tPAIBTC,tSEEUSD,tSEEBTC,tSEEETH,tESSUSD,tESSBTC,tESSETH,tATMUSD,tATMBTC,tATMETH,tHOTUSD,tHOTBTC,tHOTETH,tDTAUSD,tDTABTC,tDTAETH,tIQXUSD,tIQXBTC,tIQXEOS,tWPRUSD,tWPRBTC,tWPRETH,tZILUSD,tZILBTC,tZILETH,tBNTUSD,tBNTBTC,tBNTETH,tABSUSD,tABSETH,tXRAUSD,tXRAETH,tMANUSD,tMANETH,tBBNUSD,tBBNETH,tNIOUSD,tNIOETH,tDGXUSD,tDGXETH,tVETUSD,tVETBTC,tVETETH,tUTNUSD,tUTNETH,tTKNUSD,tTKNETH,tGOTUSD,tGOTEUR,tGOTETH,tXTZUSD,tXTZBTC,tCNNUSD,tCNNETH,tBOXUSD,tBOXETH,tTRXEUR,tTRXGBP,tTRXJPY,tMGOUSD,tMGOETH,tRTEUSD,tRTEETH,tYGGUSD,tYGGETH,tMLNUSD,tMLNETH,tWTCUSD,tWTCETH,tCSXUSD,tCSXETH,tOMNUSD,tOMNBTC,tINTUSD,tINTETH,tDRNUSD,tDRNETH,tPNKUSD,tPNKETH,tDGBUSD,tDGBBTC,tBSVUSD,tBSVBTC,tBABUSD,tBABBTC,tWLOUSD,tWLOXLM,tVLDUSD,tVLDETH,tENJUSD,tENJETH,tONLUSD,tONLETH,tRBTUSD,tRBTBTC,tUSTUSD,tEUTEUR,tEUTUSD,tGSDUSD,tUDCUSD,tTSDUSD,tPAXUSD,tRIFUSD,tRIFBTC,tPASUSD,tPASETH,tVSYUSD,tVSYBTC,tZRXDAI,tMKRDAI,tOMGDAI,tBTTUSD,tBTTBTC,tBTCUST,tETHUST,tCLOUSD,tCLOBTC,tIMPUSD,tIMPETH,tLTCUST,tEOSUST,tBABUST,tSCRUSD,tSCRETH,tGNOUSD,tGNOETH,tGENUSD,tGENETH,tATOUSD,tATOBTC,tATOETH,tWBTUSD,tXCHUSD,tEUSUSD,tWBTETH,tXCHETH,tEUSETH,tLEOUSD,tLEOBTC,tLEOUST,tLEOEOS,tLEOETH,tASTUSD,tASTETH,tFOAUSD,tFOAETH,tUFRUSD,tUFRETH,tZBTUSD,tZBTUST,tOKBUSD,tUSKUSD,tGTXUSD,tKANUSD,tOKBUST,tOKBETH,tOKBBTC,tUSKUST,tUSKETH,tUSKBTC,tUSKEOS,tGTXUST,tKANUST,tAMPUSD,tALGUSD,tALGBTC,tALGUST,tBTCXCH,tSWMUSD,tSWMETH,tTRIUSD,tTRIETH,tLOOUSD,tLOOETH,tAMPUST,tDUSK:USD,tDUSK:BTC,tUOSUSD,tUOSBTC,tRRBUSD,tRRBUST,tDTXUSD,tDTXUST,tAMPBTC,tFTTUSD,tFTTUST,tPAXUST,tUDCUST,tTSDUST,tBTC:CNHT,tUST:CNHT,tCNH:CNHT,tCHZUSD,tCHZUST,tBTCF0:USTF0,tETHF0:USTF0");
|
||||
yield return new AvailableRateProvider("okex", "OKEx", "https://www.okex.com/api/futures/v3/instruments/ticker");
|
||||
yield return new AvailableRateProvider("coinbasepro", "Coinbase Pro", "https://api.pro.coinbase.com/products");
|
||||
@ -98,6 +100,7 @@ namespace BTCPayServer.Services.Rates
|
||||
Providers.Add("bylls", new ByllsRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BYLLS")));
|
||||
Providers.Add("bitbank", new BitbankRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITBANK")));
|
||||
Providers.Add("bitpay", new BitpayRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_BITPAY")));
|
||||
Providers.Add("polispay", new PolisRateProvider(_httpClientFactory?.CreateClient("EXCHANGE_POLIS")));
|
||||
|
||||
|
||||
// Backward compatibility: coinaverage should be using coingecko to prevent stores from breaking
|
||||
|
@ -71,7 +71,7 @@ namespace BTCPayServer.Tests
|
||||
BitcoinAddress.Create(vmLedger.HintChange, user.SupportedNetwork.NBitcoinNetwork);
|
||||
Assert.NotNull(vmLedger.WebsocketPath);
|
||||
|
||||
string redirectedPSBT = AssertRedirectedPSBT(await walletController.WalletSend(walletId, sendModel, command: "analyze-psbt"));
|
||||
string redirectedPSBT = AssertRedirectedPSBT(await walletController.WalletSend(walletId, sendModel, command: "analyze-psbt"), nameof(walletController.WalletPSBT));
|
||||
var vmPSBT = await walletController.WalletPSBT(walletId, new WalletPSBTViewModel() { PSBT = redirectedPSBT }).AssertViewModelAsync<WalletPSBTViewModel>();
|
||||
var unsignedPSBT = PSBT.Parse(vmPSBT.PSBT, user.SupportedNetwork.NBitcoinNetwork);
|
||||
Assert.NotNull(vmPSBT.Decoded);
|
||||
@ -80,14 +80,20 @@ namespace BTCPayServer.Tests
|
||||
PSBT.Load(filePSBT.FileContents, user.SupportedNetwork.NBitcoinNetwork);
|
||||
|
||||
await walletController.WalletPSBT(walletId, vmPSBT, "ledger").AssertViewModelAsync<WalletSendLedgerModel>();
|
||||
var vmPSBT2 = await walletController.WalletPSBT(walletId, vmPSBT, "broadcast").AssertViewModelAsync<WalletPSBTReadyViewModel>();
|
||||
var vmPSBT2 = await walletController.WalletPSBTReady(walletId, new WalletPSBTReadyViewModel()
|
||||
{
|
||||
PSBT = AssertRedirectedPSBT( await walletController.WalletPSBT(walletId, vmPSBT, "broadcast"), nameof(walletController.WalletPSBTReady))
|
||||
} ).AssertViewModelAsync<WalletPSBTReadyViewModel>();
|
||||
Assert.NotEmpty(vmPSBT2.Inputs.Where(i => i.Error != null));
|
||||
Assert.Equal(vmPSBT.PSBT, vmPSBT2.PSBT);
|
||||
|
||||
var signedPSBT = unsignedPSBT.Clone();
|
||||
signedPSBT.SignAll(user.DerivationScheme, user.ExtKey);
|
||||
vmPSBT.PSBT = signedPSBT.ToBase64();
|
||||
var psbtReady = await walletController.WalletPSBT(walletId, vmPSBT, "broadcast").AssertViewModelAsync<WalletPSBTReadyViewModel>();
|
||||
var psbtReady = await walletController.WalletPSBTReady(walletId, new WalletPSBTReadyViewModel()
|
||||
{
|
||||
PSBT = AssertRedirectedPSBT( await walletController.WalletPSBT(walletId, vmPSBT, "broadcast"), nameof(walletController.WalletPSBTReady))
|
||||
} ).AssertViewModelAsync<WalletPSBTReadyViewModel>();
|
||||
Assert.Equal(2 + 1, psbtReady.Destinations.Count); // The fee is a destination
|
||||
Assert.Contains(psbtReady.Destinations, d => d.Destination == sendDestination && !d.Positive);
|
||||
Assert.Contains(psbtReady.Destinations, d => d.Positive);
|
||||
@ -98,7 +104,7 @@ namespace BTCPayServer.Tests
|
||||
var combineVM = await walletController.WalletPSBT(walletId, vmPSBT, "combine").AssertViewModelAsync<WalletPSBTCombineViewModel>();
|
||||
Assert.Equal(vmPSBT.PSBT, combineVM.OtherPSBT);
|
||||
combineVM.PSBT = signedPSBT.ToBase64();
|
||||
var psbt = AssertRedirectedPSBT(await walletController.WalletPSBTCombine(walletId, combineVM));
|
||||
var psbt = AssertRedirectedPSBT(await walletController.WalletPSBTCombine(walletId, combineVM), nameof(walletController.WalletPSBT));
|
||||
|
||||
var signedPSBT2 = PSBT.Parse(psbt, user.SupportedNetwork.NBitcoinNetwork);
|
||||
Assert.True(signedPSBT.TryFinalize(out _));
|
||||
@ -108,7 +114,7 @@ namespace BTCPayServer.Tests
|
||||
// Can use uploaded file?
|
||||
combineVM.PSBT = null;
|
||||
combineVM.UploadedPSBTFile = TestUtils.GetFormFile("signedPSBT", signedPSBT.ToBytes());
|
||||
psbt = AssertRedirectedPSBT(await walletController.WalletPSBTCombine(walletId, combineVM));
|
||||
psbt = AssertRedirectedPSBT(await walletController.WalletPSBTCombine(walletId, combineVM), nameof(walletController.WalletPSBT));
|
||||
signedPSBT2 = PSBT.Parse(psbt, user.SupportedNetwork.NBitcoinNetwork);
|
||||
Assert.True(signedPSBT.TryFinalize(out _));
|
||||
Assert.True(signedPSBT2.TryFinalize(out _));
|
||||
@ -116,17 +122,18 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var ready = (await walletController.WalletPSBTReady(walletId, signedPSBT.ToBase64())).AssertViewModel<WalletPSBTReadyViewModel>();
|
||||
Assert.Equal(signedPSBT.ToBase64(), ready.PSBT);
|
||||
psbt = AssertRedirectedPSBT(await walletController.WalletPSBTReady(walletId, ready, command: "analyze-psbt"));
|
||||
psbt = AssertRedirectedPSBT(await walletController.WalletPSBTReady(walletId, ready, command: "analyze-psbt"), nameof(walletController.WalletPSBT));
|
||||
Assert.Equal(signedPSBT.ToBase64(), psbt);
|
||||
redirect = Assert.IsType<RedirectToActionResult>(await walletController.WalletPSBTReady(walletId, ready, command: "broadcast"));
|
||||
Assert.Equal(nameof(walletController.WalletTransactions), redirect.ActionName);
|
||||
}
|
||||
}
|
||||
|
||||
private static string AssertRedirectedPSBT(IActionResult view)
|
||||
private static string AssertRedirectedPSBT(IActionResult view, string actionName)
|
||||
{
|
||||
var postRedirectView = Assert.IsType<ViewResult>(view);
|
||||
var postRedirectViewModel = Assert.IsType<PostRedirectViewModel>(postRedirectView.Model);
|
||||
Assert.Equal(actionName, postRedirectViewModel.AspAction);
|
||||
var redirectedPSBT = postRedirectViewModel.Parameters.Single(p => p.Key == "psbt").Value;
|
||||
return redirectedPSBT;
|
||||
}
|
||||
|
@ -56,6 +56,8 @@ namespace BTCPayServer.Tests
|
||||
(Pair: "BTC_CAD", Expected: "coinbase(BTC_CAD)"),
|
||||
(Pair: "DOGE_CAD", Expected: "bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1"),
|
||||
(Pair: "LTC_CAD", Expected: "coinaverage(LTC_CAD) * 1.02"),
|
||||
(Pair: "SATS_CAD", Expected: "0.00000001 * coinbase(BTC_CAD)"),
|
||||
(Pair: "Sats_USD", Expected: "0.00000001 * kraken(BTC_USD)")
|
||||
};
|
||||
foreach (var test in tests)
|
||||
{
|
||||
@ -102,6 +104,8 @@ namespace BTCPayServer.Tests
|
||||
(Pair: "BTC_CAD", Expected: "coinbase(BTC_CAD)", ExpectedExchangeRates: "coinbase(BTC_CAD)"),
|
||||
(Pair: "DOGE_CAD", Expected: "bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1", ExpectedExchangeRates: "bittrex(DOGE_BTC),coinbase(BTC_CAD)"),
|
||||
(Pair: "LTC_CAD", Expected: "coinaverage(LTC_CAD) * 1.02", ExpectedExchangeRates: "coinaverage(LTC_CAD)"),
|
||||
(Pair: "SATS_USD", Expected: "0.00000001 * kraken(BTC_USD)", ExpectedExchangeRates: "kraken(BTC_USD)"),
|
||||
(Pair: "SATS_EUR", Expected: "0.00000001 * coinbase(BTC_EUR)", ExpectedExchangeRates: "coinbase(BTC_EUR)")
|
||||
};
|
||||
foreach (var test in tests2)
|
||||
{
|
||||
@ -189,6 +193,37 @@ namespace BTCPayServer.Tests
|
||||
rule2.ExchangeRates.SetRate("coinaverage", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m));
|
||||
Assert.True(rule2.Reevaluate());
|
||||
Assert.Equal($"({(1m / 6100m).ToString(CultureInfo.InvariantCulture)}, {(1m / 6000m).ToString(CultureInfo.InvariantCulture)})", rule2.ToString(true));
|
||||
|
||||
// Make sure defining value in sats works
|
||||
builder = new StringBuilder();
|
||||
builder.AppendLine("BTC_USD = kraken(BTC_USD)");
|
||||
builder.AppendLine("BTC_X = coinbase(BTC_X)");
|
||||
Assert.True(RateRules.TryParse(builder.ToString(), out rules));
|
||||
rule2 = rules.GetRuleFor(CurrencyPair.Parse("SATS_USD"));
|
||||
rule2.ExchangeRates.SetRate("kraken", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m));
|
||||
Assert.True(rule2.Reevaluate());
|
||||
Assert.Equal("0.00000001 * (6000, 6100)", rule2.ToString(true));
|
||||
Assert.Equal(0.00006m, rule2.BidAsk.Bid);
|
||||
rule2 = rules.GetRuleFor(CurrencyPair.Parse("USD_SATS"));
|
||||
rule2.ExchangeRates.SetRate("kraken", CurrencyPair.Parse("BTC_USD"), new BidAsk(6000m, 6100m));
|
||||
Assert.True(rule2.Reevaluate());
|
||||
Assert.Equal("1 / (0.00000001 * (6000, 6100))", rule2.ToString(true));
|
||||
Assert.Equal(1m / 0.000061m, rule2.BidAsk.Bid);
|
||||
|
||||
// testing rounding
|
||||
rule2 = rules.GetRuleFor(CurrencyPair.Parse("Sats_EUR"));
|
||||
rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_EUR"), new BidAsk(1.23m, 2.34m));
|
||||
Assert.True(rule2.Reevaluate());
|
||||
Assert.Equal("0.00000001 * (1.23, 2.34)", rule2.ToString(true));
|
||||
Assert.Equal(0.0000000234m, rule2.BidAsk.Ask);
|
||||
Assert.Equal(0.0000000123m, rule2.BidAsk.Bid);
|
||||
|
||||
rule2 = rules.GetRuleFor(CurrencyPair.Parse("EUR_Sats"));
|
||||
rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_EUR"), new BidAsk(1.23m, 2.34m));
|
||||
Assert.True(rule2.Reevaluate());
|
||||
Assert.Equal("1 / (0.00000001 * (1.23, 2.34))", rule2.ToString(true));
|
||||
Assert.Equal(1m / 0.0000000123m, rule2.BidAsk.Ask);
|
||||
Assert.Equal(1m / 0.0000000234m, rule2.BidAsk.Bid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Views.Stores;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@ -70,18 +71,18 @@ namespace BTCPayServer.Tests
|
||||
Driver.AssertNoError();
|
||||
}
|
||||
|
||||
internal void AssertHappyMessage()
|
||||
internal void AssertHappyMessage(StatusMessageModel.StatusSeverity severity = StatusMessageModel.StatusSeverity.Success)
|
||||
{
|
||||
using var cts = new CancellationTokenSource(20_000);
|
||||
while (!cts.IsCancellationRequested)
|
||||
{
|
||||
var success = Driver.FindElements(By.ClassName("alert-success")).Where(el => el.Displayed).Any();
|
||||
var success = Driver.FindElements(By.ClassName($"alert-{StatusMessageModel.ToString(severity)}")).Any(el => el.Displayed);
|
||||
if (success)
|
||||
return;
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
Logs.Tester.LogInformation(this.Driver.PageSource);
|
||||
Assert.True(false, "Should have shown happy message");
|
||||
Assert.True(false, $"Should have shown {severity} message");
|
||||
}
|
||||
|
||||
public static readonly TimeSpan ImplicitWait = TimeSpan.FromSeconds(10);
|
||||
@ -202,7 +203,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
internal void AssertNotFound()
|
||||
{
|
||||
Assert.Contains("Status Code: 404; Not Found", Driver.PageSource);
|
||||
Assert.Contains("404 - Page not found</h1>", Driver.PageSource);
|
||||
}
|
||||
|
||||
public void GoToHome()
|
||||
|
@ -9,6 +9,8 @@ using System.Linq;
|
||||
using NBitcoin;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.RegularExpressions;
|
||||
using BTCPayServer.Models;
|
||||
using NBitcoin.Payment;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -432,6 +434,13 @@ namespace BTCPayServer.Tests
|
||||
//let's test quickly the receive wallet page
|
||||
s.Driver.FindElement(By.Id("Wallets")).Click();
|
||||
s.Driver.FindElement(By.LinkText("Manage")).Click();
|
||||
|
||||
s.Driver.FindElement(By.Id("WalletSend")).Click();
|
||||
s.Driver.ScrollTo(By.Id("SendMenu"));
|
||||
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
|
||||
//you cant use the Sign with NBX option without saving private keys when generating the wallet.
|
||||
Assert.DoesNotContain("nbx-seed", s.Driver.PageSource);
|
||||
|
||||
s.Driver.FindElement(By.Id("WalletReceive")).Click();
|
||||
//generate a receiving address
|
||||
s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
|
||||
@ -472,16 +481,19 @@ namespace BTCPayServer.Tests
|
||||
var address = invoice.EntityToDTO().Addresses["BTC"];
|
||||
|
||||
|
||||
//wallet should have been imported to bitcoin core wallet in watch only mode.
|
||||
var result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
|
||||
Assert.True(result.IsWatchOnly);
|
||||
s.GoToStore(storeId.storeId);
|
||||
var mnemonic = s.GenerateWallet("BTC", "", true, true);
|
||||
|
||||
//lets import and save private keys
|
||||
var root = new Mnemonic(mnemonic).DeriveExtKey();
|
||||
invoiceId = s.CreateInvoice(storeId.storeId);
|
||||
invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice( invoiceId);
|
||||
address = invoice.EntityToDTO().Addresses["BTC"];
|
||||
result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
|
||||
//spendable from bitcoin core wallet!
|
||||
Assert.False(result.IsWatchOnly);
|
||||
var tx = s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create(address, Network.RegTest), Money.Coins(3.0m));
|
||||
s.Server.ExplorerNode.Generate(1);
|
||||
@ -538,7 +550,43 @@ namespace BTCPayServer.Tests
|
||||
checkboxElement.Click();
|
||||
}
|
||||
}
|
||||
|
||||
SignWith(mnemonic);
|
||||
|
||||
s.Driver.FindElement(By.Id("Wallets")).Click();
|
||||
s.Driver.FindElement(By.LinkText("Manage")).Click();
|
||||
s.Driver.FindElement(By.Id("WalletSend")).Click();
|
||||
|
||||
var jack = new Key().PubKey.Hash.GetAddress(Network.RegTest);
|
||||
SetTransactionOutput(0, jack, 0.01m);
|
||||
s.Driver.ScrollTo(By.Id("SendMenu"));
|
||||
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
|
||||
|
||||
s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
|
||||
Assert.Contains(jack.ToString(), s.Driver.PageSource);
|
||||
Assert.Contains("0.01000000", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.CssSelector("button[value=analyze-psbt]")).ForceClick();
|
||||
Assert.EndsWith("psbt", s.Driver.Url);
|
||||
s.Driver.FindElement(By.CssSelector("#OtherActions")).ForceClick();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
|
||||
Assert.EndsWith("psbt/ready", s.Driver.Url);
|
||||
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
|
||||
Assert.Equal(walletTransactionLink, s.Driver.Url);
|
||||
|
||||
var bip21 = invoice.EntityToDTO().CryptoInfo.First().PaymentUrls.BIP21;
|
||||
//let's make bip21 more interesting
|
||||
bip21 += "&label=Solid Snake&message=Snake? Snake? SNAAAAKE!";
|
||||
var parsedBip21 = new BitcoinUrlBuilder(bip21, Network.RegTest);
|
||||
s.Driver.FindElement(By.Id("Wallets")).Click();
|
||||
s.Driver.FindElement(By.LinkText("Manage")).Click();
|
||||
s.Driver.FindElement(By.Id("WalletSend")).Click();
|
||||
s.Driver.FindElement(By.Id("bip21parse")).Click();
|
||||
s.Driver.SwitchTo().Alert().SendKeys(bip21);
|
||||
s.Driver.SwitchTo().Alert().Accept();
|
||||
s.AssertHappyMessage(StatusMessageModel.StatusSeverity.Info);
|
||||
Assert.Equal(parsedBip21.Amount.ToString(false), s.Driver.FindElement(By.Id($"Outputs_0__Amount")).GetAttribute("value"));
|
||||
Assert.Equal(parsedBip21.Address.ToString(), s.Driver.FindElement(By.Id($"Outputs_0__DestinationAddress")).GetAttribute("value"));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -173,7 +173,7 @@ namespace BTCPayServer.Tests
|
||||
invoiceEntity.Payments.Add(
|
||||
new PaymentEntity()
|
||||
{
|
||||
|
||||
|
||||
Accounted = true,
|
||||
CryptoCode = "BTC",
|
||||
NetworkFee = 0.00000100m,
|
||||
@ -315,7 +315,7 @@ namespace BTCPayServer.Tests
|
||||
entity.Payments.Add(
|
||||
new PaymentEntity()
|
||||
{
|
||||
Output = new TxOut(Money.Coins(0.2m), new Key()),
|
||||
Output = new TxOut(Money.Coins(0.2m), new Key()),
|
||||
Accounted = true
|
||||
});
|
||||
|
||||
@ -330,15 +330,15 @@ namespace BTCPayServer.Tests
|
||||
paymentMethods.Add(
|
||||
new PaymentMethod()
|
||||
{
|
||||
CryptoCode = "BTC",
|
||||
Rate = 1000,
|
||||
CryptoCode = "BTC",
|
||||
Rate = 1000,
|
||||
NextNetworkFee = Money.Coins(0.1m)
|
||||
});
|
||||
paymentMethods.Add(
|
||||
new PaymentMethod()
|
||||
{
|
||||
CryptoCode = "LTC",
|
||||
Rate = 500,
|
||||
CryptoCode = "LTC",
|
||||
Rate = 500,
|
||||
NextNetworkFee = Money.Coins(0.01m)
|
||||
});
|
||||
entity.SetPaymentMethods(paymentMethods);
|
||||
@ -521,6 +521,39 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanThrowBitpay404Error()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
|
||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||
{
|
||||
Buyer = new Buyer() { email = "test@fwf.com" },
|
||||
Price = 5000.0m,
|
||||
Currency = "USD",
|
||||
PosData = "posData",
|
||||
OrderId = "orderId",
|
||||
ItemDesc = "Some description",
|
||||
FullNotifications = true
|
||||
}, Facade.Merchant);
|
||||
|
||||
try
|
||||
{
|
||||
var throwsBitpay404Error = user.BitPay.GetInvoice(invoice.Id + "123");
|
||||
}
|
||||
catch (BitPayException ex)
|
||||
{
|
||||
Assert.Equal("Object not found", ex.Errors.First());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void RoundupCurrenciesCorrectly()
|
||||
@ -819,7 +852,7 @@ namespace BTCPayServer.Tests
|
||||
var walletId = new WalletId(acc.StoreId, "BTC");
|
||||
acc.IsAdmin = true;
|
||||
walletController = acc.GetController<WalletsController>();
|
||||
|
||||
|
||||
var rescan = Assert.IsType<RescanWalletModel>(Assert.IsType<ViewResult>(walletController.WalletRescan(walletId).Result).Model);
|
||||
Assert.True(rescan.Ok);
|
||||
Assert.True(rescan.IsFullySync);
|
||||
@ -1788,7 +1821,7 @@ namespace BTCPayServer.Tests
|
||||
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;
|
||||
Assert.False(derivationVM.Confirmation); // Should fail, we are giving a mainnet file to a testnet network
|
||||
Assert.False(derivationVM.Confirmation); // Should fail, we are giving a mainnet file to a testnet network
|
||||
|
||||
// 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}";
|
||||
@ -1970,15 +2003,15 @@ donation:
|
||||
Assert.Equal(10.00m, orangeInvoice.Price);
|
||||
Assert.Equal("CAD", orangeInvoice.Currency);
|
||||
Assert.Equal("orange", orangeInvoice.ItemDesc);
|
||||
|
||||
|
||||
|
||||
|
||||
Assert.IsType<RedirectToActionResult>(publicApps.ViewPointOfSale(appId, 0, null, null, null, null, "apple").Result);
|
||||
|
||||
invoices = user.BitPay.GetInvoices();
|
||||
var appleInvoice = invoices.SingleOrDefault(invoice => invoice.ItemCode.Equals("apple"));
|
||||
Assert.NotNull(appleInvoice);
|
||||
Assert.Equal("good apple", appleInvoice.ItemDesc);
|
||||
|
||||
|
||||
|
||||
// testing custom amount
|
||||
var action = Assert.IsType<RedirectToActionResult>(publicApps.ViewPointOfSale(appId, 6.6m, null, null, null, null, "donation").Result);
|
||||
@ -2026,8 +2059,8 @@ donation:
|
||||
Assert.Equal(test.ExpectedDivisibility, vmview.CurrencyInfo.Divisibility);
|
||||
Assert.Equal(test.ExpectedSymbolSpace, vmview.CurrencyInfo.SymbolSpace);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//test inventory related features
|
||||
vmpos = Assert.IsType<UpdatePointOfSaleViewModel>(Assert.IsType<ViewResult>(apps.UpdatePointOfSale(appId).Result).Model);
|
||||
vmpos.Title = "hello";
|
||||
@ -2040,13 +2073,13 @@ inventoryitem:
|
||||
noninventoryitem:
|
||||
price: 10.0";
|
||||
Assert.IsType<RedirectToActionResult>(apps.UpdatePointOfSale(appId, vmpos).Result);
|
||||
|
||||
|
||||
//inventoryitem has 1 item available
|
||||
Assert.IsType<RedirectToActionResult>(publicApps.ViewPointOfSale(appId, 1, null, null, null, null, "inventoryitem").Result);
|
||||
//we already bought all available stock so this should fail
|
||||
await Task.Delay(100);
|
||||
Assert.IsType<RedirectToActionResult>(publicApps.ViewPointOfSale(appId, 1, null, null, null, null, "inventoryitem").Result);
|
||||
|
||||
|
||||
//inventoryitem has unlimited items available
|
||||
Assert.IsType<RedirectToActionResult>(publicApps.ViewPointOfSale(appId, 1, null, null, null, null, "noninventoryitem").Result);
|
||||
Assert.IsType<RedirectToActionResult>(publicApps.ViewPointOfSale(appId, 1, null, null, null, null, "noninventoryitem").Result);
|
||||
@ -2056,7 +2089,7 @@ noninventoryitem:
|
||||
Assert.Equal(2, invoices.Count(invoice => invoice.ItemCode.Equals("noninventoryitem")));
|
||||
var inventoryItemInvoice = Assert.Single(invoices.Where(invoice => invoice.ItemCode.Equals("inventoryitem")));
|
||||
Assert.NotNull(inventoryItemInvoice);
|
||||
|
||||
|
||||
//let's mark the inventoryitem invoice as invalid, thsi should return the item to back in stock
|
||||
var controller = tester.PayTester.GetController<InvoiceController>(user.UserId, user.StoreId);
|
||||
var appService = tester.PayTester.GetService<AppService>();
|
||||
@ -2068,7 +2101,7 @@ noninventoryitem:
|
||||
vmpos = Assert.IsType<UpdatePointOfSaleViewModel>(Assert.IsType<ViewResult>(apps.UpdatePointOfSale(appId).Result).Model);
|
||||
Assert.Equal(1, appService.Parse(vmpos.Template, "BTC").Single(item => item.Id == "inventoryitem").Inventory);
|
||||
}, 10000);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -2251,7 +2284,7 @@ noninventoryitem:
|
||||
|
||||
var cashCow = tester.ExplorerNode;
|
||||
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
|
||||
//
|
||||
//
|
||||
var firstPayment = invoice.CryptoInfo[0].TotalDue - 3 * networkFee;
|
||||
cashCow.SendToAddress(invoiceAddress, firstPayment);
|
||||
Thread.Sleep(1000); // prevent race conditions, ordering payments
|
||||
@ -2689,7 +2722,7 @@ noninventoryitem:
|
||||
.Select(p => (ExpectedName: p.Key, ResultAsync: p.Value.GetRatesAsync(default), Fetcher: (BackgroundFetcherRateProvider)p.Value))
|
||||
.ToList())
|
||||
{
|
||||
|
||||
|
||||
Logs.Tester.LogInformation($"Testing {result.ExpectedName}");
|
||||
result.Fetcher.InvalidateCache();
|
||||
var exchangeRates = new ExchangeRates(result.ExpectedName, result.ResultAsync.Result);
|
||||
@ -2702,6 +2735,11 @@ noninventoryitem:
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "JPY") && e.BidAsk.Bid > 100m); // 1BTC will always be more than 100JPY
|
||||
}
|
||||
else if (result.ExpectedName == "polispay")
|
||||
{
|
||||
Assert.Contains(exchangeRates.ByExchange[result.ExpectedName],
|
||||
e => e.CurrencyPair == new CurrencyPair("BTC", "POLIS") && e.BidAsk.Bid > 1.0m); // 1BTC will always be more than 1 POLIS
|
||||
}
|
||||
else
|
||||
{
|
||||
// This check if the currency pair is using right currency pair
|
||||
@ -2887,6 +2925,18 @@ noninventoryitem:
|
||||
return name;
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Fast", "Fast")]
|
||||
public async Task CanCreateSqlitedb()
|
||||
{
|
||||
if (File.Exists("temp.db"))
|
||||
File.Delete("temp.db");
|
||||
// This test sqlite can migrate
|
||||
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
|
||||
builder.UseSqlite("Data Source=temp.db");
|
||||
await new ApplicationDbContext(builder.Options).Database.MigrateAsync();
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void CheckRatesProvider()
|
||||
@ -2947,7 +2997,7 @@ noninventoryitem:
|
||||
Assert.True(DerivationSchemeSettings.TryParseFromColdcard("{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"vpub5YjYxTemJ39tFRnuAhwduyxG2tKGjoEpmvqVQRPqdYrqa6YGoeSzBtHXaJUYB19zDbXs3JjbEcVWERjQBPf9bEfUUMZNMv1QnMyHV8JPqyf\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/84'/1'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}", testnet, out settings));
|
||||
Assert.True(settings.AccountDerivation is DirectDerivationStrategy s3 && s3.Segwit);
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Altcoins", "Altcoins")]
|
||||
@ -2968,8 +3018,8 @@ noninventoryitem:
|
||||
|
||||
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(100, "BTC"));
|
||||
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count);
|
||||
|
||||
|
||||
|
||||
|
||||
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(100, "BTC")
|
||||
{
|
||||
SupportedTransactionCurrencies = new Dictionary<string, InvoiceSupportedTransactionCurrency>()
|
||||
@ -2980,13 +3030,13 @@ noninventoryitem:
|
||||
}}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Assert.Single(invoice.SupportedTransactionCurrencies);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanLoginWithNoSecondaryAuthSystemsOrRequestItWhenAdded()
|
||||
@ -3007,7 +3057,7 @@ noninventoryitem:
|
||||
})).ActionName);
|
||||
|
||||
var manageController = user.GetController<ManageController>();
|
||||
|
||||
|
||||
//by default no u2f devices available
|
||||
Assert.Empty(Assert.IsType<U2FAuthenticationViewModel>(Assert.IsType<ViewResult>(await manageController.U2FAuthentication()).Model).Devices);
|
||||
var addRequest = Assert.IsType<AddU2FDeviceViewModel>(Assert.IsType<ViewResult>(manageController.AddU2FDevice("label")).Model);
|
||||
@ -3036,10 +3086,10 @@ noninventoryitem:
|
||||
};
|
||||
await context.U2FDevices.AddAsync(newDevice);
|
||||
await context.SaveChangesAsync();
|
||||
|
||||
|
||||
Assert.NotNull(newDevice.Id);
|
||||
Assert.NotEmpty(Assert.IsType<U2FAuthenticationViewModel>(Assert.IsType<ViewResult>(await manageController.U2FAuthentication()).Model).Devices);
|
||||
|
||||
|
||||
}
|
||||
|
||||
//check if we are showing the u2f login screen now
|
||||
@ -3053,10 +3103,10 @@ noninventoryitem:
|
||||
var vm = Assert.IsType<SecondaryLoginViewModel>(secondLoginResult.Model);
|
||||
//2fa was never enabled for user so this should be empty
|
||||
Assert.Null(vm.LoginWith2FaViewModel);
|
||||
Assert.NotNull(vm.LoginWithU2FViewModel);
|
||||
Assert.NotNull(vm.LoginWithU2FViewModel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static bool IsMapped(Invoice invoice, ApplicationDbContext ctx)
|
||||
{
|
||||
var h = BitcoinAddress.Create(invoice.BitcoinAddress, Network.RegTest).ScriptPubKey.Hash.ToString();
|
||||
|
@ -76,7 +76,7 @@ services:
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.1.5
|
||||
image: nicolasdorier/nbxplorer:2.1.8
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
|
@ -141,6 +141,7 @@ namespace BTCPayServer.Configuration
|
||||
ExternalServices.Load(net.CryptoCode, conf);
|
||||
}
|
||||
|
||||
ExternalServices.LoadNonCryptoServices(conf);
|
||||
Logs.Configuration.LogInformation("Supported chains: " + String.Join(',', supportedChains.ToArray()));
|
||||
|
||||
var services = conf.GetOrDefault<string>("externalservices", null);
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Controllers;
|
||||
@ -46,7 +47,7 @@ namespace BTCPayServer.Configuration
|
||||
}
|
||||
connectionString.Server = serviceUri;
|
||||
|
||||
if (serviceType == ExternalServiceTypes.LNDGRPC || serviceType == ExternalServiceTypes.LNDRest)
|
||||
if (serviceType == ExternalServiceTypes.LNDGRPC || serviceType == ExternalServiceTypes.LNDRest || serviceType == ExternalServiceTypes.CLightningRest)
|
||||
{
|
||||
// Read the MacaroonDirectory
|
||||
if (connectionString.MacaroonDirectoryPath != null)
|
||||
@ -77,7 +78,7 @@ namespace BTCPayServer.Configuration
|
||||
}
|
||||
}
|
||||
|
||||
if (serviceType == ExternalServiceTypes.Charge || serviceType == ExternalServiceTypes.RTL || serviceType == ExternalServiceTypes.Spark)
|
||||
if (new []{ExternalServiceTypes.Charge, ExternalServiceTypes.RTL, ExternalServiceTypes.Spark, ExternalServiceTypes.Configurator}.Contains(serviceType))
|
||||
{
|
||||
// Read access key from cookie file
|
||||
if (connectionString.CookieFilePath != null)
|
||||
@ -94,7 +95,7 @@ namespace BTCPayServer.Configuration
|
||||
{
|
||||
throw new System.IO.FileNotFoundException("Cookie file path not found", ex);
|
||||
}
|
||||
if (serviceType == ExternalServiceTypes.RTL)
|
||||
if (serviceType == ExternalServiceTypes.RTL || serviceType == ExternalServiceTypes.Configurator)
|
||||
{
|
||||
connectionString.AccessKey = cookieFileContent;
|
||||
}
|
||||
|
@ -35,17 +35,30 @@ namespace BTCPayServer.Configuration
|
||||
Load(configuration, cryptoCode, "rtl", ExternalServiceTypes.RTL, "Invalid setting {0}, " + Environment.NewLine +
|
||||
$"Valid example: 'server=https://btcpay.example.com/rtl/btc/;cookiefile=/etc/clightning_bitcoin_rtl/.cookie'" + Environment.NewLine +
|
||||
"Error: {1}",
|
||||
"LND (Ride the Lightning server)");
|
||||
"Ride the Lightning server");
|
||||
Load(configuration, cryptoCode, "clightningrest", ExternalServiceTypes.CLightningRest, "Invalid setting {0}, " + Environment.NewLine +
|
||||
$"Valid example: 'server=https://btcpay.example.com/clightning-rest/btc/;cookiefile=/etc/clightning_bitcoin_rtl/.cookie'" + Environment.NewLine +
|
||||
"Error: {1}",
|
||||
"C-Lightning REST");
|
||||
Load(configuration, cryptoCode, "charge", ExternalServiceTypes.Charge, "Invalid setting {0}, " + Environment.NewLine +
|
||||
$"lightning charge server: 'type=charge;server=https://charge.example.com;api-token=2abdf302...'" + Environment.NewLine +
|
||||
$"lightning charge server: 'type=charge;server=https://charge.example.com;cookiefilepath=/root/.charge/.cookie'" + Environment.NewLine +
|
||||
"Error: {1}",
|
||||
"C-Lightning (Charge server)");
|
||||
|
||||
}
|
||||
|
||||
public void LoadNonCryptoServices(IConfiguration configuration)
|
||||
{
|
||||
Load(configuration, null, "configurator", ExternalServiceTypes.Configurator, "Invalid setting {0}, " + Environment.NewLine +
|
||||
$"configurator: 'cookiefilepathfile=/etc/configurator/cookie'" + Environment.NewLine +
|
||||
"Error: {1}",
|
||||
"Configurator");
|
||||
}
|
||||
|
||||
void Load(IConfiguration configuration, string cryptoCode, string serviceName, ExternalServiceTypes type, string errorMessage, string displayName)
|
||||
{
|
||||
var setting = $"{cryptoCode}.external.{serviceName}";
|
||||
var setting = $"{(!string.IsNullOrEmpty(cryptoCode)? $"{cryptoCode}.": string.Empty)}external.{serviceName}";
|
||||
var connStr = configuration.GetOrDefault<string>(setting, string.Empty);
|
||||
if (connStr.Length != 0)
|
||||
{
|
||||
@ -65,8 +78,11 @@ namespace BTCPayServer.Configuration
|
||||
|
||||
public ExternalService GetService(string serviceName, string cryptoCode)
|
||||
{
|
||||
return this.FirstOrDefault(o => o.CryptoCode.Equals(cryptoCode, StringComparison.OrdinalIgnoreCase) &&
|
||||
o.ServiceName.Equals(serviceName, StringComparison.OrdinalIgnoreCase));
|
||||
return this.FirstOrDefault(o =>
|
||||
(cryptoCode == null && o.CryptoCode == null) ||
|
||||
(o.CryptoCode != null && o.CryptoCode.Equals(cryptoCode, StringComparison.OrdinalIgnoreCase))
|
||||
&&
|
||||
o.ServiceName.Equals(serviceName, StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,6 +104,8 @@ namespace BTCPayServer.Configuration
|
||||
RTL,
|
||||
Charge,
|
||||
P2P,
|
||||
RPC
|
||||
RPC,
|
||||
Configurator,
|
||||
CLightningRest
|
||||
}
|
||||
}
|
||||
|
@ -22,8 +22,8 @@ namespace BTCPayServer.Controllers
|
||||
return String.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
[HttpGet]
|
||||
[Route("{appId}/settings/crowdfund")]
|
||||
public async Task<IActionResult> UpdateCrowdfund(string appId)
|
||||
@ -61,8 +61,8 @@ namespace BTCPayServer.Controllers
|
||||
SearchTerm = app.TagAllInvoices ? $"storeid:{app.StoreDataId}" : $"orderid:{AppService.GetCrowdfundOrderId(appId)}",
|
||||
DisplayPerksRanking = settings.DisplayPerksRanking,
|
||||
SortPerksByPopularity = settings.SortPerksByPopularity,
|
||||
Sounds = string.Join(Environment.NewLine, settings.Sounds),
|
||||
AnimationColors = string.Join(Environment.NewLine, settings.AnimationColors)
|
||||
Sounds = string.Join(Environment.NewLine, settings.Sounds),
|
||||
AnimationColors = string.Join(Environment.NewLine, settings.AnimationColors)
|
||||
};
|
||||
return View(vm);
|
||||
}
|
||||
@ -70,9 +70,9 @@ namespace BTCPayServer.Controllers
|
||||
[Route("{appId}/settings/crowdfund")]
|
||||
public async Task<IActionResult> UpdateCrowdfund(string appId, UpdateCrowdfundViewModel vm, string command)
|
||||
{
|
||||
if (!string.IsNullOrEmpty( vm.TargetCurrency) && _currencies.GetCurrencyData(vm.TargetCurrency, false) == null)
|
||||
if (!string.IsNullOrEmpty(vm.TargetCurrency) && _currencies.GetCurrencyData(vm.TargetCurrency, false) == null)
|
||||
ModelState.AddModelError(nameof(vm.TargetCurrency), "Invalid currency");
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
_AppService.Parse(vm.PerksTemplate, vm.TargetCurrency).ToString();
|
||||
@ -98,14 +98,14 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
var parsedSounds = vm.Sounds.Split(
|
||||
new[] {"\r\n", "\r", "\n"},
|
||||
new[] { "\r\n", "\r", "\n" },
|
||||
StringSplitOptions.None
|
||||
).Select(s => s.Trim()).ToArray();
|
||||
if (vm.SoundsEnabled && !parsedSounds.Any())
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.Sounds), "You must have at least one sound if you enable sounds");
|
||||
}
|
||||
|
||||
|
||||
var parsedAnimationColors = vm.AnimationColors.Split(
|
||||
new[] { "\r\n", "\r", "\n" },
|
||||
StringSplitOptions.None
|
||||
@ -114,13 +114,13 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.AnimationColors), "You must have at least one animation color if you enable animations");
|
||||
}
|
||||
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
var app = await GetOwnedApp(appId, AppType.Crowdfund);
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
@ -157,24 +157,16 @@ namespace BTCPayServer.Controllers
|
||||
app.TagAllInvoices = vm.UseAllStoreInvoices;
|
||||
app.SetSettings(newSettings);
|
||||
|
||||
if (command == "save")
|
||||
{
|
||||
await _AppService.UpdateOrCreateApp(app);
|
||||
await _AppService.UpdateOrCreateApp(app);
|
||||
|
||||
_EventAggregator.Publish(new AppUpdated()
|
||||
{
|
||||
AppId = appId,
|
||||
StoreId = app.StoreDataId,
|
||||
Settings = newSettings
|
||||
});
|
||||
TempData[WellKnownTempData.SuccessMessage] = "App updated";
|
||||
return RedirectToAction(nameof(UpdateCrowdfund), new { appId });
|
||||
}
|
||||
else if (command == "viewapp")
|
||||
_EventAggregator.Publish(new AppUpdated()
|
||||
{
|
||||
return RedirectToAction(nameof(AppsPublicController.ViewCrowdfund), "AppsPublic", new { appId });
|
||||
}
|
||||
return NotFound();
|
||||
AppId = appId,
|
||||
StoreId = app.StoreDataId,
|
||||
Settings = newSettings
|
||||
});
|
||||
TempData[WellKnownTempData.SuccessMessage] = "App updated";
|
||||
return RedirectToAction(nameof(UpdateCrowdfund), new { appId });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -79,9 +79,9 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
|
||||
public string CustomCSSLink { get; set; }
|
||||
|
||||
|
||||
public string EmbeddedCSS { get; set; }
|
||||
|
||||
|
||||
public string Description { get; set; }
|
||||
public string NotificationEmail { get; set; }
|
||||
public string NotificationUrl { get; set; }
|
||||
@ -96,7 +96,7 @@ namespace BTCPayServer.Controllers
|
||||
if (app == null)
|
||||
return NotFound();
|
||||
var settings = app.GetSettings<PointOfSaleSettings>();
|
||||
|
||||
|
||||
var vm = new UpdatePointOfSaleViewModel()
|
||||
{
|
||||
NotificationEmailWarning = !await IsEmailConfigured(app.StoreDataId),
|
||||
@ -119,7 +119,7 @@ namespace BTCPayServer.Controllers
|
||||
NotificationEmail = settings.NotificationEmail,
|
||||
NotificationUrl = settings.NotificationUrl,
|
||||
SearchTerm = $"storeid:{app.StoreDataId}",
|
||||
RedirectAutomatically = settings.RedirectAutomatically.HasValue? settings.RedirectAutomatically.Value? "true": "false" : ""
|
||||
RedirectAutomatically = settings.RedirectAutomatically.HasValue ? settings.RedirectAutomatically.Value ? "true" : "false" : ""
|
||||
};
|
||||
if (HttpContext?.Request != null)
|
||||
{
|
||||
@ -197,8 +197,8 @@ namespace BTCPayServer.Controllers
|
||||
NotificationEmail = vm.NotificationEmail,
|
||||
Description = vm.Description,
|
||||
EmbeddedCSS = vm.EmbeddedCSS,
|
||||
RedirectAutomatically = string.IsNullOrEmpty(vm.RedirectAutomatically)? (bool?) null: bool.Parse(vm.RedirectAutomatically)
|
||||
|
||||
RedirectAutomatically = string.IsNullOrEmpty(vm.RedirectAutomatically) ? (bool?)null : bool.Parse(vm.RedirectAutomatically)
|
||||
|
||||
});
|
||||
await _AppService.UpdateOrCreateApp(app);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "App updated";
|
||||
@ -211,8 +211,8 @@ namespace BTCPayServer.Controllers
|
||||
if (string.IsNullOrEmpty(list))
|
||||
{
|
||||
return Array.Empty<int>();
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remove all characters except numeric and comma
|
||||
Regex charsToDestroy = new Regex(@"[^\d|\" + separator + "]");
|
||||
|
27
BTCPayServer/Controllers/ErrorController.cs
Normal file
27
BTCPayServer/Controllers/ErrorController.cs
Normal file
@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
[Route("[controller]/[action]")]
|
||||
public class ErrorController : Controller
|
||||
{
|
||||
public IActionResult Handle(int? statusCode = null)
|
||||
{
|
||||
if (statusCode.HasValue)
|
||||
{
|
||||
var specialPages = new[] { 404, 429, 500 };
|
||||
if (specialPages.Any(a => a == statusCode.Value))
|
||||
{
|
||||
var viewName = statusCode.ToString();
|
||||
return View(viewName);
|
||||
}
|
||||
}
|
||||
return View(statusCode);
|
||||
}
|
||||
}
|
||||
}
|
@ -104,7 +104,6 @@ namespace BTCPayServer.Controllers
|
||||
cryptoPayment.Paid = _CurrencyNameTable.DisplayFormatCurrency(accounting.CryptoPaid.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
|
||||
cryptoPayment.Overpaid = _CurrencyNameTable.DisplayFormatCurrency(accounting.OverpaidHelper.ToDecimal(MoneyUnit.BTC), paymentMethodId.CryptoCode);
|
||||
var paymentMethodDetails = data.GetPaymentMethodDetails();
|
||||
cryptoPayment.Address = paymentMethodDetails.GetPaymentDestination();
|
||||
cryptoPayment.Rate = ExchangeRate(data);
|
||||
model.CryptoPayments.Add(cryptoPayment);
|
||||
}
|
||||
|
@ -528,10 +528,12 @@ namespace BTCPayServer.Controllers
|
||||
return null;
|
||||
}
|
||||
|
||||
[Route("server/services/{serviceName}/{cryptoCode}")]
|
||||
|
||||
|
||||
[Route("server/services/{serviceName}/{cryptoCode?}")]
|
||||
public async Task<IActionResult> Service(string serviceName, string cryptoCode, bool showQR = false, uint? nonce = null)
|
||||
{
|
||||
if (!_dashBoard.IsFullySynched(cryptoCode, out _))
|
||||
if (!string.IsNullOrEmpty(cryptoCode) && !_dashBoard.IsFullySynched(cryptoCode, out _))
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = $"{cryptoCode} is not fully synched";
|
||||
return RedirectToAction(nameof(Services));
|
||||
@ -542,6 +544,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
if (service.Type == ExternalServiceTypes.P2P)
|
||||
{
|
||||
return View("P2PService", new LightningWalletServices()
|
||||
@ -592,9 +595,19 @@ namespace BTCPayServer.Controllers
|
||||
vm.WalletName = service.DisplayName;
|
||||
vm.ServiceLink = $"{connectionString.Server}?access-key={connectionString.AccessKey}";
|
||||
return View("LightningWalletServices", vm);
|
||||
case ExternalServiceTypes.CLightningRest:
|
||||
return LndServices(service, connectionString, nonce, "CLightningRestServices");
|
||||
case ExternalServiceTypes.LNDGRPC:
|
||||
case ExternalServiceTypes.LNDRest:
|
||||
return LndServices(service, connectionString, nonce);
|
||||
case ExternalServiceTypes.Configurator:
|
||||
return View("ConfiguratorService",
|
||||
new LightningWalletServices()
|
||||
{
|
||||
ShowQR = showQR,
|
||||
WalletName = service.ServiceName,
|
||||
ServiceLink = $"{connectionString.Server}?password={connectionString.AccessKey}"
|
||||
});
|
||||
default:
|
||||
throw new NotSupportedException(service.Type.ToString());
|
||||
}
|
||||
@ -663,7 +676,7 @@ namespace BTCPayServer.Controllers
|
||||
return View(nameof(LightningChargeServices), vm);
|
||||
}
|
||||
|
||||
private IActionResult LndServices(ExternalService service, ExternalConnectionString connectionString, uint? nonce)
|
||||
private IActionResult LndServices(ExternalService service, ExternalConnectionString connectionString, uint? nonce, string view = nameof(LndServices))
|
||||
{
|
||||
var model = new LndServicesViewModel();
|
||||
if (service.Type == ExternalServiceTypes.LNDGRPC)
|
||||
@ -673,7 +686,7 @@ namespace BTCPayServer.Controllers
|
||||
model.ConnectionType = "GRPC";
|
||||
model.GRPCSSLCipherSuites = "ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256";
|
||||
}
|
||||
else if (service.Type == ExternalServiceTypes.LNDRest)
|
||||
else if (service.Type == ExternalServiceTypes.LNDRest || service.Type == ExternalServiceTypes.CLightningRest)
|
||||
{
|
||||
model.Uri = connectionString.Server.AbsoluteUri;
|
||||
model.ConnectionType = "REST";
|
||||
@ -702,7 +715,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
}
|
||||
|
||||
return View(nameof(LndServices), model);
|
||||
return View(view, model);
|
||||
}
|
||||
|
||||
private static uint GetConfigKey(string type, string serviceName, string cryptoCode, uint nonce)
|
||||
@ -754,10 +767,10 @@ namespace BTCPayServer.Controllers
|
||||
grpcConf.SSL = connectionString.Server.Scheme == "https";
|
||||
confs.Configurations.Add(grpcConf);
|
||||
}
|
||||
else if (service.Type == ExternalServiceTypes.LNDRest)
|
||||
else if (service.Type == ExternalServiceTypes.LNDRest || service.Type == ExternalServiceTypes.CLightningRest)
|
||||
{
|
||||
var restconf = new LNDRestConfiguration();
|
||||
restconf.Type = "lnd-rest";
|
||||
restconf.Type = service.Type == ExternalServiceTypes.LNDRest? "lnd-rest": "clightning-rest";
|
||||
restconf.Uri = connectionString.Server.AbsoluteUri;
|
||||
confs.Configurations.Add(restconf);
|
||||
}
|
||||
@ -778,7 +791,6 @@ namespace BTCPayServer.Controllers
|
||||
return RedirectToAction(nameof(Service), new { cryptoCode = cryptoCode, serviceName = serviceName, nonce = nonce });
|
||||
}
|
||||
|
||||
|
||||
[Route("server/services/dynamic-dns")]
|
||||
public async Task<IActionResult> DynamicDnsServices()
|
||||
{
|
||||
|
@ -499,13 +499,16 @@ namespace BTCPayServer.Controllers
|
||||
case BitcoinPaymentType _:
|
||||
var strategy = derivationByCryptoCode.TryGet(paymentMethodId.CryptoCode);
|
||||
var network = _NetworkProvider.GetNetwork<BTCPayNetwork>(paymentMethodId.CryptoCode);
|
||||
var value = strategy?.ToPrettyString() ?? string.Empty;
|
||||
|
||||
vm.DerivationSchemes.Add(new StoreViewModel.DerivationScheme()
|
||||
{
|
||||
Crypto = paymentMethodId.CryptoCode,
|
||||
WalletSupported = network.WalletSupported,
|
||||
Value = strategy?.ToPrettyString() ?? string.Empty,
|
||||
Value = value,
|
||||
WalletId = new WalletId(store.Id, paymentMethodId.CryptoCode),
|
||||
Enabled = !excludeFilters.Match(paymentMethodId) && strategy != null
|
||||
Enabled = !excludeFilters.Match(paymentMethodId) && strategy != null,
|
||||
Collapsed = network is ElementsBTCPayNetwork elementsBTCPayNetwork && elementsBTCPayNetwork.NetworkCryptoCode != elementsBTCPayNetwork.CryptoCode && string.IsNullOrEmpty(value)
|
||||
});
|
||||
break;
|
||||
case LightningPaymentType _:
|
||||
@ -670,7 +673,7 @@ namespace BTCPayServer.Controllers
|
||||
TempData[WellKnownTempData.ErrorMessage] = "Failure to revoke this token";
|
||||
else
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Token revoked";
|
||||
return RedirectToAction(nameof(ListTokens));
|
||||
return RedirectToAction(nameof(ListTokens), new { storeId = token.StoreId});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
|
@ -7,6 +7,7 @@ using BTCPayServer.ModelBinders;
|
||||
using BTCPayServer.Models.WalletViewModels;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using NBXplorer.Models;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
@ -19,7 +20,6 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var nbx = ExplorerClientProvider.GetExplorerClient(network);
|
||||
CreatePSBTRequest psbtRequest = new CreatePSBTRequest();
|
||||
|
||||
foreach (var transactionOutput in sendModel.Outputs)
|
||||
{
|
||||
var psbtDestination = new CreatePSBTDestination();
|
||||
@ -56,12 +56,15 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
|
||||
vm.CryptoCode = network.CryptoCode;
|
||||
vm.NBXSeedAvailable = await CanUseHotWallet() && !string.IsNullOrEmpty(await ExplorerClientProvider.GetExplorerClient(network)
|
||||
.GetMetadataAsync<string>(GetDerivationSchemeSettings(walletId).AccountDerivation,
|
||||
WellknownMetadataKeys.Mnemonic));
|
||||
if (await vm.GetPSBT(network.NBitcoinNetwork) is PSBT psbt)
|
||||
{
|
||||
vm.Decoded = psbt.ToString();
|
||||
vm.PSBT = psbt.ToBase64();
|
||||
}
|
||||
return View(vm ?? new WalletPSBTViewModel() { CryptoCode = walletId.CryptoCode });
|
||||
return View(nameof(WalletPSBT), vm ?? new WalletPSBTViewModel() { CryptoCode = walletId.CryptoCode });
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("{walletId}/psbt")]
|
||||
@ -93,7 +96,7 @@ namespace BTCPayServer.Controllers
|
||||
case "vault":
|
||||
return ViewVault(walletId, psbt);
|
||||
case "ledger":
|
||||
return ViewWalletSendLedger(psbt);
|
||||
return ViewWalletSendLedger(walletId, psbt);
|
||||
case "update":
|
||||
var derivationSchemeSettings = GetDerivationSchemeSettings(walletId);
|
||||
psbt = await UpdatePSBT(derivationSchemeSettings, psbt, network);
|
||||
@ -103,12 +106,25 @@ namespace BTCPayServer.Controllers
|
||||
return View(vm);
|
||||
}
|
||||
TempData[WellKnownTempData.SuccessMessage] = "PSBT updated!";
|
||||
return RedirectToWalletPSBT(walletId, psbt, vm.FileName);
|
||||
return RedirectToWalletPSBT(psbt, vm.FileName);
|
||||
case "seed":
|
||||
return SignWithSeed(walletId, psbt.ToBase64());
|
||||
case "nbx-seed":
|
||||
if (await CanUseHotWallet())
|
||||
{
|
||||
var derivationScheme = GetDerivationSchemeSettings(walletId);
|
||||
var extKey = await ExplorerClientProvider.GetExplorerClient(network)
|
||||
.GetMetadataAsync<string>(derivationScheme.AccountDerivation,
|
||||
WellknownMetadataKeys.MasterHDKey);
|
||||
|
||||
return await SignWithSeed(walletId,
|
||||
new SignWithSeedViewModel() {SeedOrKey = extKey, PSBT = psbt.ToBase64()});
|
||||
}
|
||||
|
||||
return View(vm);
|
||||
case "broadcast":
|
||||
{
|
||||
return await WalletPSBTReady(walletId, psbt.ToBase64());
|
||||
return RedirectToWalletPSBTReady(psbt.ToBase64());
|
||||
}
|
||||
case "combine":
|
||||
ModelState.Remove(nameof(vm.PSBT));
|
||||
@ -145,7 +161,6 @@ namespace BTCPayServer.Controllers
|
||||
var vm = new WalletPSBTReadyViewModel() { PSBT = psbt };
|
||||
vm.SigningKey = signingKey;
|
||||
vm.SigningKeyPath = signingKeyPath;
|
||||
|
||||
var derivationSchemeSettings = GetDerivationSchemeSettings(walletId);
|
||||
if (derivationSchemeSettings == null)
|
||||
return NotFound();
|
||||
@ -207,7 +222,7 @@ namespace BTCPayServer.Controllers
|
||||
vm.CanCalculateBalance = true;
|
||||
vm.Positive = balanceChange >= Money.Zero;
|
||||
}
|
||||
|
||||
vm.Inputs = new List<WalletPSBTReadyViewModel.InputViewModel>();
|
||||
foreach (var input in psbtObject.Inputs)
|
||||
{
|
||||
var inputVm = new WalletPSBTReadyViewModel.InputViewModel();
|
||||
@ -220,7 +235,7 @@ namespace BTCPayServer.Controllers
|
||||
inputVm.Positive = balanceChange2 >= Money.Zero;
|
||||
inputVm.Index = (int)input.Index;
|
||||
}
|
||||
|
||||
vm.Destinations = new List<WalletPSBTReadyViewModel.DestinationViewModel>();
|
||||
foreach (var output in psbtObject.Outputs)
|
||||
{
|
||||
var dest = new WalletPSBTReadyViewModel.DestinationViewModel();
|
||||
@ -280,14 +295,14 @@ namespace BTCPayServer.Controllers
|
||||
catch
|
||||
{
|
||||
vm.GlobalError = "Invalid PSBT";
|
||||
return View(vm);
|
||||
return View(nameof(WalletPSBTReady),vm);
|
||||
}
|
||||
if (command == "broadcast")
|
||||
{
|
||||
if (!psbt.IsAllFinalized() && !psbt.TryFinalize(out var errors))
|
||||
{
|
||||
vm.SetErrors(errors);
|
||||
return View(vm);
|
||||
return View(nameof(WalletPSBTReady),vm);
|
||||
}
|
||||
var transaction = psbt.ExtractTransaction();
|
||||
try
|
||||
@ -296,24 +311,24 @@ namespace BTCPayServer.Controllers
|
||||
if (!broadcastResult.Success)
|
||||
{
|
||||
vm.GlobalError = $"RPC Error while broadcasting: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}";
|
||||
return View(vm);
|
||||
return View(nameof(WalletPSBTReady),vm);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
vm.GlobalError = "Error while broadcasting: " + ex.Message;
|
||||
return View(vm);
|
||||
return View(nameof(WalletPSBTReady),vm);
|
||||
}
|
||||
return RedirectToWalletTransaction(walletId, transaction);
|
||||
}
|
||||
else if (command == "analyze-psbt")
|
||||
{
|
||||
return RedirectToWalletPSBT(walletId, psbt);
|
||||
return RedirectToWalletPSBT(psbt);
|
||||
}
|
||||
else
|
||||
{
|
||||
vm.GlobalError = "Unknown command";
|
||||
return View(vm);
|
||||
return View(nameof(WalletPSBTReady),vm);
|
||||
}
|
||||
}
|
||||
|
||||
@ -342,7 +357,7 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
sourcePSBT = sourcePSBT.Combine(psbt);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "PSBT Successfully combined!";
|
||||
return RedirectToWalletPSBT(walletId, sourcePSBT);
|
||||
return RedirectToWalletPSBT(sourcePSBT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net.WebSockets;
|
||||
@ -8,7 +7,6 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.ModelBinders;
|
||||
using BTCPayServer.Models;
|
||||
@ -19,20 +17,15 @@ using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using LedgerWallet;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBXplorer;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
using static BTCPayServer.Controllers.StoresController;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -54,6 +47,7 @@ namespace BTCPayServer.Controllers
|
||||
private readonly BTCPayWalletProvider _walletProvider;
|
||||
private readonly WalletReceiveStateService _WalletReceiveStateService;
|
||||
private readonly EventAggregator _EventAggregator;
|
||||
private readonly SettingsRepository _settingsRepository;
|
||||
public RateFetcher RateFetcher { get; }
|
||||
|
||||
CurrencyNameTable _currencyTable;
|
||||
@ -70,7 +64,8 @@ namespace BTCPayServer.Controllers
|
||||
IFeeProviderFactory feeRateProvider,
|
||||
BTCPayWalletProvider walletProvider,
|
||||
WalletReceiveStateService walletReceiveStateService,
|
||||
EventAggregator eventAggregator)
|
||||
EventAggregator eventAggregator,
|
||||
SettingsRepository settingsRepository)
|
||||
{
|
||||
_currencyTable = currencyTable;
|
||||
Repository = repo;
|
||||
@ -86,6 +81,7 @@ namespace BTCPayServer.Controllers
|
||||
_walletProvider = walletProvider;
|
||||
_WalletReceiveStateService = walletReceiveStateService;
|
||||
_EventAggregator = eventAggregator;
|
||||
_settingsRepository = settingsRepository;
|
||||
}
|
||||
|
||||
// Borrowed from https://github.com/ManageIQ/guides/blob/master/labels.md
|
||||
@ -368,6 +364,15 @@ namespace BTCPayServer.Controllers
|
||||
return RedirectToAction(nameof(WalletReceive), new {walletId});
|
||||
}
|
||||
|
||||
private async Task<bool> CanUseHotWallet()
|
||||
{
|
||||
var isAdmin = (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings.Key)).Succeeded;
|
||||
if (isAdmin)
|
||||
return true;
|
||||
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>();
|
||||
return policies?.AllowHotWalletForAll is true;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{walletId}/send")]
|
||||
public async Task<IActionResult> WalletSend(
|
||||
@ -405,6 +410,9 @@ namespace BTCPayServer.Controllers
|
||||
var feeProvider = _feeRateProvider.CreateFeeProvider(network);
|
||||
var recommendedFees = feeProvider.GetFeeRateAsync();
|
||||
var balance = _walletProvider.GetWallet(network).GetBalance(paymentMethod.AccountDerivation);
|
||||
model.NBXSeedAvailable = await CanUseHotWallet() && !string.IsNullOrEmpty(await ExplorerClientProvider.GetExplorerClient(network)
|
||||
.GetMetadataAsync<string>(GetDerivationSchemeSettings(walletId).AccountDerivation,
|
||||
WellknownMetadataKeys.MasterHDKey));
|
||||
model.CurrentBalance = await balance;
|
||||
model.RecommendedSatoshiPerByte = (int)(await recommendedFees).GetFee(1).Satoshi;
|
||||
model.FeeSatoshiPerByte = model.RecommendedSatoshiPerByte;
|
||||
@ -430,12 +438,13 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
return View(model);
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[Route("{walletId}/send")]
|
||||
public async Task<IActionResult> WalletSend(
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, WalletSendModel vm, string command = "", CancellationToken cancellation = default)
|
||||
WalletId walletId, WalletSendModel vm, string command = "", CancellationToken cancellation = default, string bip21 = "")
|
||||
{
|
||||
if (walletId?.StoreId == null)
|
||||
return NotFound();
|
||||
@ -446,6 +455,13 @@ namespace BTCPayServer.Controllers
|
||||
if (network == null || network.ReadonlyWallet)
|
||||
return NotFound();
|
||||
vm.SupportRBF = network.SupportRBF;
|
||||
|
||||
if (!string.IsNullOrEmpty(bip21))
|
||||
{
|
||||
LoadFromBIP21(vm, bip21, network);
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
decimal transactionAmountSum = 0;
|
||||
|
||||
if (command == "add-output")
|
||||
@ -461,7 +477,6 @@ namespace BTCPayServer.Controllers
|
||||
vm.Outputs.RemoveAt(index);
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
|
||||
if (!vm.Outputs.Any())
|
||||
{
|
||||
@ -554,20 +569,70 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
case "vault":
|
||||
return ViewVault(walletId, psbt.PSBT);
|
||||
case "nbx-seed":
|
||||
var extKey = await ExplorerClientProvider.GetExplorerClient(network)
|
||||
.GetMetadataAsync<string>(derivationScheme.AccountDerivation, WellknownMetadataKeys.MasterHDKey, cancellation);
|
||||
|
||||
return await SignWithSeed(walletId, new SignWithSeedViewModel()
|
||||
{
|
||||
SeedOrKey = extKey,
|
||||
PSBT = psbt.PSBT.ToBase64()
|
||||
});
|
||||
case "ledger":
|
||||
return ViewWalletSendLedger(psbt.PSBT, psbt.ChangeAddress);
|
||||
return ViewWalletSendLedger(walletId, psbt.PSBT, psbt.ChangeAddress);
|
||||
case "seed":
|
||||
return SignWithSeed(walletId, psbt.PSBT.ToBase64());
|
||||
case "analyze-psbt":
|
||||
var name =
|
||||
$"Send-{string.Join('_', vm.Outputs.Select(output => $"{output.Amount}->{output.DestinationAddress}{(output.SubtractFeesFromOutput ? "-Fees" : string.Empty)}"))}.psbt";
|
||||
return RedirectToWalletPSBT(walletId, psbt.PSBT, name);
|
||||
return RedirectToWalletPSBT(psbt.PSBT, name);
|
||||
default:
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void LoadFromBIP21(WalletSendModel vm, string bip21, BTCPayNetwork network)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (bip21.StartsWith(network.UriScheme, StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
bip21 = $"bitcoin{bip21.Substring(network.UriScheme.Length)}";
|
||||
}
|
||||
|
||||
var uriBuilder = new NBitcoin.Payment.BitcoinUrlBuilder(bip21, network.NBitcoinNetwork);
|
||||
vm.Outputs = new List<WalletSendModel.TransactionOutput>()
|
||||
{
|
||||
new WalletSendModel.TransactionOutput()
|
||||
{
|
||||
Amount = uriBuilder.Amount.ToDecimal(MoneyUnit.BTC),
|
||||
DestinationAddress = uriBuilder.Address.ToString(),
|
||||
SubtractFeesFromOutput = false
|
||||
}
|
||||
};
|
||||
if (!string.IsNullOrEmpty(uriBuilder.Label) || !string.IsNullOrEmpty(uriBuilder.Message))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Info,
|
||||
Html =
|
||||
$"Payment {(string.IsNullOrEmpty(uriBuilder.Label) ? string.Empty : $" to {uriBuilder.Label}")} {(string.IsNullOrEmpty(uriBuilder.Message) ? string.Empty : $" for {uriBuilder.Message}")}"
|
||||
});
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = "The provided BIP21 payment URI was malformed"
|
||||
});
|
||||
}
|
||||
|
||||
ModelState.Clear();
|
||||
}
|
||||
|
||||
private IActionResult ViewVault(WalletId walletId, PSBT psbt)
|
||||
{
|
||||
return View("WalletSendVault", new WalletSendVaultModel()
|
||||
@ -578,7 +643,31 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
}
|
||||
|
||||
private IActionResult RedirectToWalletPSBT(WalletId walletId, PSBT psbt, string fileName = null)
|
||||
[HttpPost]
|
||||
[Route("{walletId}/vault")]
|
||||
public async Task<IActionResult> SubmitVault([ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, WalletSendVaultModel model)
|
||||
{
|
||||
|
||||
return RedirectToWalletPSBTReady(model.PSBT);
|
||||
}
|
||||
private IActionResult RedirectToWalletPSBTReady(string psbt, string signingKey= null, string signingKeyPath = null)
|
||||
{
|
||||
var vm = new PostRedirectViewModel()
|
||||
{
|
||||
AspController = "Wallets",
|
||||
AspAction = nameof(WalletPSBTReady),
|
||||
Parameters =
|
||||
{
|
||||
new KeyValuePair<string, string>("psbt", psbt),
|
||||
new KeyValuePair<string, string>("SigningKey", signingKey),
|
||||
new KeyValuePair<string, string>("SigningKeyPath", signingKeyPath)
|
||||
}
|
||||
};
|
||||
return View("PostRedirect", vm);
|
||||
}
|
||||
|
||||
private IActionResult RedirectToWalletPSBT(PSBT psbt, string fileName = null)
|
||||
{
|
||||
var vm = new PostRedirectViewModel()
|
||||
{
|
||||
@ -616,17 +705,25 @@ namespace BTCPayServer.Controllers
|
||||
return null;
|
||||
}
|
||||
|
||||
private ViewResult ViewWalletSendLedger(PSBT psbt, BitcoinAddress hintChange = null)
|
||||
private ViewResult ViewWalletSendLedger(WalletId walletId, PSBT psbt, BitcoinAddress hintChange = null)
|
||||
{
|
||||
SetAmbientPSBT(psbt);
|
||||
return View("WalletSendLedger", new WalletSendLedgerModel()
|
||||
{
|
||||
PSBT = psbt.ToBase64(),
|
||||
HintChange = hintChange?.ToString(),
|
||||
WebsocketPath = this.Url.Action(nameof(LedgerConnection))
|
||||
WebsocketPath = this.Url.Action(nameof(LedgerConnection), new { walletId = walletId.ToString() })
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{walletId}/ledger")]
|
||||
public async Task<IActionResult> SubmitLedger([ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, WalletSendLedgerModel model)
|
||||
{
|
||||
return RedirectToWalletPSBTReady(model.PSBT);
|
||||
}
|
||||
|
||||
[HttpGet("{walletId}/psbt/seed")]
|
||||
public IActionResult SignWithSeed([ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId,string psbt)
|
||||
@ -643,7 +740,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(viewModel);
|
||||
return View("SignWithSeed", viewModel);
|
||||
}
|
||||
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
|
||||
if (network == null)
|
||||
@ -666,7 +763,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(viewModel);
|
||||
return View("SignWithSeed", viewModel);
|
||||
}
|
||||
|
||||
ExtKey signingKey = null;
|
||||
@ -679,7 +776,7 @@ namespace BTCPayServer.Controllers
|
||||
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);
|
||||
return View("SignWithSeed", 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())
|
||||
@ -700,7 +797,7 @@ namespace BTCPayServer.Controllers
|
||||
return View(viewModel);
|
||||
}
|
||||
ModelState.Remove(nameof(viewModel.PSBT));
|
||||
return await WalletPSBTReady(walletId, psbt.ToBase64(), signingKey.GetWif(network.NBitcoinNetwork).ToString(), rootedKeyPath?.ToString());
|
||||
return RedirectToWalletPSBTReady(psbt.ToBase64(), signingKey.GetWif(network.NBitcoinNetwork).ToString(), rootedKeyPath?.ToString());
|
||||
}
|
||||
|
||||
private bool PSBTChanged(PSBT psbt, Action act)
|
||||
|
@ -115,20 +115,24 @@ namespace BTCPayServer.HostedServices
|
||||
|
||||
private async Task TryLoadRateCache()
|
||||
{
|
||||
var cache = await _SettingsRepository.GetSettingAsync<ExchangeRatesCache>();
|
||||
if (cache != null)
|
||||
try
|
||||
{
|
||||
_LastCacheDate = cache.Created;
|
||||
var stateByExchange = cache.States.ToDictionary(o => o.ExchangeName);
|
||||
foreach (var provider in _RateProviderFactory.Providers)
|
||||
var cache = await _SettingsRepository.GetSettingAsync<ExchangeRatesCache>();
|
||||
if (cache != null)
|
||||
{
|
||||
if (stateByExchange.TryGetValue(provider.Key, out var state) &&
|
||||
provider.Value is BackgroundFetcherRateProvider fetcher)
|
||||
_LastCacheDate = cache.Created;
|
||||
var stateByExchange = cache.States.ToDictionary(o => o.ExchangeName);
|
||||
foreach (var provider in _RateProviderFactory.Providers)
|
||||
{
|
||||
fetcher.LoadState(state);
|
||||
if (stateByExchange.TryGetValue(provider.Key, out var state) &&
|
||||
provider.Value is BackgroundFetcherRateProvider fetcher)
|
||||
{
|
||||
fetcher.LoadState(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
DateTimeOffset? _LastCacheDate;
|
||||
|
@ -62,10 +62,12 @@ namespace BTCPayServer.Hosting
|
||||
catch (UnauthorizedAccessException ex)
|
||||
{
|
||||
await HandleBitpayHttpException(httpContext, new BitpayHttpException(401, ex.Message));
|
||||
return;
|
||||
}
|
||||
catch (BitpayHttpException ex)
|
||||
{
|
||||
await HandleBitpayHttpException(httpContext, ex);
|
||||
return;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -133,13 +135,9 @@ namespace BTCPayServer.Hosting
|
||||
private static async Task HandleBitpayHttpException(HttpContext httpContext, BitpayHttpException ex)
|
||||
{
|
||||
httpContext.Response.StatusCode = ex.StatusCode;
|
||||
using (var writer = new StreamWriter(httpContext.Response.Body, new UTF8Encoding(false), 1024, true))
|
||||
{
|
||||
httpContext.Response.ContentType = "application/json";
|
||||
var result = JsonConvert.SerializeObject(new BitpayErrorsModel(ex));
|
||||
writer.Write(result);
|
||||
await writer.FlushAsync();
|
||||
}
|
||||
httpContext.Response.ContentType = "application/json";
|
||||
var result = JsonConvert.SerializeObject(new BitpayErrorsModel(ex));
|
||||
await httpContext.Response.WriteAsync(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using OpenIddict.Validation.AspNetCore;
|
||||
using OpenIddict.Abstractions;
|
||||
@ -49,6 +50,9 @@ namespace BTCPayServer.Hosting
|
||||
Logs.Configure(LoggerFactory);
|
||||
services.ConfigureBTCPayServer(Configuration);
|
||||
services.AddMemoryCache();
|
||||
services.AddDataProtection()
|
||||
.SetApplicationName("BTCPay Server")
|
||||
.PersistKeysToFileSystem(GetDataDir());
|
||||
services.AddIdentity<ApplicationUser, IdentityRole>()
|
||||
.AddEntityFrameworkStores<ApplicationDbContext>()
|
||||
.AddDefaultTokenProviders();
|
||||
@ -140,6 +144,11 @@ namespace BTCPayServer.Hosting
|
||||
}
|
||||
}
|
||||
|
||||
private DirectoryInfo GetDataDir()
|
||||
{
|
||||
return new DirectoryInfo(Configuration.GetDataDir(DefaultConfiguration.GetNetworkType(Configuration)));
|
||||
}
|
||||
|
||||
private void ConfigureOpenIddict(IServiceCollection services)
|
||||
{
|
||||
// Register the OpenIddict services.
|
||||
@ -232,6 +241,10 @@ namespace BTCPayServer.Hosting
|
||||
forwardingOptions.KnownProxies.Clear();
|
||||
forwardingOptions.ForwardedHeaders = ForwardedHeaders.All;
|
||||
app.UseForwardedHeaders(forwardingOptions);
|
||||
|
||||
|
||||
app.UseStatusCodePagesWithReExecute("/Error/Handle", "?statusCode={0}");
|
||||
|
||||
app.UsePayServer();
|
||||
app.UseRouting();
|
||||
app.UseCors();
|
||||
@ -243,7 +256,6 @@ namespace BTCPayServer.Hosting
|
||||
app.UseSession();
|
||||
|
||||
app.UseWebSockets();
|
||||
app.UseStatusCodePages();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
|
@ -10,16 +10,20 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
public class UpdateCrowdfundViewModel
|
||||
{
|
||||
public string StoreId { get; set; }
|
||||
[Required] [MaxLength(30)] public string Title { get; set; }
|
||||
[Required]
|
||||
[MaxLength(30)]
|
||||
public string Title { get; set; }
|
||||
|
||||
[MaxLength(50)] public string Tagline { get; set; }
|
||||
[MaxLength(50)]
|
||||
public string Tagline { get; set; }
|
||||
|
||||
[Required]
|
||||
public string Description { get; set; }
|
||||
|
||||
[Required] public string Description { get; set; }
|
||||
|
||||
[Display(Name = "Featured Image")]
|
||||
public string MainImageUrl { get; set; }
|
||||
|
||||
[Display(Name = "Callback Notification Url")]
|
||||
|
||||
[Display(Name = "Callback Notification Url")]
|
||||
[Uri]
|
||||
public string NotificationUrl { get; set; }
|
||||
[Display(Name = "Invoice IPN Notification")]
|
||||
@ -59,7 +63,8 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
|
||||
public IEnumerable<string> ResetEveryValues = Enum.GetNames(typeof(CrowdfundResetEvery));
|
||||
|
||||
[Display(Name = "Reset goal every")] public string ResetEvery { get; set; } = nameof(CrowdfundResetEvery.Never);
|
||||
[Display(Name = "Reset goal every")]
|
||||
public string ResetEvery { get; set; } = nameof(CrowdfundResetEvery.Never);
|
||||
|
||||
public int ResetEveryAmount { get; set; } = 1;
|
||||
|
||||
@ -78,7 +83,7 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
public string EmbeddedCSS { get; set; }
|
||||
|
||||
[Display(Name = "Count all invoices created on the store as part of the crowdfunding goal")]
|
||||
public bool UseAllStoreInvoices { get; set; }
|
||||
public bool UseAllStoreInvoices { get; set; }
|
||||
|
||||
public string AppId { get; set; }
|
||||
public string SearchTerm { get; set; }
|
||||
@ -90,10 +95,18 @@ namespace BTCPayServer.Models.AppViewModels
|
||||
|
||||
|
||||
[Display(Name = "Sounds to play when a payment is made. One sound per line")]
|
||||
public string Sounds{ get; set; }
|
||||
public string Sounds { get; set; }
|
||||
[Display(Name = "Colors to rotate between with animation when a payment is made. First color is the default background. One color per line. Can be any valid css color value.")]
|
||||
public string AnimationColors{ get; set; }
|
||||
public string AnimationColors { get; set; }
|
||||
|
||||
public bool NotificationEmailWarning { get; set; }
|
||||
|
||||
|
||||
// NOTE: Improve validation if needed
|
||||
public bool ModelWithMinimumData
|
||||
{
|
||||
get { return Description != null && Title != null && TargetCurrency != null; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
{
|
||||
Currency = "USD";
|
||||
}
|
||||
|
||||
[Required]
|
||||
public decimal? Amount
|
||||
{
|
||||
@ -28,11 +29,13 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
}
|
||||
|
||||
[Required]
|
||||
[DisplayName("Store Id")]
|
||||
public string StoreId
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
|
||||
[DisplayName("Order Id")]
|
||||
public string OrderId
|
||||
{
|
||||
get; set;
|
||||
@ -51,6 +54,7 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
}
|
||||
|
||||
[EmailAddress]
|
||||
[DisplayName("Buyer Email")]
|
||||
public string BuyerEmail
|
||||
{
|
||||
get; set;
|
||||
@ -72,22 +76,19 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
|
||||
public SelectList Stores
|
||||
{
|
||||
get;
|
||||
set;
|
||||
get; set;
|
||||
}
|
||||
|
||||
[DisplayName("Supported Transaction Currencies")]
|
||||
public List<string> SupportedTransactionCurrencies
|
||||
{
|
||||
get;
|
||||
set;
|
||||
get; set;
|
||||
}
|
||||
|
||||
[DisplayName("Available Payment Methods")]
|
||||
public SelectList AvailablePaymentMethods
|
||||
{
|
||||
get;
|
||||
set;
|
||||
get; set;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,6 @@ namespace BTCPayServer.Models.InvoicingModels
|
||||
public string PaymentMethod { get; set; }
|
||||
public string Due { get; set; }
|
||||
public string Paid { get; set; }
|
||||
public string Address { get; internal set; }
|
||||
public string Rate { get; internal set; }
|
||||
public string PaymentUrl { get; internal set; }
|
||||
public string Overpaid { get; set; }
|
||||
|
@ -15,26 +15,8 @@ namespace BTCPayServer.Models
|
||||
public StatusSeverity Severity { get; set; }
|
||||
public bool AllowDismiss { get; set; } = true;
|
||||
|
||||
public string SeverityCSS
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (Severity)
|
||||
{
|
||||
case StatusSeverity.Info:
|
||||
return "info";
|
||||
case StatusSeverity.Error:
|
||||
return "danger";
|
||||
case StatusSeverity.Success:
|
||||
return "success";
|
||||
case StatusSeverity.Warning:
|
||||
return "warning";
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string SeverityCSS => ToString(Severity);
|
||||
|
||||
private void ParseNonJsonStatus(string s)
|
||||
{
|
||||
Message = s;
|
||||
@ -43,6 +25,23 @@ namespace BTCPayServer.Models
|
||||
: StatusSeverity.Success;
|
||||
}
|
||||
|
||||
public static string ToString(StatusSeverity severity)
|
||||
{
|
||||
switch (severity)
|
||||
{
|
||||
case StatusSeverity.Info:
|
||||
return "info";
|
||||
case StatusSeverity.Error:
|
||||
return "danger";
|
||||
case StatusSeverity.Success:
|
||||
return "success";
|
||||
case StatusSeverity.Warning:
|
||||
return "warning";
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
public enum StatusSeverity
|
||||
{
|
||||
Info,
|
||||
|
@ -14,6 +14,7 @@ namespace BTCPayServer.Models.StoreViewModels
|
||||
public WalletId WalletId { get; set; }
|
||||
public bool WalletSupported { get; set; }
|
||||
public bool Enabled { get; set; }
|
||||
public bool Collapsed { get; set; }
|
||||
}
|
||||
|
||||
public class AdditionalPaymentMethod
|
||||
|
@ -13,6 +13,8 @@ namespace BTCPayServer.Models.WalletViewModels
|
||||
public string CryptoCode { get; set; }
|
||||
public string Decoded { get; set; }
|
||||
string _FileName;
|
||||
public bool NBXSeedAvailable { get; set; }
|
||||
|
||||
public string FileName
|
||||
{
|
||||
get
|
||||
|
@ -45,5 +45,7 @@ namespace BTCPayServer.Models.WalletViewModels
|
||||
public bool SupportRBF { get; set; }
|
||||
[Display(Name = "Disable RBF")]
|
||||
public bool DisableRBF { get; set; }
|
||||
|
||||
public bool NBXSeedAvailable { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +47,7 @@ namespace BTCPayServer.Payments.Lightning
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var test = GetNodeInfo(paymentMethod.PreferOnion, supportedPaymentMethod, (BTCPayNetwork)network);
|
||||
var invoice = paymentMethod.ParentEntity;
|
||||
var due = Extensions.RoundUp(invoice.ProductInformation.Price / paymentMethod.Rate, 8);
|
||||
var due = Extensions.RoundUp(invoice.ProductInformation.Price / paymentMethod.Rate, network.Divisibility);
|
||||
var client = _lightningClientFactory.Create(supportedPaymentMethod.GetLightningUrl(), (BTCPayNetwork)network);
|
||||
var expiry = invoice.ExpirationTime - DateTimeOffset.UtcNow;
|
||||
if (expiry < TimeSpan.Zero)
|
||||
@ -180,11 +180,15 @@ namespace BTCPayServer.Payments.Lightning
|
||||
model.LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi;
|
||||
if (storeBlob.LightningAmountInSatoshi && model.CryptoCode == "BTC" )
|
||||
{
|
||||
var satoshiCulture = new CultureInfo(CultureInfo.InvariantCulture.Name);
|
||||
satoshiCulture.NumberFormat.NumberGroupSeparator = " ";
|
||||
|
||||
model.CryptoCode = "Sats";
|
||||
model.BtcDue = Money.Parse(model.BtcDue).ToUnit(MoneyUnit.Satoshi).ToString(CultureInfo.InvariantCulture);
|
||||
model.BtcPaid = Money.Parse(model.BtcPaid).ToUnit(MoneyUnit.Satoshi).ToString(CultureInfo.InvariantCulture);
|
||||
model.BtcDue = Money.Parse(model.BtcDue).ToUnit(MoneyUnit.Satoshi).ToString("N0", satoshiCulture);
|
||||
model.BtcPaid = Money.Parse(model.BtcPaid).ToUnit(MoneyUnit.Satoshi).ToString("N0", satoshiCulture);
|
||||
model.OrderAmount = Money.Parse(model.OrderAmount).ToUnit(MoneyUnit.Satoshi).ToString("N0", satoshiCulture);
|
||||
|
||||
model.NetworkFee = new Money(model.NetworkFee, MoneyUnit.BTC).ToUnit(MoneyUnit.Satoshi);
|
||||
model.OrderAmount = Money.Parse(model.OrderAmount).ToUnit(MoneyUnit.Satoshi).ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
public override string GetCryptoImage(PaymentMethodId paymentMethodId)
|
||||
|
@ -64,7 +64,6 @@ namespace BTCPayServer.Payments
|
||||
public abstract string SerializePaymentData(BTCPayNetworkBase network, CryptoPaymentData paymentData);
|
||||
public abstract IPaymentMethodDetails DeserializePaymentMethodDetails(string str);
|
||||
public abstract ISupportedPaymentMethod DeserializeSupportedPaymentMethod(BTCPayNetworkBase network, JToken value);
|
||||
|
||||
public abstract string GetTransactionLink(BTCPayNetworkBase network, string txId);
|
||||
public abstract string InvoiceViewPaymentPartialName { get; }
|
||||
}
|
||||
|
@ -40,6 +40,7 @@
|
||||
"BTCPAY_BTCEXTERNALLNDSEEDBACKUP": "../BTCPayServer.Tests/TestData/LndSeedBackup/walletunlock.json",
|
||||
"BTCPAY_BTCEXTERNALSPARK": "server=/spark/btc/;cookiefile=fake",
|
||||
"BTCPAY_BTCEXTERNALCHARGE": "server=https://127.0.0.1:53280/mycharge/btc/;cookiefilepath=fake",
|
||||
"BTCPAY_EXTERNALCONFIGURATOR": "passwordfile=testpwd;server=/configurator",
|
||||
"BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/",
|
||||
"BTCPAY_ALLOW-ADMIN-REGISTRATION": "true",
|
||||
"BTCPAY_DISABLE-REGISTRATION": "false",
|
||||
|
@ -847,7 +847,7 @@ namespace BTCPayServer.Services.Invoices
|
||||
var paid = 0m;
|
||||
var cryptoPaid = 0.0m;
|
||||
|
||||
int precision = 8;
|
||||
int precision = Network?.Divisibility ?? 8;
|
||||
var totalDueNoNetworkCost = Money.Coins(Extensions.RoundUp(totalDue, precision));
|
||||
bool paidEnough = paid >= Extensions.RoundUp(totalDue, precision);
|
||||
int txRequired = 0;
|
||||
@ -857,8 +857,8 @@ namespace BTCPayServer.Services.Invoices
|
||||
.OrderBy(p => p.ReceivedTime)
|
||||
.Select(_ =>
|
||||
{
|
||||
var txFee = _.GetValue(paymentMethods, GetId(), _.NetworkFee);
|
||||
paid += _.GetValue(paymentMethods, GetId());
|
||||
var txFee = _.GetValue(paymentMethods, GetId(), _.NetworkFee, precision);
|
||||
paid += _.GetValue(paymentMethods, GetId(), null, precision);
|
||||
if (!paidEnough)
|
||||
{
|
||||
totalDue += txFee;
|
||||
@ -991,18 +991,18 @@ namespace BTCPayServer.Services.Invoices
|
||||
#pragma warning restore CS0618
|
||||
return this;
|
||||
}
|
||||
internal decimal GetValue(PaymentMethodDictionary paymentMethods, PaymentMethodId paymentMethodId, decimal? value = null)
|
||||
internal decimal GetValue(PaymentMethodDictionary paymentMethods, PaymentMethodId paymentMethodId, decimal? value, int precision)
|
||||
{
|
||||
|
||||
value = value ?? this.GetCryptoPaymentData().GetValue();
|
||||
var to = paymentMethodId;
|
||||
var from = this.GetPaymentMethodId();
|
||||
if (to == from)
|
||||
return decimal.Round(value.Value, 8);
|
||||
return decimal.Round(value.Value, precision);
|
||||
var fromRate = paymentMethods[from].Rate;
|
||||
var toRate = paymentMethods[to].Rate;
|
||||
|
||||
var fiatValue = fromRate * decimal.Round(value.Value, 8);
|
||||
var fiatValue = fromRate * decimal.Round(value.Value, precision);
|
||||
var otherCurrencyValue = toRate == 0 ? 0.0m : fiatValue / toRate;
|
||||
return otherCurrencyValue;
|
||||
}
|
||||
|
@ -96,8 +96,14 @@ namespace BTCPayServer.Services.Rates
|
||||
|
||||
foreach (var network in new BTCPayNetworkProvider(NetworkType.Mainnet).GetAll())
|
||||
{
|
||||
AddCurrency(_CurrencyProviders, network.CryptoCode, 8, network.CryptoCode);
|
||||
AddCurrency(_CurrencyProviders, network.CryptoCode, network.Divisibility, network.CryptoCode);
|
||||
}
|
||||
|
||||
_CurrencyProviders.TryAdd("SATS",
|
||||
new NumberFormatInfo()
|
||||
{
|
||||
CurrencySymbol = "sats", CurrencyDecimalDigits = 0, CurrencyPositivePattern = 3
|
||||
});
|
||||
}
|
||||
return _CurrencyProviders.TryGet(currency.ToUpperInvariant());
|
||||
}
|
||||
@ -180,7 +186,7 @@ namespace BTCPayServer.Services.Rates
|
||||
if (!dico.TryAdd(network.CryptoCode, new CurrencyData()
|
||||
{
|
||||
Code = network.CryptoCode,
|
||||
Divisibility = 8,
|
||||
Divisibility = network.Divisibility,
|
||||
Name = network.CryptoCode,
|
||||
Crypto = true
|
||||
}))
|
||||
@ -189,6 +195,15 @@ namespace BTCPayServer.Services.Rates
|
||||
}
|
||||
}
|
||||
|
||||
dico.TryAdd("SATS", new CurrencyData()
|
||||
{
|
||||
Code = "SATS",
|
||||
Crypto = true,
|
||||
Divisibility = 0,
|
||||
Name = "Satoshis",
|
||||
Symbol = "Sats",
|
||||
});
|
||||
|
||||
return dico.Values.ToArray();
|
||||
}
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
@inject BTCPayServer.HostedServices.CssThemeManager themeManager
|
||||
@{
|
||||
ViewData["Title"] = "Log in";
|
||||
Layout = "_WelcomeLayout.cshtml";
|
||||
Layout = "_LayoutWelcome";
|
||||
}
|
||||
@if (TempData.HasStatusMessage())
|
||||
{
|
||||
|
@ -2,7 +2,7 @@
|
||||
@{
|
||||
ViewData["Title"] = "Register";
|
||||
var useBasicLayout = ViewData["UseBasicLayout"] is true;
|
||||
Layout = useBasicLayout ? "../Shared/_Layout.cshtml" : "_WelcomeLayout.cshtml";
|
||||
Layout = useBasicLayout ? "_Layout" : "_LayoutWelcome";
|
||||
}
|
||||
@if (TempData.HasStatusMessage())
|
||||
{
|
||||
|
@ -56,9 +56,13 @@
|
||||
<td style="text-align:right">
|
||||
@if (app.IsOwner)
|
||||
{
|
||||
<a asp-action="@app.UpdateAction" asp-controller="Apps" asp-route-appId="@app.Id">Settings</a><span> - </span>
|
||||
<a asp-action="@app.UpdateAction" asp-controller="Apps" asp-route-appId="@app.Id">Settings</a>
|
||||
<span> - </span>
|
||||
}
|
||||
<a asp-action="@app.ViewAction" asp-controller="AppsPublic" asp-route-appId="@app.Id">View</a><span> - </span>
|
||||
<a asp-action="@app.ViewAction" asp-controller="AppsPublic" asp-route-appId="@app.Id">View</a>
|
||||
<a asp-action="@app.ViewAction" asp-controller="AppsPublic" asp-route-appId="@app.Id" target="_blank"
|
||||
title="View in New Window"><span class="fa fa-external-link"></span></a>
|
||||
<span> - </span>
|
||||
<a asp-action="DeleteApp" asp-route-appId="@app.Id">Remove</a>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -215,13 +215,21 @@
|
||||
</div>
|
||||
<input type="hidden" asp-for="NotificationEmailWarning" />
|
||||
<div class="form-group">
|
||||
<button name="command" type="submit" class="btn btn-primary" value="save" id="SaveSettings">Save settings</button>
|
||||
<a class="btn btn-secondary" target="_blank" asp-action="ListInvoices" asp-controller="Invoice" asp-route-searchterm="@Model.SearchTerm">Invoices</a>
|
||||
@if ((Model.Description != null) && (Model.Title != null) && (Model.TargetCurrency != null))
|
||||
<button type="submit" class="btn btn-primary" id="SaveSettings">Save Settings</button>
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-outline-primary" asp-action="ListInvoices" asp-controller="Invoice" asp-route-searchterm="@Model.SearchTerm">Invoices</a>
|
||||
<a class="btn btn-outline-primary" asp-action="ListInvoices" asp-controller="Invoice" asp-route-searchterm="@Model.SearchTerm"
|
||||
target="viewinvoices_@Model.AppId"><span class="fa fa-external-link"></span></a>
|
||||
</div>
|
||||
@if (Model.ModelWithMinimumData)
|
||||
{
|
||||
<button name="command" type="submit" value="viewapp" class="btn btn-secondary" id="ViewApp">View App</button>
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-outline-primary" asp-action="ViewCrowdfund" asp-controller="AppsPublic" asp-route-appId="@Model.AppId" id="ViewApp">View App</a>
|
||||
<a class="btn btn-outline-primary" asp-action="ViewCrowdfund" asp-controller="AppsPublic" asp-route-appId="@Model.AppId"
|
||||
target="viewapp_@Model.AppId"><span class="fa fa-external-link"></span></a>
|
||||
</div>
|
||||
}
|
||||
<a class="btn btn-secondary" target="_blank" asp-action="ListApps">Back to the app list</a>
|
||||
<a class="btn btn-outline-primary" target="_blank" asp-action="ListApps">Back to the app list</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@ -255,13 +263,12 @@
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<label>Price</label>*
|
||||
<input
|
||||
class="js-product-price form-control mb-2"
|
||||
inputmode="numeric"
|
||||
pattern="\d*"
|
||||
step="any"
|
||||
type="number"
|
||||
value="{price}" />
|
||||
<input class="js-product-price form-control mb-2"
|
||||
inputmode="numeric"
|
||||
pattern="\d*"
|
||||
step="any"
|
||||
type="number"
|
||||
value="{price}" />
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<label>Custom price</label>
|
||||
@ -285,7 +292,7 @@
|
||||
<div class="form-row">
|
||||
<div class="col">
|
||||
<label>Inventory (leave blank to not use inventory feature)</label>
|
||||
<input type="number" step="1" class="js-product-inventory form-control mb-2" value="{inventory}"/>
|
||||
<input type="number" step="1" class="js-product-inventory form-control mb-2" value="{inventory}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -145,10 +145,18 @@
|
||||
</div>
|
||||
<input type="hidden" asp-for="NotificationEmailWarning" />
|
||||
<div class="form-group">
|
||||
<input type="submit" class="btn btn-primary" value="Save Settings" id="SaveSettings" />
|
||||
<a class="btn btn-secondary" target="_blank" asp-action="ListInvoices" asp-controller="Invoice" asp-route-searchterm="@Model.SearchTerm">Invoices</a>
|
||||
<a class="btn btn-secondary" target="_blank" asp-action="ViewPointOfSale" asp-controller="AppsPublic" asp-route-appId="@Model.Id" id="ViewApp">View App</a>
|
||||
<a class="btn btn-secondary" target="_blank" asp-action="ListApps">Back to the app list</a>
|
||||
<button type="submit" class="btn btn-primary" id="SaveSettings">Save Settings</button>
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-outline-primary" asp-action="ListInvoices" asp-controller="Invoice" asp-route-searchterm="@Model.SearchTerm">Invoices</a>
|
||||
<a class="btn btn-outline-primary" asp-action="ListInvoices" asp-controller="Invoice" asp-route-searchterm="@Model.SearchTerm"
|
||||
target="viewinvoices_@Model.Id"><span class="fa fa-external-link"></span></a>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-outline-primary" asp-action="ViewPointOfSale" asp-controller="AppsPublic" asp-route-appId="@Model.Id" id="ViewApp">View App</a>
|
||||
<a class="btn btn-outline-primary" asp-action="ViewPointOfSale" asp-controller="AppsPublic" asp-route-appId="@Model.Id"
|
||||
target="viewapp_@Model.Id"><span class="fa fa-external-link"></span></a>
|
||||
</div>
|
||||
<a class="btn btn-outline-primary" target="_blank" asp-action="ListApps">Back to the app list</a>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="accordion" id="accordian-dev-info">
|
||||
@ -253,12 +261,11 @@
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<label>Price</label>*
|
||||
<input
|
||||
class="js-product-price form-control mb-2"
|
||||
inputmode="numeric"
|
||||
pattern="\d*"
|
||||
type="number"
|
||||
value="{price}" />
|
||||
<input class="js-product-price form-control mb-2"
|
||||
inputmode="numeric"
|
||||
pattern="\d*"
|
||||
type="number"
|
||||
value="{price}" />
|
||||
</div>
|
||||
<div class="col-sm-3">
|
||||
<label>Custom price</label>
|
||||
@ -282,7 +289,7 @@
|
||||
<div class="form-row">
|
||||
<div class="col">
|
||||
<label>Inventory (leave blank to not use inventory feature)</label>
|
||||
<input type="number" step="1" class="js-product-inventory form-control mb-2" value="{inventory}"/>
|
||||
<input type="number" step="1" class="js-product-inventory form-control mb-2" value="{inventory}" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -33,27 +33,27 @@
|
||||
<bundle name="wwwroot/bundles/crowdfund-bundle.min.css"></bundle>
|
||||
@if (!string.IsNullOrEmpty(Model.EmbeddedCSS))
|
||||
{
|
||||
@Safe.Raw($"<style>{Model.EmbeddedCSS}</style>");
|
||||
@Safe.Raw($"<style>{Model.EmbeddedCSS}</style>");
|
||||
}
|
||||
|
||||
</head>
|
||||
<body>
|
||||
@if (Context.Request.Query.ContainsKey("simple"))
|
||||
{
|
||||
@await Html.PartialAsync("/Views/AppsPublic/Crowdfund/MinimalCrowdfund.cshtml", Model)
|
||||
}
|
||||
else
|
||||
{
|
||||
<noscript>
|
||||
@await Html.PartialAsync("/Views/AppsPublic/Crowdfund/MinimalCrowdfund.cshtml", Model)
|
||||
</noscript>
|
||||
|
||||
if (Model.AnimationsEnabled)
|
||||
@if (Context.Request.Query.ContainsKey("simple"))
|
||||
{
|
||||
<canvas id="fireworks"></canvas>
|
||||
@await Html.PartialAsync("/Views/AppsPublic/Crowdfund/MinimalCrowdfund.cshtml", Model)
|
||||
}
|
||||
else
|
||||
{
|
||||
<noscript>
|
||||
@await Html.PartialAsync("/Views/AppsPublic/Crowdfund/MinimalCrowdfund.cshtml", Model)
|
||||
</noscript>
|
||||
|
||||
if (Model.AnimationsEnabled)
|
||||
{
|
||||
<canvas id="fireworks"></canvas>
|
||||
}
|
||||
@await Html.PartialAsync("/Views/AppsPublic/Crowdfund/VueCrowdfund.cshtml", Model)
|
||||
}
|
||||
@await Html.PartialAsync("/Views/AppsPublic/Crowdfund/VueCrowdfund.cshtml", Model)
|
||||
}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
15
BTCPayServer/Views/Error/404.cshtml
Normal file
15
BTCPayServer/Views/Error/404.cshtml
Normal file
@ -0,0 +1,15 @@
|
||||
@{
|
||||
ViewData["ErrorTitle"] = "404 - Page not found";
|
||||
}
|
||||
|
||||
<p class="lead-login">
|
||||
This is like searching for a person more beautiful than <a href="https://twitter.com/NicolasDorier" target="_blank">Nicolas Dorier</a>.
|
||||
<br /><br />
|
||||
<a href="https://twitter.com/NicolasDorier" target="_blank">
|
||||
<img src="~/img/errorpages/404_nicolas.jpg" alt="Nicolas Dorier beauty" title="Slowly stroke the image" />
|
||||
</a>
|
||||
<br /><br />
|
||||
It doesn't exist.
|
||||
<br /><br />
|
||||
You can always try <a href="/">navigating back to home</a>.
|
||||
</p>
|
15
BTCPayServer/Views/Error/429.cshtml
Normal file
15
BTCPayServer/Views/Error/429.cshtml
Normal file
@ -0,0 +1,15 @@
|
||||
@{
|
||||
ViewData["ErrorTitle"] = "429 - Too Many Requests";
|
||||
}
|
||||
|
||||
<p class="lead-login">
|
||||
Please send requests slower. Or face the wrath of <a href="https://twitter.com/r0ckstardev" target="_blank">Vin Diesel</a>.
|
||||
<br /><br />
|
||||
<a href="https://twitter.com/r0ckstardev" target="_blank">
|
||||
<img src="~/img/errorpages/429_rockstardev.jpg" alt="Vin is angry because you caused 429" title="Move away that cursor" />
|
||||
</a>
|
||||
<br /><br />
|
||||
You sure you want to risk that?
|
||||
<br /><br />
|
||||
Slowly <a href="/">navigate back to home</a>.
|
||||
</p>
|
15
BTCPayServer/Views/Error/500.cshtml
Normal file
15
BTCPayServer/Views/Error/500.cshtml
Normal file
@ -0,0 +1,15 @@
|
||||
@{
|
||||
ViewData["ErrorTitle"] = "500 - Internal Server Error";
|
||||
}
|
||||
|
||||
<p class="lead-login">
|
||||
Whoops, something really went wrong! <a href="https://twitter.com/mrkukks">Mr Kukks</a> is so sorry.
|
||||
<br /><br />
|
||||
<a href="https://twitter.com/mrkukks" target="_blank">
|
||||
<img src="~/img/errorpages/500_mrkukks.jpg" alt="Mr Kukks puppy eyes" title="The most innocent look you'll ever see" />
|
||||
</a>
|
||||
<br /><br />
|
||||
Consult server log and consider submitting issue on BTCPayServer GitHub.
|
||||
<br /><br />
|
||||
<a href="/">Navigate back to home</a>.
|
||||
</p>
|
19
BTCPayServer/Views/Error/Handle.cshtml
Normal file
19
BTCPayServer/Views/Error/Handle.cshtml
Normal file
@ -0,0 +1,19 @@
|
||||
@using System.Net
|
||||
@model int?
|
||||
@{
|
||||
ViewData["ErrorTitle"] = "Generic Error occurred";
|
||||
if (Model.HasValue)
|
||||
{
|
||||
var httpCode = (HttpStatusCode)Model.Value;
|
||||
ViewData["ErrorTitle"] = $"{(int)httpCode} - {httpCode.ToString()}";
|
||||
}
|
||||
}
|
||||
|
||||
<p class="lead-login">
|
||||
Generic error occurred, HTTP Code: @Model
|
||||
<br /><br />
|
||||
Consult server log for more details.
|
||||
<br /><br />
|
||||
<a href="/">Navigate back to home</a>.
|
||||
<br /><br />
|
||||
</p>
|
118
BTCPayServer/Views/Error/_LayoutError.cshtml
Normal file
118
BTCPayServer/Views/Error/_LayoutError.cshtml
Normal file
@ -0,0 +1,118 @@
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
@inject BTCPayServer.Services.BTCPayServerEnvironment env
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<partial name="Header" />
|
||||
|
||||
<link href="~/main/fonts/Montserrat.css" rel="stylesheet">
|
||||
<style>
|
||||
.content-wrapper {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
@@media screen and (min-width: 768px) {
|
||||
.content-wrapper {
|
||||
padding: 20px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.col-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@media screen and (min-width: 768px) {
|
||||
.col-head {
|
||||
text-align: left;
|
||||
flex-direction: row;
|
||||
justify-content: start;
|
||||
}
|
||||
}
|
||||
|
||||
.head-logo {
|
||||
height: 70px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
@@media screen and (min-width: 768px) {
|
||||
.head-logo {
|
||||
height: 100px;
|
||||
margin-bottom: 0;
|
||||
margin-right: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
.lead-title {
|
||||
font-family: Montserrat;
|
||||
font-style: normal;
|
||||
font-weight: bold;
|
||||
font-size: 24px;
|
||||
line-height: 1.2;
|
||||
/* or 150% */
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
@@media screen and (min-width: 768px) {
|
||||
.lead-title {
|
||||
font-size: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.lead-login {
|
||||
font-family: Montserrat;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
font-size: 18px;
|
||||
line-height: 33px;
|
||||
/* or 183% */
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
.lead-h {
|
||||
font-family: Montserrat;
|
||||
font-style: normal;
|
||||
margin-bottom: 30px;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
line-height: 18px;
|
||||
/* identical to box height, or 129% */
|
||||
letter-spacing: 0.1em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<center>
|
||||
<section class="content-wrapper">
|
||||
<!-- Dummy navbar-brand, hackish way to keep test AssertNoError passing -->
|
||||
<div class="navbar-brand" style="display:none;"></div>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-12 col-head" style="justify-content:center;">
|
||||
<a asp-controller="Home" asp-action="Index"><img src="~/img/btcpay-logo.svg" alt="BTCPay Server" class="head-logo" /></a>
|
||||
<h1 class="lead-title text-uppercase">@ViewData["ErrorTitle"]</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<hr class="primary ml-0" style="margin:20px auto;" />
|
||||
@RenderBody()
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<hr class="primary ml-0" style="margin:20px auto;" />
|
||||
@await Html.PartialAsync("_BTCPaySupporters")
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</center>
|
||||
</body>
|
||||
</html>
|
3
BTCPayServer/Views/Error/_ViewStart.cshtml
Normal file
3
BTCPayServer/Views/Error/_ViewStart.cshtml
Normal file
@ -0,0 +1,3 @@
|
||||
@{
|
||||
Layout = "_LayoutError";
|
||||
}
|
@ -3,12 +3,11 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 invoice-payments">
|
||||
<h3>Paid summary</h3>
|
||||
<h3>Current status</h3>
|
||||
<table class="table table-sm table-responsive-md">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th>Payment method</th>
|
||||
<th>Address</th>
|
||||
<th class="text-right">Rate</th>
|
||||
<th class="text-right">Paid</th>
|
||||
<th class="text-right">Due</th>
|
||||
@ -23,9 +22,6 @@
|
||||
{
|
||||
<tr>
|
||||
<td>@payment.PaymentMethod</td>
|
||||
<td title="@payment.Address">
|
||||
<span class="text-truncate d-block" style="max-width: 400px">@payment.Address</span>
|
||||
</td>
|
||||
<td class="text-right">@payment.Rate</td>
|
||||
<td class="text-right">@payment.Paid</td>
|
||||
<td class="text-right">@payment.Due</td>
|
||||
|
165
BTCPayServer/Views/Server/CLightningRestServices.cshtml
Normal file
165
BTCPayServer/Views/Server/CLightningRestServices.cshtml
Normal file
@ -0,0 +1,165 @@
|
||||
@model LndServicesViewModel
|
||||
@{
|
||||
ViewData.SetActivePageAndTitle(ServerNavPages.Services);
|
||||
}
|
||||
|
||||
<h4>C-Lightning @Model.ConnectionType</h4>
|
||||
<partial name="_StatusMessage" />
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<p>
|
||||
<span>BTCPay exposes Clightning-Rest's service for outside consumption, you will find connection information here.<br /></span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<h5>Compatible wallets</h5>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-3 ml-auto text-center">
|
||||
<a href="https://github.com/ZeusLN/zeus" target="_blank">
|
||||
<img src="~/img/zeus.jpg" height="100" />
|
||||
</a>
|
||||
<p><a href="https://github.com/ZeusLN/zeus" target="_blank">Zeus</a></p>
|
||||
</div>
|
||||
<div class="col-lg-3 mr-auto text-center">
|
||||
|
||||
</div>
|
||||
<div class="col-lg-3 mr-auto text-center">
|
||||
|
||||
</div>
|
||||
<div class="col-lg-3 mr-auto text-center">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<h5>QR Code connection</h5>
|
||||
<p>
|
||||
<span>You can use this QR Code to connect external software to your C-Lightning instance.<br /></span>
|
||||
<span>This QR Code is only valid for 10 minutes</span>
|
||||
</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
@if (Model.QRCode == null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<form method="post">
|
||||
<button type="submit" class="btn btn-primary">Show QR Code</button>
|
||||
</form>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="form-group">
|
||||
<div id="qrCode"></div>
|
||||
<div id="qrCodeData" data-url="@Model.QRCode"></div>
|
||||
</div>
|
||||
<p>See QR Code information by clicking <a href="#detailsQR" data-toggle="collapse">here</a></p>
|
||||
<div id="detailsQR" class="collapse">
|
||||
<div class="form-group">
|
||||
<label>QR Code data</label>
|
||||
<input asp-for="QRCode" readonly class="form-control" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
Click <a href="@Model.QRCodeLink" target="_blank">here</a> to open the configuration file.
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="form-group">
|
||||
<h5>More details...</h5>
|
||||
<p>Alternatively, you can see the settings by clicking <a href="#details" data-toggle="collapse">here</a></p>
|
||||
</div>
|
||||
<div id="details" class="collapse">
|
||||
@if (Model.Uri == null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="Host"></label>
|
||||
<input asp-for="Host" readonly class="form-control" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="SSL"></label>
|
||||
<input asp-for="SSL" disabled type="checkbox" class="form-check-inline" />
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="Uri"></label>
|
||||
<input asp-for="Uri" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if (Model.Macaroon != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="Macaroon"></label>
|
||||
<input asp-for="Macaroon" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if (Model.AdminMacaroon != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="AdminMacaroon"></label>
|
||||
<input asp-for="AdminMacaroon" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if (Model.InvoiceMacaroon != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="InvoiceMacaroon"></label>
|
||||
<input asp-for="InvoiceMacaroon" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if (Model.ReadonlyMacaroon != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="ReadonlyMacaroon"></label>
|
||||
<input asp-for="ReadonlyMacaroon" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if (Model.GRPCSSLCipherSuites != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="GRPCSSLCipherSuites"></label>
|
||||
<input asp-for="GRPCSSLCipherSuites" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
@if (Model.CertificateThumbprint != null)
|
||||
{
|
||||
<div class="form-group">
|
||||
<label asp-for="CertificateThumbprint"></label>
|
||||
<input asp-for="CertificateThumbprint" readonly class="form-control" />
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
|
||||
@if(Model.QRCode != null)
|
||||
{
|
||||
<script type="text/javascript" src="~/js/qrcode.js"></script>
|
||||
<script type="text/javascript">
|
||||
new QRCode(document.getElementById("qrCode"),
|
||||
{
|
||||
text: @Safe.Json(Model.QRCode),
|
||||
width: 200,
|
||||
height: 200,
|
||||
useSVG: true
|
||||
});
|
||||
</script>
|
||||
}
|
||||
}
|
33
BTCPayServer/Views/Server/ConfiguratorService.cshtml
Normal file
33
BTCPayServer/Views/Server/ConfiguratorService.cshtml
Normal file
@ -0,0 +1,33 @@
|
||||
@model LightningWalletServices
|
||||
@{
|
||||
ViewData.SetActivePageAndTitle(ServerNavPages.Services);
|
||||
}
|
||||
|
||||
<h4>BTCPay Server Configurator</h4>
|
||||
<partial name="_StatusMessage" />
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
<div class="form-group">
|
||||
<p>
|
||||
<span>This page exposes information to use the configured BTCPay Server Configurator to modify this setup.</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<a href="@Model.ServiceLink" target="_blank" class="form-group">
|
||||
<label>Service</label>
|
||||
<input asp-for="ServiceLink" readonly class="form-control" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
|
||||
}
|
@ -16,7 +16,7 @@
|
||||
This is recommended if you are hosting BTCPayServer at home and wish to have a clearnet HTTPS address to access your server.
|
||||
</span>
|
||||
</p>
|
||||
<p>Note that you need to properly configure your NAT and BTCPayServer install to get HTTPS certificate. Check the documentation for <a href="https://docs.btcpayserver.org/features/dynamicdns" target="_blank">more information</a>.</p>
|
||||
<p>Note that you need to properly configure your NAT and BTCPayServer install to get HTTPS certificate. Check the documentation for <a href="https://docs.btcpayserver.org/deployment/advanced-deployment/dynamicdns" target="_blank">more information</a>.</p>
|
||||
</div>
|
||||
<form method="post" asp-action="DynamicDnsService">
|
||||
<button id="AddDynamicDNS" class="btn btn-primary" type="submit"><span class="fa fa-plus"></span> Add Dynamic DNS</button>
|
||||
|
@ -29,7 +29,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var s in Model.ExternalServices)
|
||||
@foreach (var s in Model.ExternalServices.Where(service => !string.IsNullOrEmpty(service.CryptoCode)))
|
||||
{
|
||||
<tr>
|
||||
<td>@s.CryptoCode</td>
|
||||
@ -52,7 +52,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (Model.OtherExternalServices.Count != 0)
|
||||
@if (Model.OtherExternalServices.Count != 0 || Model.ExternalServices.Any(service => string.IsNullOrEmpty(service.CryptoCode)))
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-md-8">
|
||||
@ -69,6 +69,15 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var s in Model.ExternalServices.Where(service => string.IsNullOrEmpty(service.CryptoCode)))
|
||||
{
|
||||
<tr>
|
||||
<td>@s.DisplayName</td>
|
||||
<td style="text-align: right">
|
||||
<a asp-action="Service" asp-route-serviceName="@s.ServiceName">See information</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@foreach (var s in Model.OtherExternalServices)
|
||||
{
|
||||
<tr>
|
||||
|
@ -18,7 +18,9 @@
|
||||
<div class="dropdown-menu" aria-labelledby="dropdownMenuButton">
|
||||
<a class="dropdown-item" href="" data-Server="smtp.gmail.com" data-Port="587" data-EnableSSL="true">Gmail.com</a>
|
||||
<a class="dropdown-item" href="" data-Server="mail.yahoo.com" data-Port="587" data-EnableSSL="true">Yahoo.com</a>
|
||||
<a class="dropdown-item" href="" data-Server="smtp.mailgun.org" data-Port="587" data-EnableSSL="true">Mailgun</a>
|
||||
<a class="dropdown-item" href="" data-Server="smtp.office365.com" data-Port="587" data-EnableSSL="true">Office365</a>
|
||||
<a class="dropdown-item" href="" data-Server="smtp.sendgrid.net" data-Port="587" data-EnableSSL="true">SendGrid</a>
|
||||
</div>
|
||||
</div>
|
||||
<p></p>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<html>
|
||||
<head></head>
|
||||
<body>
|
||||
<form method="post" id="postform" asp-action="@Model.AspAction" asp-controller="@Model.AspController">
|
||||
<form method="post" id="postform" asp-action="@Model.AspAction" asp-controller="@Model.AspController" asp-route-walletId="@this.Context.GetRouteValue("walletId").ToString()">
|
||||
@foreach(var o in Model.Parameters) {
|
||||
<input type="hidden" name="@o.Key" value="@o.Value" />
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
<p>
|
||||
Your node is synching the entire blockchain and validating the consensus rules...
|
||||
</p>
|
||||
@foreach (var line in dashboard.GetAll())
|
||||
@foreach (var line in dashboard.GetAll().Where(summary => summary.Network.ShowSyncSummary))
|
||||
{
|
||||
<h4>@line.Network.CryptoCode</h4>
|
||||
@if (line.Status == null)
|
||||
|
41
BTCPayServer/Views/Shared/_BTCPaySupporters.cshtml
Normal file
41
BTCPayServer/Views/Shared/_BTCPaySupporters.cshtml
Normal file
@ -0,0 +1,41 @@
|
||||
<h3 class="lead-h">BTCPayServer Supporters <a href="https://foundation.btcpayserver.org/" target="_blank"><span class="fa fa-question-circle-o" title="More information..."></span></a></h3>
|
||||
<div class="figure">
|
||||
<a href="https://twitter.com/sqcrypto" target="_blank">
|
||||
<img src="~/img/squarecrypto.svg" alt="Sponsor Square Crypto" height="75" />
|
||||
</a>
|
||||
<div class="figure-caption text-center">
|
||||
<a href="https://twitter.com/sqcrypto" class="text-muted small" target="_blank">Square Crypto</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="figure ml-4">
|
||||
<a href="https://www.dglab.com/en/" target="_blank">
|
||||
<img src="~/img/dglab.svg" alt="Sponsor DG lab" height="75" />
|
||||
</a>
|
||||
<div class="figure-caption text-center">
|
||||
<a href="https://www.dglab.com/en/" class="text-muted small" target="_blank">DG Lab</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="figure ml-4">
|
||||
<a href="https://acinq.co/" target="_blank">
|
||||
<img src="~/img/acinq-logo.svg" alt="Sponsor ACINQ" height="75" />
|
||||
</a>
|
||||
<div class="figure-caption text-center">
|
||||
<a href="https://acinq.co/" class="text-muted small" target="_blank">ACINQ</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="figure ml-4">
|
||||
<a href="https://lunanode.com/" target="_blank">
|
||||
<img src="~/img/lunanode.svg" alt="Sponsor LunaNode" height="75" />
|
||||
</a>
|
||||
<div class="figure-caption text-center">
|
||||
<a href="https://lunanode.com/" class="text-muted small" target="_blank">LunaNode</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="figure ml-4">
|
||||
<a href="https://walletofsatoshi.com/" target="_blank">
|
||||
<img src="~/img/walletofsatoshi.svg" alt="Sponsor Wallet of Satoshi" height="75" />
|
||||
</a>
|
||||
<div class="figure-caption text-center">
|
||||
<a href="https://walletofsatoshi.com/" class="text-muted small" target="_blank">Wallet of Satoshi</a>
|
||||
</div>
|
||||
</div>
|
42
BTCPayServer/Views/Account/_WelcomeLayout.cshtml → BTCPayServer/Views/Shared/_LayoutWelcome.cshtml
42
BTCPayServer/Views/Account/_WelcomeLayout.cshtml → BTCPayServer/Views/Shared/_LayoutWelcome.cshtml
@ -102,47 +102,7 @@
|
||||
<div class="col-md-7 order-md-1 order-2">
|
||||
<hr class="primary ml-0" style="margin:30px auto;">
|
||||
<p class="lead-login" style="margin-bottom:69px;">BTCPay Server is a self-hosted, open-source cryptocurrency payment processor. It is secure, private, censorship-resistant and free.</p>
|
||||
<h3 class="lead-h">Our supporters <a href="https://foundation.btcpayserver.org/" target="_blank"><span class="fa fa-question-circle-o" title="More information..."></span></a></h3>
|
||||
<div class="figure">
|
||||
<a href="https://twitter.com/sqcrypto" target="_blank">
|
||||
<img src="~/img/squarecrypto.svg" alt="Sponsor Square Crypto" height="75" />
|
||||
</a>
|
||||
<div class="figure-caption text-center">
|
||||
<a href="https://twitter.com/sqcrypto" class="text-muted small" target="_blank">Square Crypto</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="figure ml-4">
|
||||
<a href="https://www.dglab.com/en/" target="_blank">
|
||||
<img src="~/img/dglab.svg" alt="Sponsor DG lab" height="75" />
|
||||
</a>
|
||||
<div class="figure-caption text-center">
|
||||
<a href="https://www.dglab.com/en/" class="text-muted small" target="_blank">DG Lab</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="figure ml-4">
|
||||
<a href="https://acinq.co/" target="_blank">
|
||||
<img src="~/img/acinq-logo.svg" alt="Sponsor ACINQ" height="75" />
|
||||
</a>
|
||||
<div class="figure-caption text-center">
|
||||
<a href="https://acinq.co/" class="text-muted small" target="_blank">ACINQ</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="figure ml-4">
|
||||
<a href="https://lunanode.com/" target="_blank">
|
||||
<img src="~/img/lunanode.svg" alt="Sponsor LunaNode" height="75" />
|
||||
</a>
|
||||
<div class="figure-caption text-center">
|
||||
<a href="https://lunanode.com/" class="text-muted small" target="_blank">LunaNode</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="figure ml-4">
|
||||
<a href="https://walletofsatoshi.com/" target="_blank">
|
||||
<img src="~/img/walletofsatoshi.svg" alt="Sponsor Wallet of Satoshi" height="75" />
|
||||
</a>
|
||||
<div class="figure-caption text-center">
|
||||
<a href="https://walletofsatoshi.com/" class="text-muted small" target="_blank">Wallet of Satoshi</a>
|
||||
</div>
|
||||
</div>
|
||||
@await Html.PartialAsync("_BTCPaySupporters")
|
||||
</div>
|
||||
<div class="col-md-5 order-md-2 order-1">
|
||||
@RenderBody()
|
@ -81,7 +81,7 @@
|
||||
Import from...
|
||||
</button>
|
||||
<div class="dropdown-menu dropdown-menu-right">
|
||||
<button class="dropdown-item" type="button">... Coldcard (air gap)</button>
|
||||
<button class="dropdown-item" type="button" data-toggle="modal" data-target="#coldcardimport">... Coldcard (air gap)</button>
|
||||
<button class="dropdown-item check-for-ledger" type="button">... Ledger Wallet</button>
|
||||
@if (Model.CryptoCode == "BTC")
|
||||
{
|
||||
|
@ -63,7 +63,7 @@
|
||||
<div class="modal-dialog" role="document">
|
||||
<form class="modal-content" form method="post" enctype="multipart/form-data">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">Import Coldcard Wallet</h5>
|
||||
<h5 class="modal-title" id="coldcardimportLabel">Import Coldcard Wallet</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
|
@ -20,7 +20,9 @@
|
||||
<label asp-for="ExistingMnemonic">Existing Seed</label>
|
||||
<input type="text" asp-for="ExistingMnemonic" class="form-control" />
|
||||
<span asp-validation-for="ExistingMnemonic" class="text-danger"></span>
|
||||
<p class="text-black-50">You can choose to import an existing mnemonic seed phrase. If you leave blank, we will generate one for you.</p>
|
||||
<small class="form-text text-muted">
|
||||
You can choose to import an existing mnemonic seed phrase. If you leave blank, we will generate one for you.
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label asp-for="Passphrase">Passphrase (optional)</label>
|
||||
@ -56,14 +58,17 @@
|
||||
<input type="checkbox" class="form-check-inline" asp-for="SavePrivateKeys" />
|
||||
<label asp-for="SavePrivateKeys">Is hot wallet</label>
|
||||
<span asp-validation-for="SavePrivateKeys" class="text-danger"></span>
|
||||
<p class="text-danger">If checked, each private key associated with an address generated will be stored as metadata in NBXplorer. While convenient, this means that anyone with access to your server will have access to your private keys and will be able to steal your funds.</p>
|
||||
|
||||
<small class="form-text text-danger">
|
||||
If checked, each private key associated with an address generated will be stored as metadata in NBXplorer. While convenient, this means that anyone with access to your server will have access to your private keys and will be able to steal your funds.
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="checkbox" class="form-check-inline" asp-for="ImportKeysToRPC" />
|
||||
<label asp-for="ImportKeysToRPC">Import keys to RPC</label>
|
||||
<span asp-validation-for="ImportKeysToRPC" class="text-danger"></span>
|
||||
<p class="text-black-50">If checked, each address generated will be imported into the node wallet so that you can view your balance through your node. When this is enabled alongside <code>Is hot wallet</code>, you're also able to use the node wallet to spend (this works pretty well in conjunction with apps such as FullyNoded).</p>
|
||||
<small class="form-text text-muted">
|
||||
If checked, each address generated will be imported into the node wallet so that you can view your balance through your node. When this is enabled alongside <code>Is hot wallet</code>, you're also able to use the node wallet to spend (this works pretty well in conjunction with apps such as FullyNoded).
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
|
@ -19,7 +19,7 @@
|
||||
<div class="form-group">
|
||||
<h5>Scripting</h5>
|
||||
<p>Rate script allows you to express precisely how you want to calculate rates for currency pairs.</p>
|
||||
<p>We are retrieving the rate of each exchange either directly, via <a href="https://www.coingecko.com/" target="_blank">CoinGecko (free)</a> or <a href="https://bitcoinaverage.com/" target="_blank">BitcoinAverage (commercial)</a></p>
|
||||
<p>We are retrieving the rate of each exchange either directly, via <a href="https://www.coingecko.com/" target="_blank">CoinGecko (free)</a>.</p>
|
||||
<div class="accordion" id="accordion-info">
|
||||
<div class="card">
|
||||
<div class="card-header" id="direct-header">
|
||||
|
@ -97,9 +97,9 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach(var scheme in Model.DerivationSchemes)
|
||||
@foreach(var scheme in Model.DerivationSchemes.OrderBy(scheme => scheme.Collapsed))
|
||||
{
|
||||
<tr>
|
||||
<tr class="@(@scheme.Collapsed? "collapsed": "")">
|
||||
<td>@scheme.Crypto</td>
|
||||
<td class="smMaxWidth text-truncate">@scheme.Value</td>
|
||||
<td style="text-align:center;">
|
||||
@ -121,6 +121,13 @@
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
|
||||
@if (Model.DerivationSchemes.Any(scheme => scheme.Collapsed))
|
||||
{
|
||||
<tr class="only-for-js">
|
||||
<td colspan="4"><button class="btn btn-link" id="toggle-assets" type="button">Show additional assets</button></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -250,4 +257,14 @@
|
||||
|
||||
@section Scripts {
|
||||
@await Html.PartialAsync("_ValidationScriptsPartial")
|
||||
|
||||
<script>
|
||||
$(document).ready(function(){
|
||||
$(".collapsed").hide();
|
||||
$("#toggle-assets").click(function() {
|
||||
$(".collapsed").show();
|
||||
$(this).parents("tr").hide();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
}
|
||||
|
@ -28,8 +28,9 @@
|
||||
{
|
||||
<h3>Decoded PSBT</h3>
|
||||
<div class="form-group">
|
||||
<form method="post" asp-action="WalletPSBT">
|
||||
<form method="post" asp-action="WalletPSBT" asp-route-walletId="@this.Context.GetRouteValue("walletId")">
|
||||
<input type="hidden" asp-for="CryptoCode" />
|
||||
<input type="hidden" asp-for="NBXSeedAvailable" />
|
||||
<input type="hidden" asp-for="PSBT" />
|
||||
<input type="hidden" asp-for="FileName" />
|
||||
<div class="dropdown d-inline-block" style="margin-top:16px;">
|
||||
@ -43,6 +44,10 @@
|
||||
@if (Model.CryptoCode == "BTC") {
|
||||
<button name="command" type="submit" class="dropdown-item" value="vault">... the vault (preview)</button>
|
||||
}
|
||||
@if (Model.NBXSeedAvailable)
|
||||
{
|
||||
<button name="command" type="submit" class="dropdown-item" value="nbx-seed">... the seed saved in NBXplorer</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown d-inline-block">
|
||||
@ -60,7 +65,7 @@
|
||||
<pre><code class="json">@Model.Decoded</code></pre>
|
||||
}
|
||||
<h3>PSBT to decode</h3>
|
||||
<form class="form-group" method="post" asp-action="WalletPSBT" enctype="multipart/form-data">
|
||||
<form class="form-group" method="post" asp-action="WalletPSBT" asp-route-walletId="@this.Context.GetRouteValue("walletId")" enctype="multipart/form-data">
|
||||
<div class="form-group">
|
||||
<textarea class="form-control" rows="5" asp-for="PSBT"></textarea>
|
||||
<span asp-validation-for="PSBT" class="text-danger"></span>
|
||||
|
@ -2,13 +2,24 @@
|
||||
@{
|
||||
Layout = "../Shared/_Layout.cshtml";
|
||||
}
|
||||
|
||||
<section>
|
||||
<div class="container">
|
||||
@if (TempData.HasStatusMessage())
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-md-10 text-center">
|
||||
<partial name="_StatusMessage"/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (Model.GlobalError != null)
|
||||
{
|
||||
<div class="alert alert-danger alert-dismissible" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<span>@Model.GlobalError</span><br />
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
<span>@Model.GlobalError</span><br/>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
@ -18,14 +29,16 @@
|
||||
@if (Model.CanCalculateBalance)
|
||||
{
|
||||
<p>
|
||||
If you broadcast this transaction, your balance will change: @if (Model.Positive)
|
||||
If you broadcast this transaction, your balance will change:
|
||||
@if (Model.Positive)
|
||||
{
|
||||
<span style="color:green;">@Model.BalanceChange</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span style="color:red;">@Model.BalanceChange</span>
|
||||
}, do you want to continue?
|
||||
}
|
||||
, do you want to continue?
|
||||
</p>
|
||||
}
|
||||
else
|
||||
@ -40,36 +53,38 @@
|
||||
<h4 class="text-left">Inputs</h4>
|
||||
<table class="table table-sm table-responsive-lg">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th style="text-align:left" class="col-md-auto">
|
||||
Index
|
||||
</th>
|
||||
<th style="text-align:right">Amount</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="text-align:left" class="col-md-auto">
|
||||
Index
|
||||
</th>
|
||||
<th style="text-align:right">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var input in Model.Inputs)
|
||||
{
|
||||
<tr>
|
||||
@if (input.Error != null)
|
||||
{
|
||||
<td style="text-align:left">@input.Index <span class="fa fa-exclamation-triangle" style="color:red;" title="@input.Error"></span></td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td style="text-align:left">@input.Index</td>
|
||||
}
|
||||
@foreach (var input in Model.Inputs)
|
||||
{
|
||||
<tr>
|
||||
@if (input.Error != null)
|
||||
{
|
||||
<td style="text-align:left">
|
||||
@input.Index <span class="fa fa-exclamation-triangle" style="color:red;" title="@input.Error"></span>
|
||||
</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td style="text-align:left">@input.Index</td>
|
||||
}
|
||||
|
||||
@if (input.Positive)
|
||||
{
|
||||
<td style="text-align:right; color:green;">@input.BalanceChange</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td style="text-align:right; color:red;">@input.BalanceChange</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
@if (input.Positive)
|
||||
{
|
||||
<td style="text-align:right; color:green;">@input.BalanceChange</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td style="text-align:right; color:red;">@input.BalanceChange</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -82,28 +97,28 @@
|
||||
<h4 class="text-left">Outputs</h4>
|
||||
<table class="table table-sm table-responsive-lg">
|
||||
<thead class="thead-inverse">
|
||||
<tr>
|
||||
<th style="text-align:left" class="col-md-auto">
|
||||
Destination
|
||||
</th>
|
||||
<th style="text-align:right">Amount</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th style="text-align:left" class="col-md-auto">
|
||||
Destination
|
||||
</th>
|
||||
<th style="text-align:right">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var destination in Model.Destinations)
|
||||
{
|
||||
<tr>
|
||||
<td style="text-align:left">@destination.Destination</td>
|
||||
@if (destination.Positive)
|
||||
{
|
||||
<td style="text-align:right; color:green;">@destination.Balance</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td style="text-align:right; color:red;">@destination.Balance</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
@foreach (var destination in Model.Destinations)
|
||||
{
|
||||
<tr>
|
||||
<td style="text-align:left">@destination.Destination</td>
|
||||
@if (destination.Positive)
|
||||
{
|
||||
<td style="text-align:right; color:green;">@destination.Balance</td>
|
||||
}
|
||||
else
|
||||
{
|
||||
<td style="text-align:right; color:red;">@destination.Balance</td>
|
||||
}
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -122,12 +137,13 @@
|
||||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<form method="post" asp-action="WalletPSBTReady" asp-route-walletId="@this.Context.GetRouteValue("walletId")">
|
||||
<input type="hidden" asp-for="PSBT" />
|
||||
<input type="hidden" asp-for="SigningKey" />
|
||||
<input type="hidden" asp-for="SigningKeyPath" />
|
||||
<input type="hidden" asp-for="PSBT"/>
|
||||
<input type="hidden" asp-for="SigningKey"/>
|
||||
<input type="hidden" asp-for="SigningKeyPath"/>
|
||||
@if (!Model.HasErrors)
|
||||
{
|
||||
<button type="submit" class="btn btn-primary" name="command" value="broadcast">Broadcast it</button> <span> or </span>
|
||||
<button type="submit" class="btn btn-primary" name="command" value="broadcast">Broadcast it</button>
|
||||
<span> or </span>
|
||||
}
|
||||
<button type="submit" class="btn btn-secondary" name="command" value="analyze-psbt">Export as PSBT</button>
|
||||
</form>
|
||||
|
@ -5,16 +5,25 @@
|
||||
ViewData["Title"] = "Manage wallet";
|
||||
ViewData.SetActivePageAndTitle(WalletsNavPages.Send);
|
||||
}
|
||||
|
||||
@if (TempData.HasStatusMessage())
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-md-10 text-center">
|
||||
<partial name="_StatusMessage" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="@(Model.Outputs.Count==1? "col-lg-6 transaction-output-form": "col-lg-8")">
|
||||
<form method="post">
|
||||
<input type="hidden" asp-for="Divisibility" />
|
||||
<input type="hidden" asp-for="NBXSeedAvailable" />
|
||||
<input type="hidden" asp-for="Fiat" />
|
||||
<input type="hidden" asp-for="Rate" />
|
||||
<input type="hidden" asp-for="CurrentBalance" />
|
||||
<input type="hidden" asp-for="RecommendedSatoshiPerByte" />
|
||||
<input type="hidden" asp-for="CryptoCode" />
|
||||
<input type="hidden" name="BIP21" id="BIP21" />
|
||||
<ul class="text-danger">
|
||||
@foreach (var errors in ViewData.ModelState.Where(pair => pair.Key == string.Empty && pair.Value.ValidationState == ModelValidationState.Invalid))
|
||||
{
|
||||
@ -117,6 +126,7 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="card">
|
||||
<button class="btn btn-light collapsed" type="button" data-toggle="collapse" data-target="#accordian-advanced" aria-expanded="false" aria-controls="accordian-advanced">
|
||||
Advanced settings
|
||||
@ -155,9 +165,14 @@
|
||||
@if (Model.CryptoCode == "BTC") {
|
||||
<button name="command" type="submit" class="dropdown-item" value="vault">... the vault (preview)</button>
|
||||
}
|
||||
@if (Model.NBXSeedAvailable)
|
||||
{
|
||||
<button name="command" type="submit" class="dropdown-item" value="nbx-seed">... the seed saved in NBXplorer</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" name="command" value="add-output" class="ml-1 btn btn-secondary">Add another destination </button>
|
||||
<button type="button" id="bip21parse" class="ml-1 btn btn-secondary">Parse BIP21</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -6,14 +6,22 @@
|
||||
}
|
||||
|
||||
<h4>Sign the transaction with Ledger</h4>
|
||||
@if (TempData.HasStatusMessage())
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-md-10 text-center">
|
||||
<partial name="_StatusMessage" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div id="walletAlert" class="alert alert-danger alert-dismissible" style="display:none;" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<span id="alertMessage"></span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<form id="broadcastForm" asp-action="WalletPSBTReady" asp-route-walletId="@this.Context.GetRouteValue("walletId")" method="post" style="display:none;">
|
||||
<input type="hidden" id="PSBT" asp-for="PSBT" />
|
||||
<form id="broadcastForm" asp-action="SubmitLedger" asp-route-walletId="@this.Context.GetRouteValue("walletId")" method="post" style="display:none;">
|
||||
<input type="hidden" id="PSBT" asp-for="PSBT" value="@Model.PSBT"/>
|
||||
<input type="hidden" asp-for="HintChange" />
|
||||
<input type="hidden" asp-for="WebsocketPath" />
|
||||
</form>
|
||||
|
@ -6,15 +6,23 @@
|
||||
}
|
||||
|
||||
<h4>Sign the transaction with BTCPayServer Vault</h4>
|
||||
@if (TempData.HasStatusMessage())
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-md-10 text-center">
|
||||
<partial name="_StatusMessage" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div id="walletAlert" class="alert alert-danger alert-dismissible" style="display:none;" role="alert">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<span id="alertMessage"></span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div id="body" class="col-md-10">
|
||||
<form id="broadcastForm" asp-action="WalletPSBTReady" asp-route-walletId="@this.Context.GetRouteValue("walletId")" method="post" style="display:none;">
|
||||
<form id="broadcastForm" asp-action="SubmitVault" asp-route-walletId="@this.Context.GetRouteValue("walletId")" method="post" style="display:none;">
|
||||
<input type="hidden" id="WalletId" asp-for="WalletId" />
|
||||
<input type="hidden" id="PSBT" asp-for="PSBT" />
|
||||
<input type="hidden" id="PSBT" asp-for="PSBT" value="@Model.PSBT"/>
|
||||
<input type="hidden" asp-for="WebsocketPath" />
|
||||
</form>
|
||||
<div id="vaultPlaceholder"></div>
|
||||
|
@ -57,7 +57,7 @@
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
If BTCPay Server shows you an invalid balance, <a asp-action="WalletRescan">rescan your wallet</a>. <br />
|
||||
If BTCPay Server shows you an invalid balance, <a asp-action="WalletRescan" asp-route-walletId="@this.Context.GetRouteValue("walletId")">rescan your wallet</a>. <br />
|
||||
If some transactions appear in BTCPay Server, but are missing on Electrum or another wallet, <a href="https://docs.btcpayserver.org/faq-and-common-issues/faq-wallet#missing-payments-in-my-software-or-hardware-wallet">follow those instructions</a>.
|
||||
</div>
|
||||
</div>
|
||||
|
BIN
BTCPayServer/wwwroot/img/errorpages/404_nicolas.jpg
Normal file
BIN
BTCPayServer/wwwroot/img/errorpages/404_nicolas.jpg
Normal file
Binary file not shown.
After ![]() (image error) Size: 14 KiB |
BIN
BTCPayServer/wwwroot/img/errorpages/429_rockstardev.jpg
Normal file
BIN
BTCPayServer/wwwroot/img/errorpages/429_rockstardev.jpg
Normal file
Binary file not shown.
After ![]() (image error) Size: 15 KiB |
BIN
BTCPayServer/wwwroot/img/errorpages/500_mrkukks.jpg
Normal file
BIN
BTCPayServer/wwwroot/img/errorpages/500_mrkukks.jpg
Normal file
Binary file not shown.
After ![]() (image error) Size: 18 KiB |
BIN
BTCPayServer/wwwroot/imlegacy/etb.png
Normal file
BIN
BTCPayServer/wwwroot/imlegacy/etb.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 31 KiB |
Binary file not shown.
Before ![]() (image error) Size: 11 KiB After ![]() (image error) Size: 10 KiB ![]() ![]() |
@ -44,4 +44,12 @@ $(function () {
|
||||
updateFiatValue(outputAmountElement);
|
||||
return false;
|
||||
});
|
||||
|
||||
$("#bip21parse").on("click", function(){
|
||||
var bip21 = prompt("Paste BIP21 here");
|
||||
if(bip21){
|
||||
$("#BIP21").val(bip21);
|
||||
$("form").submit();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -1,5 +1,5 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>1.0.3.153</Version>
|
||||
<Version>1.0.3.158</Version>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
22
README.md
22
README.md
@ -11,11 +11,11 @@
|
||||
|
||||
BTCPay Server is a free and open-source cryptocurrency payment processor which allows you to receive payments in Bitcoin and altcoins directly, with no fees, transaction cost or a middleman.
|
||||
|
||||
BTCPay is a non-custodial invoicing system which eliminates the involvement of a third-party. Payments with BTCPay go directly to your wallet, which increases the privacy and security. Your private keys are never uploaded to the server. There is no address re-use, since each invoice generates a new address deriving from your xpubkey.
|
||||
BTCPay Server is a non-custodial invoicing system which eliminates the involvement of a third-party. Payments with BTCPay Server go directly to your wallet, which increases the privacy and security. Your private keys are never uploaded to the server. There is no address re-use, since each invoice generates a new address deriving from your xpubkey.
|
||||
|
||||
The software is built in C# and conforms to the invoice [API of BitPay](https://bitpay.com/api). It allows for your website to be easily migrated from BitPay and configured as a self-hosted payment processor.
|
||||
|
||||
You can run BTCPay as a self-hosted solution on your own server, or use a [third-party host](https://github.com/btcpayserver/btcpayserver-doc/blob/master/ThirdPartyHosting.md).
|
||||
You can run BTCPay Server as a self-hosted solution on your own server, or use a [third-party host](https://github.com/btcpayserver/btcpayserver-doc/blob/master/ThirdPartyHosting.md).
|
||||
|
||||
The self-hosted solution allows you not only to attach an unlimited number of stores and use the Lightning Network but also become the payment processor for others.
|
||||
|
||||
@ -36,25 +36,26 @@ Thanks to the [apps](https://github.com/btcpayserver/btcpayserver-doc/blob/maste
|
||||
|
||||
## Features
|
||||
|
||||
* Direct, peer-to-peer Bitcoin and altcoin payments
|
||||
* No transaction fees (other than those for the crypto networks)
|
||||
* Direct, peer-to-peer Bitcoin payments
|
||||
* No transaction fees (other than the [network fee](https://en.bitcoin.it/wiki/Miner_fees))
|
||||
* No processing fees
|
||||
* No middleman
|
||||
* No KYC
|
||||
* User has complete control over private keys
|
||||
* Non-custodial (complete control over the private key)
|
||||
* Enhanced privacy
|
||||
* Enhanced security
|
||||
* Self-hosted
|
||||
* SegWit support
|
||||
* Lightning Network support (LND and c-lightning)
|
||||
* Lightning Network support (LND, c-lightning, Eclair and Ptarmigan)
|
||||
* Tor support
|
||||
* Opt-in [altcoin](https://github.com/btcpayserver/btcpayserver-doc/blob/master/Altcoins.md) integrations
|
||||
* Full compatibility with BitPay API (easy migration)
|
||||
* Process payments for others
|
||||
* Easy-embeddable Payment buttons
|
||||
* Point of sale app
|
||||
* Crowdfunding app
|
||||
* Payment requests
|
||||
* Internal Web Wallet
|
||||
* Payment Requests
|
||||
* Internal, full-node reliant wallet with [hardware wallet integration](https://github.com/btcpayserver/btcpayserver-doc/blob/master/Vault.md)
|
||||
|
||||
## Getting Started
|
||||
|
||||
@ -71,6 +72,7 @@ If you have trouble using BTCPay, consider joining [communities listed on offici
|
||||
Main community chat is located on [Mattermost](https://chat.btcpayserver.org/).
|
||||
|
||||
## Contributing
|
||||
|
||||
BTCPay is built and maintained entirely by volunteer contributors around the internet. We welcome and appreciate new contributions.
|
||||
|
||||
If you're a developer looking to help, but you're not sure where to begin, check the [good first issue label](https://github.com/btcpayserver/btcpayserver/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22), which contains small pieces of work that have been specifically flagged as being friendly to new contributors.
|
||||
@ -139,6 +141,10 @@ For more information, see the documentation: [How to deploy a BTCPay server inst
|
||||
|
||||
Bitcoin is the only focus of the project and its core developers. However, opt in integrations are present for [several altcoins](https://github.com/btcpayserver/btcpayserver-doc/blob/master/Altcoins.md). Altcoins are maintained by their respective communities.
|
||||
|
||||
## License
|
||||
|
||||
BTCPay Server software, logo and designs are provided under [MIT License](https://github.com/btcpayserver/btcpayserver/blob/master/LICENSE).
|
||||
|
||||
## Supporters
|
||||
|
||||
The BTCPay Server Project is proudly supported by these entities through the [BTCPay Server Foundation](https://foundation.btcpayserver.org/).
|
||||
|
Reference in New Issue
Block a user