btcpayserver/BTCPayServer/Controllers/StoresController.BTCLike.cs

376 lines
16 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.Globalization;
2019-05-08 20:37:37 +02:00
using System.IO;
using System.Linq;
using System.Net.WebSockets;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
2020-06-28 17:55:27 +09:00
using BTCPayServer.Client;
using BTCPayServer.Data;
using BTCPayServer.Events;
2020-06-28 17:55:27 +09:00
using BTCPayServer.Logging;
2019-05-08 20:37:37 +02:00
using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Services;
2020-06-28 17:55:27 +09:00
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
2020-06-28 17:55:27 +09:00
using Microsoft.Extensions.Logging;
using NBitcoin;
using NBXplorer.DerivationStrategy;
2019-11-29 21:24:52 +01:00
using NBXplorer.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Controllers
{
public partial class StoresController
{
[HttpGet]
[Route("{storeId}/derivations/{cryptoCode}")]
public async Task<IActionResult> AddDerivationScheme(string storeId, string cryptoCode)
{
2018-04-30 02:33:42 +09:00
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
2018-04-10 19:07:57 +09:00
var network = cryptoCode == null ? null : _ExplorerProvider.GetNetwork(cryptoCode);
if (network == null)
{
return NotFound();
}
DerivationSchemeViewModel vm = new DerivationSchemeViewModel();
vm.CryptoCode = cryptoCode;
2018-04-12 11:48:33 +09:00
vm.RootKeyPath = network.GetRootKeyPath();
Put Ledger Wallet pairing in a popup, prepare code for Trezor pairing (#836) * Allowing for POS to be displayed at website root * Switching to asp attributes for form post action * Applying default formatting rules on HTML * The destination pays mining fees => Subtract fees from amount * small cleanup (#851) * Part2: Openiddict: Init OpenIddict & Database Migration & Auth Policies (#567) * Part 1: OpenIddict - Minor Changes & Config prep * Part 1: OpenIddict - Minor Changes & Config prep * Part2: Openiddict: Init OpenIddict & Database Migration & Auth Policies * pr changes * pr changes * fix merge * pr fixes * remove config for openid -- no need for it for now * fix compile * fix compile #2 * remove extra ns using * Update Startup.cs * compile * adjust settings a bit * remove duplicate * remove external login provider placeholder html * remove unused directives * regenerate db snapshot model * Remove dynamic policy * Provide Pretty descriptions for payment methods from their handlers (#852) * small cleanup * Provide Pretty descriptions for payment methods from their handlers * remove PrettyMethod() * integration with trezor * rough load xpub from trezor * update deriv scheme trezor * move ledger import to dialog * add import from hw wallet dropdown * Support temporary links for local file system provider (#848) * wip * Support temporary links for local file system provider * pass base url to file services * fix test * do not crash on errors with local filesystem * remove console * fix paranthesis * work on trezor.net integration * pushed non compiling sign wallet code * comment out wallet code * abstract ledger ws in add deriv * Auto stash before merge of "trezor" and "btcpayserver/master" * final add changes * cleanup * improve connectivity and fix e2e tests * fix selenium * add experimental warning for trezor * move import button to right and convert to text link * switch to defer and async scripts in add deriv scheme * make defer not async * more elaborate import trezor dialog * Fix small issues * hide trezor for now
2019-05-25 02:45:36 +00:00
vm.Network = network;
var derivation = GetExistingDerivationStrategy(vm.CryptoCode, store);
if (derivation != null)
{
vm.DerivationScheme = derivation.AccountDerivation.ToString();
vm.Config = derivation.ToJson();
}
vm.Enabled = !store.GetStoreBlob().IsExcluded(new PaymentMethodId(vm.CryptoCode, PaymentTypes.BTCLike));
var hotWallet = await CanUseHotWallet();
vm.CanUseHotWallet = hotWallet.HotWallet;
vm.CanUseRPCImport = hotWallet.RPCImport;
return View(vm);
}
private DerivationSchemeSettings GetExistingDerivationStrategy(string cryptoCode, StoreData store)
{
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
var existing = store.GetSupportedPaymentMethods(_NetworkProvider)
.OfType<DerivationSchemeSettings>()
.FirstOrDefault(d => d.PaymentId == id);
return existing;
}
[HttpPost]
2020-06-28 17:55:27 +09:00
[Route("{storeId}/derivations/{cryptoCode}")]
2020-02-26 09:10:27 +01:00
[ApiExplorerSettings(IgnoreApi = true)]
2020-02-25 15:33:04 +01:00
public async Task<IActionResult> AddDerivationScheme(string storeId, [FromForm] DerivationSchemeViewModel vm,
2019-05-08 20:37:37 +02:00
string cryptoCode)
{
vm.CryptoCode = cryptoCode;
2018-04-30 02:33:42 +09:00
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
var network = cryptoCode == null ? null : _ExplorerProvider.GetNetwork(cryptoCode);
if (network == null)
{
return NotFound();
}
2019-05-08 20:37:37 +02:00
Put Ledger Wallet pairing in a popup, prepare code for Trezor pairing (#836) * Allowing for POS to be displayed at website root * Switching to asp attributes for form post action * Applying default formatting rules on HTML * The destination pays mining fees => Subtract fees from amount * small cleanup (#851) * Part2: Openiddict: Init OpenIddict & Database Migration & Auth Policies (#567) * Part 1: OpenIddict - Minor Changes & Config prep * Part 1: OpenIddict - Minor Changes & Config prep * Part2: Openiddict: Init OpenIddict & Database Migration & Auth Policies * pr changes * pr changes * fix merge * pr fixes * remove config for openid -- no need for it for now * fix compile * fix compile #2 * remove extra ns using * Update Startup.cs * compile * adjust settings a bit * remove duplicate * remove external login provider placeholder html * remove unused directives * regenerate db snapshot model * Remove dynamic policy * Provide Pretty descriptions for payment methods from their handlers (#852) * small cleanup * Provide Pretty descriptions for payment methods from their handlers * remove PrettyMethod() * integration with trezor * rough load xpub from trezor * update deriv scheme trezor * move ledger import to dialog * add import from hw wallet dropdown * Support temporary links for local file system provider (#848) * wip * Support temporary links for local file system provider * pass base url to file services * fix test * do not crash on errors with local filesystem * remove console * fix paranthesis * work on trezor.net integration * pushed non compiling sign wallet code * comment out wallet code * abstract ledger ws in add deriv * Auto stash before merge of "trezor" and "btcpayserver/master" * final add changes * cleanup * improve connectivity and fix e2e tests * fix selenium * add experimental warning for trezor * move import button to right and convert to text link * switch to defer and async scripts in add deriv scheme * make defer not async * more elaborate import trezor dialog * Fix small issues * hide trezor for now
2019-05-25 02:45:36 +00:00
vm.Network = network;
2018-04-12 11:48:33 +09:00
vm.RootKeyPath = network.GetRootKeyPath();
2019-05-08 20:37:37 +02:00
DerivationSchemeSettings strategy = null;
var wallet = _WalletProvider.GetWallet(network);
if (wallet == null)
{
return NotFound();
}
if (!string.IsNullOrEmpty(vm.Config))
{
if (!DerivationSchemeSettings.TryParseFromJson(vm.Config, network, out strategy))
{
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = "Config file was not in the correct format"
});
vm.Confirmation = false;
2020-06-28 17:55:27 +09:00
return View(nameof(AddDerivationScheme), vm);
}
}
if (vm.WalletFile != null)
{
if (!DerivationSchemeSettings.TryParseFromWalletFile(await ReadAllText(vm.WalletFile), network, out strategy))
{
TempData.SetStatusMessageModel(new StatusMessageModel()
2019-05-08 20:37:37 +02:00
{
Severity = StatusMessageModel.StatusSeverity.Error,
Message = "Wallet file was not in the correct format"
});
vm.Confirmation = false;
2020-06-28 17:55:27 +09:00
return View(nameof(AddDerivationScheme), vm);
}
}
2019-05-08 20:37:37 +02:00
else
{
2019-05-08 20:37:37 +02:00
try
{
if (!string.IsNullOrEmpty(vm.DerivationScheme))
{
var newStrategy = ParseDerivationStrategy(vm.DerivationScheme, null, network);
if (newStrategy.AccountDerivation != strategy?.AccountDerivation)
{
var accountKey = string.IsNullOrEmpty(vm.AccountKey) ? null : new BitcoinExtPubKey(vm.AccountKey, network.NBitcoinNetwork);
if (accountKey != null)
{
var accountSettings = newStrategy.AccountKeySettings.FirstOrDefault(a => a.AccountKey == accountKey);
if (accountSettings != null)
{
accountSettings.AccountKeyPath = vm.KeyPath == null ? null : KeyPath.Parse(vm.KeyPath);
accountSettings.RootFingerprint = string.IsNullOrEmpty(vm.RootFingerprint) ? (HDFingerprint?)null : new HDFingerprint(NBitcoin.DataEncoders.Encoders.Hex.DecodeData(vm.RootFingerprint));
}
}
strategy = newStrategy;
strategy.Source = vm.Source;
vm.DerivationScheme = strategy.AccountDerivation.ToString();
}
}
else
{
strategy = null;
2019-05-08 20:37:37 +02:00
}
}
catch
{
ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid Derivation Scheme");
vm.Confirmation = false;
2020-06-28 17:55:27 +09:00
return View(nameof(AddDerivationScheme), vm);
2019-05-08 20:37:37 +02:00
}
}
2019-05-08 20:37:37 +02:00
var oldConfig = vm.Config;
vm.Config = strategy == null ? null : strategy.ToJson();
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
var exisingStrategy = store.GetSupportedPaymentMethods(_NetworkProvider)
.Where(c => c.PaymentId == paymentMethodId)
.OfType<DerivationSchemeSettings>()
.FirstOrDefault();
2018-10-12 13:17:38 +09:00
var storeBlob = store.GetStoreBlob();
var wasExcluded = storeBlob.GetExcludedPaymentMethods().Match(paymentMethodId);
var willBeExcluded = !vm.Enabled;
2018-08-01 15:59:29 +09:00
2018-10-12 13:17:38 +09:00
var showAddress = // Show addresses if:
// - If the user is testing the hint address in confirmation screen
2019-05-08 20:37:37 +02:00
(vm.Confirmation && !string.IsNullOrWhiteSpace(vm.HintAddress)) ||
// - The user is clicking on continue after changing the config
(!vm.Confirmation && oldConfig != vm.Config) ||
// - The user is clickingon continue without changing config nor enabling/disabling
(!vm.Confirmation && oldConfig == vm.Config && willBeExcluded == wasExcluded);
2018-08-01 15:59:29 +09:00
showAddress = showAddress && strategy != null;
2018-08-01 15:59:29 +09:00
if (!showAddress)
{
try
{
if (strategy != null)
await wallet.TrackAsync(strategy.AccountDerivation);
store.SetSupportedPaymentMethod(paymentMethodId, strategy);
storeBlob.SetExcluded(paymentMethodId, willBeExcluded);
2018-08-01 15:59:29 +09:00
store.SetStoreBlob(storeBlob);
}
catch
{
ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid Derivation Scheme");
return View(vm);
}
await _Repo.UpdateStore(store);
_EventAggregator.Publish(new WalletChangedEvent()
{
WalletId = new WalletId(storeId, cryptoCode)
});
2020-06-28 17:55:27 +09:00
if (willBeExcluded != wasExcluded)
{
var label = willBeExcluded ? "disabled" : "enabled";
TempData[WellKnownTempData.SuccessMessage] = $"On-Chain payments for {network.CryptoCode} has been {label}.";
}
else
{
TempData[WellKnownTempData.SuccessMessage] = $"Derivation settings for {network.CryptoCode} has been modified.";
}
return RedirectToAction(nameof(UpdateStore), new { storeId = storeId });
2018-08-01 15:59:29 +09:00
}
else if (!string.IsNullOrEmpty(vm.HintAddress))
2018-03-24 20:40:26 +09:00
{
BitcoinAddress address = null;
try
{
address = BitcoinAddress.Create(vm.HintAddress, network.NBitcoinNetwork);
}
catch
{
ModelState.AddModelError(nameof(vm.HintAddress), "Invalid hint address");
return ShowAddresses(vm, strategy);
}
try
{
var newStrategy = ParseDerivationStrategy(vm.DerivationScheme, address.ScriptPubKey, network);
if (newStrategy.AccountDerivation != strategy.AccountDerivation)
{
strategy.AccountDerivation = newStrategy.AccountDerivation;
strategy.AccountOriginal = null;
}
2018-03-24 20:40:26 +09:00
}
catch
{
ModelState.AddModelError(nameof(vm.HintAddress), "Impossible to find a match with this address");
return ShowAddresses(vm, strategy);
}
2019-05-08 20:37:37 +02:00
2018-03-24 20:40:26 +09:00
vm.HintAddress = "";
TempData[WellKnownTempData.SuccessMessage] =
2019-05-08 20:37:37 +02:00
"Address successfully found, please verify that the rest is correct and click on \"Confirm\"";
2018-03-24 20:40:26 +09:00
ModelState.Remove(nameof(vm.HintAddress));
ModelState.Remove(nameof(vm.DerivationScheme));
}
2019-05-08 20:37:37 +02:00
2018-08-01 15:59:29 +09:00
return ShowAddresses(vm, strategy);
2018-03-24 20:40:26 +09:00
}
2019-11-29 21:24:52 +01:00
[HttpPost]
[Route("{storeId}/derivations/{cryptoCode}/generatenbxwallet")]
2019-12-05 21:07:41 +01:00
public async Task<IActionResult> GenerateNBXWallet(string storeId, string cryptoCode,
GenerateWalletRequest request)
2019-11-29 21:24:52 +01:00
{
var hotWallet = await CanUseHotWallet();
if (!hotWallet.HotWallet || (!hotWallet.RPCImport && request.ImportKeysToRPC))
{
return NotFound();
}
2019-11-29 21:24:52 +01:00
var network = _NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
2019-12-05 21:07:41 +01:00
var client = _ExplorerProvider.GetExplorerClient(cryptoCode);
GenerateWalletResponse response;
try
{
response = await client.GenerateWalletAsync(request);
}
catch (Exception e)
{
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Error,
Html = $"There was an error generating your wallet: {e.Message}"
});
2020-06-28 17:55:27 +09:00
return RedirectToAction(nameof(AddDerivationScheme), new { storeId, cryptoCode });
}
2020-06-28 17:55:27 +09:00
if (response == null)
{
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Error,
Html = "There was an error generating your wallet. Is your node available?"
});
2020-06-28 17:55:27 +09:00
return RedirectToAction(nameof(AddDerivationScheme), new { storeId, cryptoCode });
}
2019-12-05 21:07:41 +01:00
var store = HttpContext.GetStoreData();
var result = await AddDerivationScheme(storeId,
new DerivationSchemeViewModel()
{
Confirmation = string.IsNullOrEmpty(request.ExistingMnemonic),
2019-12-05 21:07:41 +01:00
Network = network,
RootFingerprint = response.AccountKeyPath.MasterFingerprint.ToString(),
RootKeyPath = network.GetRootKeyPath(),
CryptoCode = cryptoCode,
DerivationScheme = response.DerivationScheme.ToString(),
Source = "NBXplorer",
AccountKey = response.AccountHDKey.Neuter().ToWif(),
DerivationSchemeFormat = "BTCPay",
KeyPath = response.AccountKeyPath.KeyPath.ToString(),
Enabled = !store.GetStoreBlob()
.IsExcluded(new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike))
}, cryptoCode);
if (!ModelState.IsValid || !(result is RedirectToActionResult))
return result;
TempData.Clear();
if (string.IsNullOrEmpty(request.ExistingMnemonic))
2019-12-05 21:07:41 +01:00
{
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Success,
2020-04-13 15:57:57 +09:00
Html = $"Your wallet has been generated. Please store your seed securely! <br/><code class=\"alert-link\">{response.Mnemonic}</code>"
});
}
else
{
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Warning,
Html = "Please check your addresses and confirm"
});
}
2019-12-05 21:07:41 +01:00
return result;
2019-11-29 21:24:52 +01:00
}
private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet()
{
var isAdmin = (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings)).Succeeded;
if (isAdmin)
return (true, true);
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>();
var hotWallet = policies?.AllowHotWalletForAll is true;
return (hotWallet, hotWallet && policies?.AllowHotWalletRPCImportForAll is true);
}
private async Task<string> ReadAllText(IFormFile file)
{
using (var stream = new StreamReader(file.OpenReadStream()))
{
return await stream.ReadToEndAsync();
}
}
2018-03-24 20:40:26 +09:00
2020-06-28 17:55:27 +09:00
private IActionResult
2019-12-24 08:20:44 +01:00
ShowAddresses(DerivationSchemeViewModel vm, DerivationSchemeSettings strategy)
2018-03-24 20:40:26 +09:00
{
vm.DerivationScheme = strategy.AccountDerivation.ToString();
2019-08-17 15:14:31 +09:00
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
2018-03-24 20:40:26 +09:00
if (!string.IsNullOrEmpty(vm.DerivationScheme))
{
2019-08-17 15:14:31 +09:00
var line = strategy.AccountDerivation.GetLineFor(deposit);
2018-03-24 20:40:26 +09:00
for (int i = 0; i < 10; i++)
{
var keyPath = deposit.GetKeyPath((uint)i);
var rootedKeyPath = vm.GetAccountKeypath()?.Derive(keyPath);
2019-12-24 08:20:44 +01:00
var derivation = line.Derive((uint)i);
var address = strategy.Network.NBXplorerNetwork.CreateAddress(strategy.AccountDerivation,
line.KeyPathTemplate.GetKeyPath((uint)i),
derivation.ScriptPubKey).ToString();
vm.AddressSamples.Add((keyPath.ToString(), address, rootedKeyPath));
}
}
2018-03-24 20:40:26 +09:00
vm.Confirmation = true;
ModelState.Remove(nameof(vm.Config)); // Remove the cached value
2020-06-28 17:55:27 +09:00
return View(nameof(AddDerivationScheme), vm);
}
}
}