btcpayserver/BTCPayServer/Controllers/WalletsController.cs

645 lines
29 KiB
C#
Raw Normal View History

2018-07-26 15:32:24 +02:00
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.ModelBinders;
using BTCPayServer.Models;
using BTCPayServer.Models.WalletViewModels;
using BTCPayServer.Security;
using BTCPayServer.Services;
2018-07-26 16:23:28 +02:00
using BTCPayServer.Services.Rates;
2018-07-26 15:32:24 +02:00
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.Extensions.Options;
using NBitcoin;
using NBXplorer.DerivationStrategy;
2018-10-26 16:07:39 +02:00
using NBXplorer.Models;
2018-07-26 15:32:24 +02:00
using Newtonsoft.Json;
using static BTCPayServer.Controllers.StoresController;
namespace BTCPayServer.Controllers
{
[Route("wallets")]
[Authorize(AuthenticationSchemes = Policies.CookieAuthentication)]
[AutoValidateAntiforgeryToken]
public partial class WalletsController : Controller
2018-07-26 15:32:24 +02:00
{
public StoreRepository Repository { get; }
public BTCPayNetworkProvider NetworkProvider { get; }
public ExplorerClientProvider ExplorerClientProvider { get; }
2018-07-26 15:32:24 +02:00
private readonly UserManager<ApplicationUser> _userManager;
private readonly IOptions<MvcJsonOptions> _mvcJsonOptions;
private readonly NBXplorerDashboard _dashboard;
2018-07-26 15:32:24 +02:00
private readonly IFeeProviderFactory _feeRateProvider;
private readonly BTCPayWalletProvider _walletProvider;
public RateFetcher RateFetcher { get; }
[TempData]
public string StatusMessage { get; set; }
2018-07-26 16:23:28 +02:00
CurrencyNameTable _currencyTable;
2018-07-26 15:32:24 +02:00
public WalletsController(StoreRepository repo,
2018-07-26 16:23:28 +02:00
CurrencyNameTable currencyTable,
2018-07-26 15:32:24 +02:00
BTCPayNetworkProvider networkProvider,
UserManager<ApplicationUser> userManager,
IOptions<MvcJsonOptions> mvcJsonOptions,
NBXplorerDashboard dashboard,
RateFetcher rateProvider,
2018-07-26 15:32:24 +02:00
ExplorerClientProvider explorerProvider,
IFeeProviderFactory feeRateProvider,
BTCPayWalletProvider walletProvider)
{
2018-07-26 16:23:28 +02:00
_currencyTable = currencyTable;
Repository = repo;
RateFetcher = rateProvider;
NetworkProvider = networkProvider;
2018-07-26 15:32:24 +02:00
_userManager = userManager;
_mvcJsonOptions = mvcJsonOptions;
_dashboard = dashboard;
ExplorerClientProvider = explorerProvider;
2018-07-26 15:32:24 +02:00
_feeRateProvider = feeRateProvider;
_walletProvider = walletProvider;
}
public async Task<IActionResult> ListWallets()
{
var wallets = new ListWalletsViewModel();
var stores = await Repository.GetStoresByUserId(GetUserId());
2018-07-26 15:32:24 +02:00
var onChainWallets = stores
.SelectMany(s => s.GetSupportedPaymentMethods(NetworkProvider)
.OfType<DerivationSchemeSettings>()
2018-07-26 15:32:24 +02:00
.Select(d => ((Wallet: _walletProvider.GetWallet(d.Network),
DerivationStrategy: d.AccountDerivation,
2018-07-26 15:32:24 +02:00
Network: d.Network)))
.Where(_ => _.Wallet != null)
.Select(_ => (Wallet: _.Wallet,
Store: s,
Balance: GetBalanceString(_.Wallet, _.DerivationStrategy),
DerivationStrategy: _.DerivationStrategy,
Network: _.Network)))
.ToList();
foreach (var wallet in onChainWallets)
{
ListWalletsViewModel.WalletViewModel walletVm = new ListWalletsViewModel.WalletViewModel();
wallets.Wallets.Add(walletVm);
walletVm.Balance = await wallet.Balance + " " + wallet.Wallet.Network.CryptoCode;
if (!wallet.Store.HasClaim(Policies.CanModifyStoreSettings.Key))
{
walletVm.Balance = "";
}
walletVm.CryptoCode = wallet.Network.CryptoCode;
walletVm.StoreId = wallet.Store.Id;
walletVm.Id = new WalletId(wallet.Store.Id, wallet.Network.CryptoCode);
walletVm.StoreName = wallet.Store.StoreName;
walletVm.IsOwner = wallet.Store.HasClaim(Policies.CanModifyStoreSettings.Key);
}
return View(wallets);
}
[HttpGet]
[Route("{walletId}")]
public async Task<IActionResult> WalletTransactions(
2018-07-26 15:32:24 +02:00
[ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId)
{
var store = await Repository.FindStore(walletId.StoreId, GetUserId());
DerivationSchemeSettings paymentMethod = GetPaymentMethod(walletId, store);
if (paymentMethod == null)
2018-07-26 15:32:24 +02:00
return NotFound();
var wallet = _walletProvider.GetWallet(paymentMethod.Network);
var transactions = await wallet.FetchTransactions(paymentMethod.AccountDerivation);
var model = new ListTransactionsViewModel();
foreach (var tx in transactions.UnconfirmedTransactions.Transactions.Concat(transactions.ConfirmedTransactions.Transactions))
{
var vm = new ListTransactionsViewModel.TransactionViewModel();
model.Transactions.Add(vm);
vm.Id = tx.TransactionId.ToString();
vm.Link = string.Format(CultureInfo.InvariantCulture, paymentMethod.Network.BlockExplorerLink, vm.Id);
vm.Timestamp = tx.Timestamp;
vm.Positive = tx.BalanceChange >= Money.Zero;
vm.Balance = tx.BalanceChange.ToString();
vm.IsConfirmed = tx.Confirmations != 0;
}
model.Transactions = model.Transactions.OrderByDescending(t => t.Timestamp).ToList();
return View(model);
}
2018-07-26 15:32:24 +02:00
[HttpGet]
[Route("{walletId}/send")]
public async Task<IActionResult> WalletSend(
[ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId, string defaultDestination = null, string defaultAmount = null)
{
if (walletId?.StoreId == null)
return NotFound();
var store = await Repository.FindStore(walletId.StoreId, GetUserId());
DerivationSchemeSettings paymentMethod = GetPaymentMethod(walletId, store);
2018-07-26 15:32:24 +02:00
if (paymentMethod == null)
return NotFound();
var network = this.NetworkProvider.GetNetwork(walletId?.CryptoCode);
if (network == null)
return NotFound();
2018-07-26 16:23:28 +02:00
var storeData = store.GetStoreBlob();
var rateRules = store.GetStoreBlob().GetRateRules(NetworkProvider);
rateRules.Spread = 0.0m;
2018-07-26 18:17:43 +02:00
var currencyPair = new Rating.CurrencyPair(paymentMethod.PaymentId.CryptoCode, GetCurrencyCode(storeData.DefaultLang) ?? "USD");
WalletSendModel model = new WalletSendModel()
{
Destination = defaultDestination,
CryptoCode = walletId.CryptoCode
};
if (double.TryParse(defaultAmount, out var amount))
model.Amount = (decimal)amount;
var feeProvider = _feeRateProvider.CreateFeeProvider(network);
var recommendedFees = feeProvider.GetFeeRateAsync();
var balance = _walletProvider.GetWallet(network).GetBalance(paymentMethod.AccountDerivation);
model.CurrentBalance = (await balance).ToDecimal(MoneyUnit.BTC);
model.RecommendedSatoshiPerByte = (int)(await recommendedFees).GetFee(1).Satoshi;
model.FeeSatoshiPerByte = model.RecommendedSatoshiPerByte;
model.SupportRBF = network.SupportRBF;
2018-07-26 16:23:28 +02:00
using (CancellationTokenSource cts = new CancellationTokenSource())
{
try
{
cts.CancelAfter(TimeSpan.FromSeconds(5));
var result = await RateFetcher.FetchRate(currencyPair, rateRules, cts.Token).WithCancellation(cts.Token);
if (result.BidAsk != null)
2018-07-26 16:23:28 +02:00
{
model.Rate = result.BidAsk.Center;
2018-07-26 16:23:28 +02:00
model.Divisibility = _currencyTable.GetNumberFormatInfo(currencyPair.Right, true).CurrencyDecimalDigits;
model.Fiat = currencyPair.Right;
}
else
{
model.RateError = $"{result.EvaluatedRule} ({string.Join(", ", result.Errors.OfType<object>().ToArray())})";
}
2018-07-26 16:23:28 +02:00
}
catch (Exception ex) { model.RateError = ex.Message; }
2018-07-26 16:23:28 +02:00
}
2018-07-26 15:32:24 +02:00
return View(model);
}
[HttpPost]
[Route("{walletId}/send")]
public async Task<IActionResult> WalletSend(
[ModelBinder(typeof(WalletIdModelBinder))]
2019-05-08 07:39:37 +02:00
WalletId walletId, WalletSendModel vm, string command = null, CancellationToken cancellation = default)
{
if (walletId?.StoreId == null)
return NotFound();
var store = await Repository.FindStore(walletId.StoreId, GetUserId());
if (store == null)
return NotFound();
var network = this.NetworkProvider.GetNetwork(walletId?.CryptoCode);
if (network == null)
return NotFound();
vm.SupportRBF = network.SupportRBF;
var destination = ParseDestination(vm.Destination, network.NBitcoinNetwork);
if (destination == null)
ModelState.AddModelError(nameof(vm.Destination), "Invalid address");
if (vm.Amount.HasValue)
{
if (vm.CurrentBalance == vm.Amount.Value && !vm.SubstractFees)
ModelState.AddModelError(nameof(vm.Amount), "You are sending all your balance to the same destination, you should substract the fees");
if (vm.CurrentBalance < vm.Amount.Value)
ModelState.AddModelError(nameof(vm.Amount), "You are sending more than what you own");
}
if (!ModelState.IsValid)
return View(vm);
var storeData = (await Repository.FindStore(walletId.StoreId, GetUserId()));
var derivationScheme = GetPaymentMethod(walletId, storeData);
var psbt = await CreatePSBT(network, derivationScheme, vm, cancellation);
2019-05-08 07:39:37 +02:00
if (command == "ledger")
{
2019-05-11 13:26:31 +02:00
return ViewWalletSendLedger(psbt.PSBT, psbt.ChangeAddress);
2019-05-08 07:39:37 +02:00
}
else
{
2019-05-08 16:58:25 +02:00
try
2019-05-11 13:26:31 +02:00
{
2019-05-10 17:29:29 +02:00
if (command == "analyze-psbt")
return View(nameof(WalletPSBT), new WalletPSBTViewModel()
{
Decoded = psbt.PSBT.ToString(),
PSBT = psbt.PSBT.ToBase64()
});
2019-05-11 13:26:31 +02:00
return FilePSBT(psbt.PSBT, $"Send-{vm.Amount.Value}-{network.CryptoCode}-to-{destination[0].ToString()}.psbt");
2019-05-08 16:58:25 +02:00
}
catch (NBXplorerException ex)
{
ModelState.AddModelError(nameof(vm.Amount), ex.Error.Message);
return View(vm);
}
catch (NotSupportedException)
{
ModelState.AddModelError(nameof(vm.Destination), "You need to update your version of NBXplorer");
return View(vm);
}
2019-05-08 07:39:37 +02:00
}
}
2019-05-11 13:26:31 +02:00
private IActionResult FilePSBT(PSBT psbt, string fileName)
{
return File(psbt.ToBytes(), "application/octet-stream", fileName);
}
private ViewResult ViewWalletSendLedger(PSBT psbt, BitcoinAddress hintChange = null)
{
return View("WalletSendLedger", new WalletSendLedgerModel()
{
PSBT = psbt.ToBase64(),
HintChange = hintChange?.ToString(),
WebsocketPath = this.Url.Action(nameof(LedgerConnection)),
SuccessPath = this.Url.Action(nameof(WalletSendLedgerSuccess))
});
}
2019-05-09 12:34:45 +02:00
[NonAction]
public async Task<CreatePSBTResponse> CreatePSBT(BTCPayNetwork network, DerivationSchemeSettings derivationSettings, WalletSendModel sendModel, CancellationToken cancellationToken)
2019-05-08 07:39:37 +02:00
{
var nbx = ExplorerClientProvider.GetExplorerClient(network);
CreatePSBTRequest psbtRequest = new CreatePSBTRequest();
CreatePSBTDestination psbtDestination = new CreatePSBTDestination();
psbtRequest.Destinations.Add(psbtDestination);
if (network.SupportRBF)
{
psbtRequest.RBF = !sendModel.DisableRBF;
}
2019-05-08 07:39:37 +02:00
psbtDestination.Destination = BitcoinAddress.Create(sendModel.Destination, network.NBitcoinNetwork);
psbtDestination.Amount = Money.Coins(sendModel.Amount.Value);
2019-05-08 07:39:37 +02:00
psbtRequest.FeePreference = new FeePreference();
psbtRequest.FeePreference.ExplicitFeeRate = new FeeRate(Money.Satoshis(sendModel.FeeSatoshiPerByte), 1);
if (sendModel.NoChange)
{
psbtRequest.ExplicitChangeAddress = psbtDestination.Destination;
}
psbtDestination.SubstractFees = sendModel.SubstractFees;
if (derivationSettings.AccountKeyPath != null && derivationSettings.AccountKeyPath.Indexes.Length != 0)
{
psbtRequest.RebaseKeyPaths = new List<PSBTRebaseKeyRules>()
{
new PSBTRebaseKeyRules()
{
AccountKeyPath = derivationSettings.AccountKeyPath,
AccountKey = derivationSettings.AccountKey,
MasterFingerprint = derivationSettings.RootFingerprint is HDFingerprint fp ? fp : default
}
};
}
var psbt = (await nbx.CreatePSBTAsync(derivationSettings.AccountDerivation, psbtRequest, cancellationToken));
if (psbt == null)
throw new NotSupportedException("You need to update your version of NBXplorer");
2019-05-08 07:39:37 +02:00
return psbt;
}
private IDestination[] ParseDestination(string destination, Network network)
{
try
{
2018-11-01 04:54:25 +01:00
destination = destination?.Trim();
return new IDestination[] { BitcoinAddress.Create(destination, network) };
}
catch
{
return null;
}
}
2019-05-10 17:29:29 +02:00
[HttpGet]
[Route("{walletId}/psbt")]
public IActionResult WalletPSBT()
{
return View(new WalletPSBTViewModel());
}
[HttpPost]
[Route("{walletId}/psbt")]
public IActionResult WalletPSBT(
[ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId,
WalletPSBTViewModel vm)
{
try
{
if (!string.IsNullOrEmpty(vm.PSBT))
vm.Decoded = PSBT.Parse(vm.PSBT, NetworkProvider.GetNetwork(walletId.CryptoCode).NBitcoinNetwork).ToString();
}
catch (FormatException ex)
{
ModelState.AddModelError(nameof(vm.PSBT), ex.Message);
}
return View(vm);
}
2019-05-11 13:26:31 +02:00
[HttpPost]
[Route("{walletId}/psbt/sign")]
public IActionResult WalletPSBTSign(
[ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId,
WalletPSBTViewModel vm,
string command = null
)
{
var psbt = PSBT.Parse(vm.PSBT, NetworkProvider.GetNetwork(walletId.CryptoCode).NBitcoinNetwork);
if (command == "ledger")
{
return ViewWalletSendLedger(psbt);
}
else
{
return FilePSBT(psbt, "psbt-export.psbt");
}
}
2018-10-26 16:07:39 +02:00
[HttpGet]
[Route("{walletId}/rescan")]
public async Task<IActionResult> WalletRescan(
[ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId)
{
if (walletId?.StoreId == null)
return NotFound();
var store = await Repository.FindStore(walletId.StoreId, GetUserId());
DerivationSchemeSettings paymentMethod = GetPaymentMethod(walletId, store);
2018-10-26 16:07:39 +02:00
if (paymentMethod == null)
return NotFound();
var vm = new RescanWalletModel();
vm.IsFullySync = _dashboard.IsFullySynched(walletId.CryptoCode, out var unused);
2018-10-26 16:07:39 +02:00
vm.IsServerAdmin = User.Claims.Any(c => c.Type == Policies.CanModifyServerSettings.Key && c.Value == "true");
vm.IsSupportedByCurrency = _dashboard.Get(walletId.CryptoCode)?.Status?.BitcoinStatus?.Capabilities?.CanScanTxoutSet == true;
var explorer = ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode);
var scanProgress = await explorer.GetScanUTXOSetInformationAsync(paymentMethod.AccountDerivation);
if (scanProgress != null)
2018-10-26 16:07:39 +02:00
{
vm.PreviousError = scanProgress.Error;
if (scanProgress.Status == ScanUTXOStatus.Queued || scanProgress.Status == ScanUTXOStatus.Pending)
{
if (scanProgress.Progress == null)
{
vm.Progress = 0;
}
else
{
vm.Progress = scanProgress.Progress.OverallProgress;
vm.RemainingTime = TimeSpan.FromSeconds(scanProgress.Progress.RemainingSeconds).PrettyPrint();
}
}
if (scanProgress.Status == ScanUTXOStatus.Complete)
{
vm.LastSuccess = scanProgress.Progress;
vm.TimeOfScan = (scanProgress.Progress.CompletedAt.Value - scanProgress.Progress.StartedAt).PrettyPrint();
}
}
return View(vm);
}
[HttpPost]
[Route("{walletId}/rescan")]
2018-10-27 15:51:09 +02:00
[Authorize(Policy = Policies.CanModifyServerSettings.Key)]
2018-10-26 16:07:39 +02:00
public async Task<IActionResult> WalletRescan(
[ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId, RescanWalletModel vm)
{
if (walletId?.StoreId == null)
return NotFound();
var store = await Repository.FindStore(walletId.StoreId, GetUserId());
DerivationSchemeSettings paymentMethod = GetPaymentMethod(walletId, store);
2018-10-26 16:07:39 +02:00
if (paymentMethod == null)
return NotFound();
var explorer = ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode);
try
{
await explorer.ScanUTXOSetAsync(paymentMethod.AccountDerivation, vm.BatchSize, vm.GapLimit, vm.StartingIndex);
2018-10-26 16:07:39 +02:00
}
catch (NBXplorerException ex) when (ex.Error.Code == "scanutxoset-in-progress")
{
}
return RedirectToAction();
}
2018-07-26 18:17:43 +02:00
private string GetCurrencyCode(string defaultLang)
{
if (defaultLang == null)
return null;
try
{
var ri = new RegionInfo(defaultLang);
return ri.ISOCurrencySymbol;
}
catch (ArgumentException) { }
2018-07-26 18:17:43 +02:00
return null;
}
private DerivationSchemeSettings GetPaymentMethod(WalletId walletId, StoreData store)
{
if (store == null || !store.HasClaim(Policies.CanModifyStoreSettings.Key))
return null;
var paymentMethod = store
.GetSupportedPaymentMethods(NetworkProvider)
.OfType<DerivationSchemeSettings>()
.FirstOrDefault(p => p.PaymentId.PaymentType == Payments.PaymentTypes.BTCLike && p.PaymentId.CryptoCode == walletId.CryptoCode);
return paymentMethod;
}
2018-07-26 15:32:24 +02:00
private static async Task<string> GetBalanceString(BTCPayWallet wallet, DerivationStrategyBase derivationStrategy)
{
using (CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)))
{
try
{
return (await wallet.GetBalance(derivationStrategy, cts.Token)).ToString();
}
catch
{
return "--";
}
}
}
private string GetUserId()
{
return _userManager.GetUserId(User);
}
[HttpGet]
[Route("{walletId}/send/ledger/success")]
public IActionResult WalletSendLedgerSuccess(
[ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId,
string txid)
2018-07-26 15:32:24 +02:00
{
StatusMessage = $"Transaction broadcasted ({txid})";
return RedirectToAction(nameof(this.WalletTransactions), new { walletId = walletId.ToString() });
2018-07-26 15:32:24 +02:00
}
[HttpGet]
[Route("{walletId}/send/ledger/ws")]
2018-07-26 15:32:24 +02:00
public async Task<IActionResult> LedgerConnection(
[ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId,
2018-07-26 15:32:24 +02:00
string command,
// getinfo
// getxpub
int account = 0,
// sendtoaddress
string psbt = null,
string hintChange = null
2018-07-26 15:32:24 +02:00
)
{
if (!HttpContext.WebSockets.IsWebSocketRequest)
return NotFound();
var network = NetworkProvider.GetNetwork(walletId.CryptoCode);
if (network == null)
throw new FormatException("Invalid value for crypto code");
var storeData = (await Repository.FindStore(walletId.StoreId, GetUserId()));
var derivationSettings = GetPaymentMethod(walletId, storeData);
2018-07-26 15:32:24 +02:00
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
using (var normalOperationTimeout = new CancellationTokenSource())
using (var signTimeout = new CancellationTokenSource())
{
normalOperationTimeout.CancelAfter(TimeSpan.FromMinutes(30));
2019-05-10 07:36:25 +02:00
var hw = new LedgerHardwareWalletService(webSocket);
2019-05-08 07:39:37 +02:00
var model = new WalletSendLedgerModel();
2018-07-26 15:32:24 +02:00
object result = null;
try
{
if (command == "test")
{
result = await hw.Test(normalOperationTimeout.Token);
2018-07-26 15:32:24 +02:00
}
if (command == "sendtoaddress")
{
if (!_dashboard.IsFullySynched(network.CryptoCode, out var summary))
throw new Exception($"{network.CryptoCode}: not started or fully synched");
// Some deployment does not have the AccountKeyPath set, let's fix this...
if (derivationSettings.AccountKeyPath == null)
2018-07-26 15:32:24 +02:00
{
// If the saved wallet key path is not present or incorrect, let's scan the wallet to see if it can sign strategy
2019-05-10 03:55:10 +02:00
var foundKeyPath = await hw.FindKeyPathFromDerivation(network,
derivationSettings.AccountDerivation,
normalOperationTimeout.Token);
derivationSettings.AccountKeyPath = foundKeyPath ?? throw new HardwareWalletException($"This store is not configured to use this ledger");
storeData.SetSupportedPaymentMethod(derivationSettings);
await Repository.UpdateStore(storeData);
}
// If it has already the AccountKeyPath, we did not looked up for it, so we need to check if we are on the right ledger
else
{
// Checking if ledger is right with the RootFingerprint is faster as it does not need to make a query to the parent xpub,
// but some deployment does not have it, so let's use AccountKeyPath instead
if (derivationSettings.RootFingerprint == null)
{
var actualPubKey = await hw.GetExtPubKey(network, derivationSettings.AccountKeyPath, normalOperationTimeout.Token);
if (!derivationSettings.AccountDerivation.GetExtPubKeys().Any(p => p.GetPublicKey() == actualPubKey.GetPublicKey()))
throw new HardwareWalletException($"This store is not configured to use this ledger");
}
// We have the root fingerprint, we can check the root from it
else
{
var actualPubKey = await hw.GetPubKey(network, new KeyPath(), normalOperationTimeout.Token);
if (actualPubKey.GetHDFingerPrint() != derivationSettings.RootFingerprint.Value)
throw new HardwareWalletException($"This store is not configured to use this ledger");
}
}
// Some deployment does not have the RootFingerprint set, let's fix this...
if (derivationSettings.RootFingerprint == null)
{
derivationSettings.RootFingerprint = (await hw.GetPubKey(network, new KeyPath(), normalOperationTimeout.Token)).GetHDFingerPrint();
storeData.SetSupportedPaymentMethod(derivationSettings);
await Repository.UpdateStore(storeData);
}
2018-07-26 15:32:24 +02:00
var psbtResponse = new CreatePSBTResponse()
{
PSBT = PSBT.Parse(psbt, network.NBitcoinNetwork),
ChangeAddress = string.IsNullOrEmpty(hintChange) ? null : BitcoinAddress.Create(hintChange, network.NBitcoinNetwork)
};
2018-07-26 15:32:24 +02:00
signTimeout.CancelAfter(TimeSpan.FromMinutes(5));
psbtResponse.PSBT = await hw.SignTransactionAsync(psbtResponse.PSBT, psbtResponse.ChangeAddress?.ScriptPubKey, signTimeout.Token);
if(!psbtResponse.PSBT.TryFinalize(out var errors))
2018-07-26 15:32:24 +02:00
{
throw new Exception($"Error while finalizing the transaction ({new PSBTException(errors).ToString()})");
}
var transaction = psbtResponse.PSBT.ExtractTransaction();
2018-07-26 15:32:24 +02:00
try
{
2019-05-08 07:39:37 +02:00
var broadcastResult = await ExplorerClientProvider.GetExplorerClient(network).BroadcastAsync(transaction);
if (!broadcastResult.Success)
2018-07-26 15:32:24 +02:00
{
throw new Exception($"RPC Error while broadcasting: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}");
2018-07-26 15:32:24 +02:00
}
}
catch (Exception ex)
{
throw new Exception("Error while broadcasting: " + ex.Message);
}
var wallet = _walletProvider.GetWallet(network);
wallet.InvalidateCache(derivationSettings.AccountDerivation);
2018-07-26 15:32:24 +02:00
result = new SendToAddressResult() { TransactionId = transaction.GetHash().ToString() };
}
}
catch (OperationCanceledException)
{ result = new LedgerTestResult() { Success = false, Error = "Timeout" }; }
catch (Exception ex)
{ result = new LedgerTestResult() { Success = false, Error = ex.Message }; }
finally { hw.Dispose(); }
try
{
if (result != null)
{
UTF8Encoding UTF8NOBOM = new UTF8Encoding(false);
var bytes = UTF8NOBOM.GetBytes(JsonConvert.SerializeObject(result, _mvcJsonOptions.Value.SerializerSettings));
await webSocket.SendAsync(new ArraySegment<byte>(bytes), WebSocketMessageType.Text, true, new CancellationTokenSource(2000).Token);
}
}
catch { }
finally
{
await webSocket.CloseSocket();
}
}
return new EmptyResult();
}
}
public class GetInfoResult
{
}
public class SendToAddressResult
{
public string TransactionId { get; set; }
}
}