Wallet prep work for BPU (#1331)

* Wallet prep work for BPU

This PR prepares the wallet for #1321. It makes transfers from the vault and ledger to go to their own post actions for processing (not particularly useful in this PR but is needed in BPU to propose a new tx)  It also makes the Sign with seed consistent with redirect to /psbt/ready after signing which it did not do (it stayed on the seed route)

* fix test

* add assert
This commit is contained in:
Andrew Camilleri 2020-02-13 14:06:00 +01:00 committed by GitHub
parent 07f0d95f56
commit db6a4687d2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 157 additions and 95 deletions

View file

@ -71,7 +71,7 @@ namespace BTCPayServer.Tests
BitcoinAddress.Create(vmLedger.HintChange, user.SupportedNetwork.NBitcoinNetwork); BitcoinAddress.Create(vmLedger.HintChange, user.SupportedNetwork.NBitcoinNetwork);
Assert.NotNull(vmLedger.WebsocketPath); 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 vmPSBT = await walletController.WalletPSBT(walletId, new WalletPSBTViewModel() { PSBT = redirectedPSBT }).AssertViewModelAsync<WalletPSBTViewModel>();
var unsignedPSBT = PSBT.Parse(vmPSBT.PSBT, user.SupportedNetwork.NBitcoinNetwork); var unsignedPSBT = PSBT.Parse(vmPSBT.PSBT, user.SupportedNetwork.NBitcoinNetwork);
Assert.NotNull(vmPSBT.Decoded); Assert.NotNull(vmPSBT.Decoded);
@ -80,14 +80,20 @@ namespace BTCPayServer.Tests
PSBT.Load(filePSBT.FileContents, user.SupportedNetwork.NBitcoinNetwork); PSBT.Load(filePSBT.FileContents, user.SupportedNetwork.NBitcoinNetwork);
await walletController.WalletPSBT(walletId, vmPSBT, "ledger").AssertViewModelAsync<WalletSendLedgerModel>(); 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.NotEmpty(vmPSBT2.Inputs.Where(i => i.Error != null));
Assert.Equal(vmPSBT.PSBT, vmPSBT2.PSBT); Assert.Equal(vmPSBT.PSBT, vmPSBT2.PSBT);
var signedPSBT = unsignedPSBT.Clone(); var signedPSBT = unsignedPSBT.Clone();
signedPSBT.SignAll(user.DerivationScheme, user.ExtKey); signedPSBT.SignAll(user.DerivationScheme, user.ExtKey);
vmPSBT.PSBT = signedPSBT.ToBase64(); 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.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.Destination == sendDestination && !d.Positive);
Assert.Contains(psbtReady.Destinations, d => 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>(); var combineVM = await walletController.WalletPSBT(walletId, vmPSBT, "combine").AssertViewModelAsync<WalletPSBTCombineViewModel>();
Assert.Equal(vmPSBT.PSBT, combineVM.OtherPSBT); Assert.Equal(vmPSBT.PSBT, combineVM.OtherPSBT);
combineVM.PSBT = signedPSBT.ToBase64(); 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); var signedPSBT2 = PSBT.Parse(psbt, user.SupportedNetwork.NBitcoinNetwork);
Assert.True(signedPSBT.TryFinalize(out _)); Assert.True(signedPSBT.TryFinalize(out _));
@ -108,7 +114,7 @@ namespace BTCPayServer.Tests
// Can use uploaded file? // Can use uploaded file?
combineVM.PSBT = null; combineVM.PSBT = null;
combineVM.UploadedPSBTFile = TestUtils.GetFormFile("signedPSBT", signedPSBT.ToBytes()); 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); signedPSBT2 = PSBT.Parse(psbt, user.SupportedNetwork.NBitcoinNetwork);
Assert.True(signedPSBT.TryFinalize(out _)); Assert.True(signedPSBT.TryFinalize(out _));
Assert.True(signedPSBT2.TryFinalize(out _)); Assert.True(signedPSBT2.TryFinalize(out _));
@ -116,17 +122,18 @@ namespace BTCPayServer.Tests
var ready = (await walletController.WalletPSBTReady(walletId, signedPSBT.ToBase64())).AssertViewModel<WalletPSBTReadyViewModel>(); var ready = (await walletController.WalletPSBTReady(walletId, signedPSBT.ToBase64())).AssertViewModel<WalletPSBTReadyViewModel>();
Assert.Equal(signedPSBT.ToBase64(), ready.PSBT); 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); Assert.Equal(signedPSBT.ToBase64(), psbt);
redirect = Assert.IsType<RedirectToActionResult>(await walletController.WalletPSBTReady(walletId, ready, command: "broadcast")); redirect = Assert.IsType<RedirectToActionResult>(await walletController.WalletPSBTReady(walletId, ready, command: "broadcast"));
Assert.Equal(nameof(walletController.WalletTransactions), redirect.ActionName); 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 postRedirectView = Assert.IsType<ViewResult>(view);
var postRedirectViewModel = Assert.IsType<PostRedirectViewModel>(postRedirectView.Model); var postRedirectViewModel = Assert.IsType<PostRedirectViewModel>(postRedirectView.Model);
Assert.Equal(actionName, postRedirectViewModel.AspAction);
var redirectedPSBT = postRedirectViewModel.Parameters.Single(p => p.Key == "psbt").Value; var redirectedPSBT = postRedirectViewModel.Parameters.Single(p => p.Key == "psbt").Value;
return redirectedPSBT; return redirectedPSBT;
} }

View file

@ -566,9 +566,10 @@ namespace BTCPayServer.Tests
Assert.Contains(jack.ToString(), s.Driver.PageSource); Assert.Contains(jack.ToString(), s.Driver.PageSource);
Assert.Contains("0.01000000", s.Driver.PageSource); Assert.Contains("0.01000000", s.Driver.PageSource);
s.Driver.FindElement(By.CssSelector("button[value=analyze-psbt]")).ForceClick(); 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("#OtherActions")).ForceClick();
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick(); s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
Assert.EndsWith("psbt", s.Driver.Url); Assert.EndsWith("psbt/ready", s.Driver.Url);
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick(); s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
Assert.Equal(walletTransactionLink, s.Driver.Url); Assert.Equal(walletTransactionLink, s.Driver.Url);

View file

@ -20,7 +20,6 @@ namespace BTCPayServer.Controllers
{ {
var nbx = ExplorerClientProvider.GetExplorerClient(network); var nbx = ExplorerClientProvider.GetExplorerClient(network);
CreatePSBTRequest psbtRequest = new CreatePSBTRequest(); CreatePSBTRequest psbtRequest = new CreatePSBTRequest();
foreach (var transactionOutput in sendModel.Outputs) foreach (var transactionOutput in sendModel.Outputs)
{ {
var psbtDestination = new CreatePSBTDestination(); var psbtDestination = new CreatePSBTDestination();
@ -65,7 +64,7 @@ namespace BTCPayServer.Controllers
vm.Decoded = psbt.ToString(); vm.Decoded = psbt.ToString();
vm.PSBT = psbt.ToBase64(); vm.PSBT = psbt.ToBase64();
} }
return View(vm ?? new WalletPSBTViewModel() { CryptoCode = walletId.CryptoCode }); return View(nameof(WalletPSBT), vm ?? new WalletPSBTViewModel() { CryptoCode = walletId.CryptoCode });
} }
[HttpPost] [HttpPost]
[Route("{walletId}/psbt")] [Route("{walletId}/psbt")]
@ -107,7 +106,7 @@ namespace BTCPayServer.Controllers
return View(vm); return View(vm);
} }
TempData[WellKnownTempData.SuccessMessage] = "PSBT updated!"; TempData[WellKnownTempData.SuccessMessage] = "PSBT updated!";
return RedirectToWalletPSBT(walletId, psbt, vm.FileName); return RedirectToWalletPSBT(psbt, vm.FileName);
case "seed": case "seed":
return SignWithSeed(walletId, psbt.ToBase64()); return SignWithSeed(walletId, psbt.ToBase64());
case "nbx-seed": case "nbx-seed":
@ -125,7 +124,7 @@ namespace BTCPayServer.Controllers
return View(vm); return View(vm);
case "broadcast": case "broadcast":
{ {
return await WalletPSBTReady(walletId, psbt.ToBase64()); return RedirectToWalletPSBTReady(psbt.ToBase64());
} }
case "combine": case "combine":
ModelState.Remove(nameof(vm.PSBT)); ModelState.Remove(nameof(vm.PSBT));
@ -162,7 +161,6 @@ namespace BTCPayServer.Controllers
var vm = new WalletPSBTReadyViewModel() { PSBT = psbt }; var vm = new WalletPSBTReadyViewModel() { PSBT = psbt };
vm.SigningKey = signingKey; vm.SigningKey = signingKey;
vm.SigningKeyPath = signingKeyPath; vm.SigningKeyPath = signingKeyPath;
var derivationSchemeSettings = GetDerivationSchemeSettings(walletId); var derivationSchemeSettings = GetDerivationSchemeSettings(walletId);
if (derivationSchemeSettings == null) if (derivationSchemeSettings == null)
return NotFound(); return NotFound();
@ -224,7 +222,7 @@ namespace BTCPayServer.Controllers
vm.CanCalculateBalance = true; vm.CanCalculateBalance = true;
vm.Positive = balanceChange >= Money.Zero; vm.Positive = balanceChange >= Money.Zero;
} }
vm.Inputs = new List<WalletPSBTReadyViewModel.InputViewModel>();
foreach (var input in psbtObject.Inputs) foreach (var input in psbtObject.Inputs)
{ {
var inputVm = new WalletPSBTReadyViewModel.InputViewModel(); var inputVm = new WalletPSBTReadyViewModel.InputViewModel();
@ -237,7 +235,7 @@ namespace BTCPayServer.Controllers
inputVm.Positive = balanceChange2 >= Money.Zero; inputVm.Positive = balanceChange2 >= Money.Zero;
inputVm.Index = (int)input.Index; inputVm.Index = (int)input.Index;
} }
vm.Destinations = new List<WalletPSBTReadyViewModel.DestinationViewModel>();
foreach (var output in psbtObject.Outputs) foreach (var output in psbtObject.Outputs)
{ {
var dest = new WalletPSBTReadyViewModel.DestinationViewModel(); var dest = new WalletPSBTReadyViewModel.DestinationViewModel();
@ -297,14 +295,14 @@ namespace BTCPayServer.Controllers
catch catch
{ {
vm.GlobalError = "Invalid PSBT"; vm.GlobalError = "Invalid PSBT";
return View(vm); return View(nameof(WalletPSBTReady),vm);
} }
if (command == "broadcast") if (command == "broadcast")
{ {
if (!psbt.IsAllFinalized() && !psbt.TryFinalize(out var errors)) if (!psbt.IsAllFinalized() && !psbt.TryFinalize(out var errors))
{ {
vm.SetErrors(errors); vm.SetErrors(errors);
return View(vm); return View(nameof(WalletPSBTReady),vm);
} }
var transaction = psbt.ExtractTransaction(); var transaction = psbt.ExtractTransaction();
try try
@ -313,24 +311,24 @@ namespace BTCPayServer.Controllers
if (!broadcastResult.Success) if (!broadcastResult.Success)
{ {
vm.GlobalError = $"RPC Error while broadcasting: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}"; vm.GlobalError = $"RPC Error while broadcasting: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}";
return View(vm); return View(nameof(WalletPSBTReady),vm);
} }
} }
catch (Exception ex) catch (Exception ex)
{ {
vm.GlobalError = "Error while broadcasting: " + ex.Message; vm.GlobalError = "Error while broadcasting: " + ex.Message;
return View(vm); return View(nameof(WalletPSBTReady),vm);
} }
return RedirectToWalletTransaction(walletId, transaction); return RedirectToWalletTransaction(walletId, transaction);
} }
else if (command == "analyze-psbt") else if (command == "analyze-psbt")
{ {
return RedirectToWalletPSBT(walletId, psbt); return RedirectToWalletPSBT(psbt);
} }
else else
{ {
vm.GlobalError = "Unknown command"; vm.GlobalError = "Unknown command";
return View(vm); return View(nameof(WalletPSBTReady),vm);
} }
} }
@ -359,7 +357,7 @@ namespace BTCPayServer.Controllers
} }
sourcePSBT = sourcePSBT.Combine(psbt); sourcePSBT = sourcePSBT.Combine(psbt);
TempData[WellKnownTempData.SuccessMessage] = "PSBT Successfully combined!"; TempData[WellKnownTempData.SuccessMessage] = "PSBT Successfully combined!";
return RedirectToWalletPSBT(walletId, sourcePSBT); return RedirectToWalletPSBT(sourcePSBT);
} }
} }
} }

View file

@ -1,6 +1,5 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
using System.Net.WebSockets; using System.Net.WebSockets;
@ -8,7 +7,6 @@ using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Data; using BTCPayServer.Data;
using BTCPayServer.Events;
using BTCPayServer.HostedServices; using BTCPayServer.HostedServices;
using BTCPayServer.ModelBinders; using BTCPayServer.ModelBinders;
using BTCPayServer.Models; using BTCPayServer.Models;
@ -19,21 +17,15 @@ using BTCPayServer.Services;
using BTCPayServer.Services.Rates; using BTCPayServer.Services.Rates;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets; using BTCPayServer.Services.Wallets;
using LedgerWallet;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using NBitcoin; using NBitcoin;
using NBitcoin.DataEncoders; using NBitcoin.DataEncoders;
using NBXplorer; using NBXplorer;
using NBXplorer.DerivationStrategy; using NBXplorer.DerivationStrategy;
using NBXplorer.Models; using NBXplorer.Models;
using Newtonsoft.Json; using Newtonsoft.Json;
using static BTCPayServer.Controllers.StoresController;
namespace BTCPayServer.Controllers namespace BTCPayServer.Controllers
{ {
@ -420,7 +412,7 @@ namespace BTCPayServer.Controllers
var balance = _walletProvider.GetWallet(network).GetBalance(paymentMethod.AccountDerivation); var balance = _walletProvider.GetWallet(network).GetBalance(paymentMethod.AccountDerivation);
model.NBXSeedAvailable = await CanUseHotWallet() && !string.IsNullOrEmpty(await ExplorerClientProvider.GetExplorerClient(network) model.NBXSeedAvailable = await CanUseHotWallet() && !string.IsNullOrEmpty(await ExplorerClientProvider.GetExplorerClient(network)
.GetMetadataAsync<string>(GetDerivationSchemeSettings(walletId).AccountDerivation, .GetMetadataAsync<string>(GetDerivationSchemeSettings(walletId).AccountDerivation,
WellknownMetadataKeys.Mnemonic)); WellknownMetadataKeys.MasterHDKey));
model.CurrentBalance = await balance; model.CurrentBalance = await balance;
model.RecommendedSatoshiPerByte = (int)(await recommendedFees).GetFee(1).Satoshi; model.RecommendedSatoshiPerByte = (int)(await recommendedFees).GetFee(1).Satoshi;
model.FeeSatoshiPerByte = model.RecommendedSatoshiPerByte; model.FeeSatoshiPerByte = model.RecommendedSatoshiPerByte;
@ -593,7 +585,7 @@ namespace BTCPayServer.Controllers
case "analyze-psbt": case "analyze-psbt":
var name = var name =
$"Send-{string.Join('_', vm.Outputs.Select(output => $"{output.Amount}->{output.DestinationAddress}{(output.SubtractFeesFromOutput ? "-Fees" : string.Empty)}"))}.psbt"; $"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: default:
return View(vm); return View(vm);
} }
@ -651,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() var vm = new PostRedirectViewModel()
{ {
@ -700,6 +716,14 @@ namespace BTCPayServer.Controllers
}); });
} }
[HttpPost]
[Route("{walletId}/ledger")]
public async Task<IActionResult> SubmitLedger([ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId, WalletSendLedgerModel model)
{
return RedirectToWalletPSBTReady(model.PSBT);
}
[HttpGet("{walletId}/psbt/seed")] [HttpGet("{walletId}/psbt/seed")]
public IActionResult SignWithSeed([ModelBinder(typeof(WalletIdModelBinder))] public IActionResult SignWithSeed([ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId,string psbt) WalletId walletId,string psbt)
@ -773,7 +797,7 @@ namespace BTCPayServer.Controllers
return View(viewModel); return View(viewModel);
} }
ModelState.Remove(nameof(viewModel.PSBT)); 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) private bool PSBTChanged(PSBT psbt, Action act)

View file

@ -28,7 +28,7 @@
{ {
<h3>Decoded PSBT</h3> <h3>Decoded PSBT</h3>
<div class="form-group"> <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="CryptoCode" />
<input type="hidden" asp-for="NBXSeedAvailable" /> <input type="hidden" asp-for="NBXSeedAvailable" />
<input type="hidden" asp-for="PSBT" /> <input type="hidden" asp-for="PSBT" />
@ -65,7 +65,7 @@
<pre><code class="json">@Model.Decoded</code></pre> <pre><code class="json">@Model.Decoded</code></pre>
} }
<h3>PSBT to decode</h3> <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"> <div class="form-group">
<textarea class="form-control" rows="5" asp-for="PSBT"></textarea> <textarea class="form-control" rows="5" asp-for="PSBT"></textarea>
<span asp-validation-for="PSBT" class="text-danger"></span> <span asp-validation-for="PSBT" class="text-danger"></span>

View file

@ -2,13 +2,24 @@
@{ @{
Layout = "../Shared/_Layout.cshtml"; Layout = "../Shared/_Layout.cshtml";
} }
<section> <section>
<div class="container"> <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) @if (Model.GlobalError != null)
{ {
<div class="alert alert-danger alert-dismissible" role="alert"> <div class="alert alert-danger alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button> <button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span>@Model.GlobalError</span><br /> <span aria-hidden="true">&times;</span>
</button>
<span>@Model.GlobalError</span><br/>
</div> </div>
} }
<div class="row"> <div class="row">
@ -18,14 +29,16 @@
@if (Model.CanCalculateBalance) @if (Model.CanCalculateBalance)
{ {
<p> <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> <span style="color:green;">@Model.BalanceChange</span>
} }
else else
{ {
<span style="color:red;">@Model.BalanceChange</span> <span style="color:red;">@Model.BalanceChange</span>
}, do you want to continue? }
, do you want to continue?
</p> </p>
} }
else else
@ -53,7 +66,9 @@
<tr> <tr>
@if (input.Error != null) @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> <td style="text-align:left">
@input.Index <span class="fa fa-exclamation-triangle" style="color:red;" title="@input.Error"></span>
</td>
} }
else else
{ {
@ -122,12 +137,13 @@
<div class="row"> <div class="row">
<div class="col-lg-12 text-center"> <div class="col-lg-12 text-center">
<form method="post" asp-action="WalletPSBTReady" asp-route-walletId="@this.Context.GetRouteValue("walletId")"> <form method="post" asp-action="WalletPSBTReady" asp-route-walletId="@this.Context.GetRouteValue("walletId")">
<input type="hidden" asp-for="PSBT" /> <input type="hidden" asp-for="PSBT"/>
<input type="hidden" asp-for="SigningKey" /> <input type="hidden" asp-for="SigningKey"/>
<input type="hidden" asp-for="SigningKeyPath" /> <input type="hidden" asp-for="SigningKeyPath"/>
@if (!Model.HasErrors) @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> <button type="submit" class="btn btn-secondary" name="command" value="analyze-psbt">Export as PSBT</button>
</form> </form>

View file

@ -6,14 +6,22 @@
} }
<h4>Sign the transaction with Ledger</h4> <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"> <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">&times;</span></button> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<span id="alertMessage"></span> <span id="alertMessage"></span>
</div> </div>
<div class="row"> <div class="row">
<div class="col-md-10"> <div 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="SubmitLedger" asp-route-walletId="@this.Context.GetRouteValue("walletId")" method="post" style="display:none;">
<input type="hidden" id="PSBT" asp-for="PSBT" /> <input type="hidden" id="PSBT" asp-for="PSBT" value="@Model.PSBT"/>
<input type="hidden" asp-for="HintChange" /> <input type="hidden" asp-for="HintChange" />
<input type="hidden" asp-for="WebsocketPath" /> <input type="hidden" asp-for="WebsocketPath" />
</form> </form>

View file

@ -6,15 +6,23 @@
} }
<h4>Sign the transaction with BTCPayServer Vault</h4> <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"> <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">&times;</span></button> <button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<span id="alertMessage"></span> <span id="alertMessage"></span>
</div> </div>
<div class="row"> <div class="row">
<div id="body" class="col-md-10"> <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="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" /> <input type="hidden" asp-for="WebsocketPath" />
</form> </form>
<div id="vaultPlaceholder"></div> <div id="vaultPlaceholder"></div>