Move PSBT flow into wizard

This commit is contained in:
Dennis Reimann
2021-07-27 17:01:00 +02:00
committed by Andrew Camilleri
parent 4371b81ef3
commit 3895b133a3
9 changed files with 269 additions and 202 deletions

View File

@@ -130,23 +130,22 @@ namespace BTCPayServer.Controllers
}
switch (command)
{
case "export":
var viewModel = new WalletPSBTExportViewModel {SigningContext = vm.SigningContext};
viewModel.PSBT = psbt.ToBase64();
viewModel.PSBTHex = psbt.ToHex();
viewModel.CryptoCode = network.CryptoCode;
await FetchTransactionDetails(derivationSchemeSettings, viewModel, network);
return View("WalletPSBTExport", viewModel);
case "decode":
vm.Decoded = psbt.ToString();
ModelState.Remove(nameof(vm.PSBT));
ModelState.Remove(nameof(vm.FileName));
ModelState.Remove(nameof(vm.UploadedPSBTFile));
vm.PSBT = psbt.ToBase64();
vm.PSBT = vm.SigningContext.PSBT;
vm.PSBTHex = psbt.ToHex();
vm.FileName = vm.UploadedPSBTFile?.FileName;
return View(vm);
vm.Decoded = psbt.ToString();
await FetchTransactionDetails(derivationSchemeSettings, vm, network);
return View("WalletPSBTDecoded", vm);
case "export":
vm.PSBT = vm.SigningContext.PSBT;
vm.PSBTHex = psbt.ToHex();
vm.Decoded = psbt.ToString();
await FetchTransactionDetails(derivationSchemeSettings, vm, network);
return View("WalletPSBTExport", vm);
case "update":
psbt = await ExplorerClientProvider.UpdatePSBT(derivationSchemeSettings, psbt);
@@ -172,8 +171,10 @@ namespace BTCPayServer.Controllers
case "combine":
ModelState.Remove(nameof(vm.PSBT));
return View(nameof(WalletPSBTCombine), new WalletPSBTCombineViewModel { OtherPSBT = psbt.ToBase64() });
case "save-psbt":
return FilePSBT(psbt, vm.FileName);
default:
return View(vm);
}

View File

@@ -1,15 +0,0 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Http;
namespace BTCPayServer.Models.WalletViewModels
{
public class WalletPSBTExportViewModel : WalletPSBTReadyViewModel
{
public string CryptoCode { get; set; }
public string PSBTHex { get; set; }
public string PSBT { get; set; }
[Display(Name = "Upload PSBT from file")]
public IFormFile UploadedPSBTFile { get; set; }
}
}

View File

@@ -8,7 +8,7 @@ using NBitcoin;
namespace BTCPayServer.Models.WalletViewModels
{
public class WalletPSBTViewModel
public class WalletPSBTViewModel : WalletPSBTReadyViewModel
{
public string CryptoCode { get; set; }
public string Decoded { get; set; }
@@ -34,8 +34,6 @@ namespace BTCPayServer.Models.WalletViewModels
[Display(Name = "Upload PSBT from file")]
public IFormFile UploadedPSBTFile { get; set; }
public SigningContextModel SigningContext { get; set; } = new SigningContextModel();
public async Task<PSBT> GetPSBT(Network network)
{
if (UploadedPSBTFile != null)

View File

@@ -2,7 +2,7 @@
@addTagHelper *, BundlerMinifier.TagHelpers
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData.SetActivePageAndTitle(WalletsNavPages.PSBT, string.IsNullOrEmpty(Model.Decoded) ? "Decode PSBT" : "Decoded PSBT", Context.GetStoreData().StoreName);
ViewData.SetActivePageAndTitle(WalletsNavPages.PSBT, "Decode PSBT", Context.GetStoreData().StoreName);
}
@section PageHeadContent {
@@ -43,34 +43,8 @@
}
</div>
}
@if (!string.IsNullOrEmpty(Model.Decoded))
{
<div class="form-group">
<form method="post" asp-action="WalletPSBT" asp-route-walletId="@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="d-flex">
<button type="submit" id="SignTransaction" name="command" value="@(Model.NBXSeedAvailable ? "nbx-seed" : "sign")" class="btn btn-primary">Sign transaction</button>
<div class="ms-2 dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" id="OtherActionsDropdownToggle" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Other actions...
</button>
<div class="dropdown-menu" aria-labelledby="OtherActionsDropdownToggle">
<button name="command" type="submit" class="dropdown-item" value="broadcast">Review</button>
<button name="command" type="submit" class="dropdown-item" value="update">Update</button>
<button name="command" type="submit" class="dropdown-item" value="combine">Combine</button>
<button name="command" type="submit" class="dropdown-item" value="save-psbt">Download</button>
<button name="command" type="button" class="dropdown-item only-for-js" data-bs-toggle="modal" data-bs-target="#scan-qr-modal">Show QR</button>
</div>
</div>
</div>
</form>
</div>
<pre><code class="json">@Model.Decoded</code></pre>
}
<p>You can decode a PSBT by either pasting its content or by uploading the file.</p>
<p>You can decode a PSBT by either pasting its content, uploading the file or scanning the wallet QR code.</p>
<form class="form-group" method="post" asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" enctype="multipart/form-data">
<div class="form-group">
<label asp-for="PSBT" class="form-label"></label>
@@ -83,9 +57,10 @@
</div>
<div class="d-flex">
<button type="submit" name="command" value="decode" class="btn btn-primary" id="Decode">Decode PSBT</button>
<button type="button" id="scanqrcode" class="btn btn-primary only-for-js ms-2" data-bs-toggle="modal" data-bs-target="#scanModal">Scan wallet QR with camera</button>
<button type="button" id="scanqrcode" class="btn btn-secondary only-for-js ms-2" data-bs-toggle="modal" data-bs-target="#scanModal">Scan wallet QR with camera</button>
</div>
</form>
</div>
</div>
<partial name="ShowQR"/>

View File

@@ -1,12 +1,21 @@
@model WalletPSBTCombineViewModel
@{
Layout = "../Shared/_NavLayout.cshtml";
Layout = "_LayoutWizard";
ViewData.SetActivePageAndTitle(WalletsNavPages.PSBT, "Combine PSBT", Context.GetStoreData().StoreName);
}
<div class="row">
<div class="col-md-10">
<h4 class="mb-3">@ViewData["PageTitle"]</h4>
@section Navbar {
<a asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" onclick="history.back();return false;">
<vc:icon symbol="back" />
</a>
<a asp-action="WalletSend" asp-route-walletId="@Context.GetRouteValue("walletId")" class="cancel">
<vc:icon symbol="close" />
</a>
}
<header class="text-center">
<h1>@ViewData["PageTitle"]</h1>
</header>
<form class="form-group" method="post" asp-action="WalletPSBTCombine" enctype="multipart/form-data">
<input type="hidden" asp-for="OtherPSBT"/>
@@ -21,5 +30,3 @@
</div>
<button type="submit" class="btn btn-primary">Combine</button>
</form>
</div>
</div>

View File

@@ -0,0 +1,99 @@
@model WalletPSBTViewModel
@addTagHelper *, BundlerMinifier.TagHelpers
@{
Layout = "_LayoutWizard";
ViewData.SetActivePageAndTitle(WalletsNavPages.PSBT, "PSBT", Context.GetStoreData().StoreName);
}
@section PageHeadContent {
<link rel="stylesheet" href="~/vendor/highlightjs/default.min.css" asp-append-version="true">
<link href="~/vendor/vue-qrcode-reader/vue-qrcode-reader.css" rel="stylesheet" asp-append-version="true"/>
}
@section PageFootContent {
<script src="~/vendor/highlightjs/highlight.min.js" asp-append-version="true"></script>
<bundle name="wwwroot/bundles/camera-bundle.min.js"></bundle>
<script>
hljs.initHighlightingOnLoad();
document.addEventListener("DOMContentLoaded", function () {
initQRShow("Scan PSBT", @Json.Serialize(Model.PSBTHex), "scan-qr-modal");
initCameraScanningApp("Scan PSBT", function (data){
$("textarea[name=PSBT]").val(data);
$("#Decode").click();
}, "scanModal");
});
</script>
}
@section Navbar {
<a asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" onclick="history.back();return false;">
<vc:icon symbol="back" />
</a>
<a asp-action="WalletSend" asp-route-walletId="@Context.GetRouteValue("walletId")" class="cancel">
<vc:icon symbol="close" />
</a>
}
<header class="text-center">
<h1>@ViewData["PageTitle"]</h1>
<p class="lead text-secondary mt-3">
You can either sign the PSBT or export and update it.
</p>
@if (Model.CanCalculateBalance)
{
<p class="lead text-secondary mt-3">
If you broadcast this transaction, your balance will change:
<br>
@if (Model.Positive)
{
<span class="text-success">@Model.BalanceChange</span>
}
else
{
<span class="text-danger">@Model.BalanceChange</span>
}
</p>
}
</header>
<partial name="_PSBTInfo" model="Model" />
<div class="form-group">
<form method="post" asp-action="WalletPSBT" asp-route-walletId="@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="d-flex justify-content-center">
<button type="submit" id="SignTransaction" name="command" value="@(Model.NBXSeedAvailable ? "nbx-seed" : "sign")" class="btn btn-primary me-2">Sign transaction</button>
<button type="submit" name="command" value="update" class="btn btn-secondary me-2">Update</button>
<button type="submit" name="command" value="combine" class="btn btn-secondary">Combine</button>
</div>
</form>
</div>
<p class="my-5 text-center">
<a href="#ExportOptions" data-bs-toggle="collapse" class="text-secondary">Show export options</a>
</p>
<div id="ExportOptions" class="collapse">
<h4 class="mb-3">Export PSBT for signing</h4>
<div class="form-group">
<form method="post" asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")">
<input type="hidden" asp-for="CryptoCode"/>
<input type="hidden" asp-for="PSBT"/>
<div class="d-flex">
<button name="command" type="submit" class="btn btn-primary me-2" value="save-psbt">Download PSBT file</button>
<button name="command" type="button" class="btn btn-primary only-for-js" data-bs-toggle="modal" data-bs-target="#scan-qr-modal">Show QR for wallet camera</button>
</div>
</form>
</div>
<h4 class="mt-5 mb-3">Raw version</h4>
<pre><code class="json">@Model.Decoded</code></pre>
<pre><code class="text">@Model.PSBT</code></pre>
</div>
<partial name="ShowQR"/>
<partial name="CameraScanner"/>

View File

@@ -1,7 +1,7 @@
@model WalletPSBTExportViewModel
@model WalletPSBTViewModel
@addTagHelper *, BundlerMinifier.TagHelpers
@{
Layout = "../Shared/_NavLayout.cshtml";
Layout = "_LayoutWizard";
ViewData.SetActivePageAndTitle(WalletsNavPages.PSBT, "PSBT Signing", Context.GetStoreData().StoreName);
}
@@ -22,14 +22,26 @@
</script>
}
<h4 class="mb-3">@ViewData["PageTitle"]</h4>
@section Navbar {
<a asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" onclick="history.back();return false;">
<vc:icon symbol="back" />
</a>
<a asp-action="WalletSend" asp-route-walletId="@Context.GetRouteValue("walletId")" class="cancel">
<vc:icon symbol="close" />
</a>
}
<div class="row">
<div class="col-lg-9 col-xl-8">
<header class="text-center">
<h1>@ViewData["PageTitle"]</h1>
<p class="lead text-secondary mt-3">
Export the PSBT for your wallet. Sign the PSBT with your wallet and
import the signed version here for finalization and broadcasting.
</p>
@if (Model.CanCalculateBalance)
{
<p>
<p class="lead text-secondary mt-3">
This transaction will change your balance:
<br>
@if (Model.Positive)
{
<span class="text-success">@Model.BalanceChange</span>
@@ -39,12 +51,9 @@
<span class="text-danger">@Model.BalanceChange</span>
}
</p>
<p>
Review the details and export the PSBT for your wallet.
Sign the PSBT with your wallet and import the signed version
here for finalization and broadcastimg.
</p>
}
</header>
<partial name="_PSBTInfo" model="Model" />
<h4 class="mt-5 mb-3">Export PSBT for signing</h4>
@@ -74,7 +83,6 @@
<button type="button" id="scanqrcode" class="btn btn-primary only-for-js ms-2" data-bs-toggle="modal" data-bs-target="#scanModal">Scan wallet QR with camera</button>
</div>
</form>
</div>
</div>
<partial name="ShowQR"/>
<partial name="CameraScanner"/>

View File

@@ -1,13 +1,42 @@
@model WalletPSBTReadyViewModel
@{
Layout = "../Shared/_Layout.cshtml";
Layout = "_LayoutWizard";
ViewData.SetActivePageAndTitle(WalletsNavPages.PSBT, "Review PSBT", Context.GetStoreData().StoreName);
}
<section>
<div class="container">
<div class="row">
<div class="col-lg-12 section-heading text-center">
@section Navbar {
<a asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" onclick="history.back();return false;">
<vc:icon symbol="back" />
</a>
<a asp-action="WalletSend" asp-route-walletId="@Context.GetRouteValue("walletId")" class="cancel">
<vc:icon symbol="close" />
</a>
}
<header class="text-center">
<h1>@ViewData["PageTitle"]</h1>
@if (Model.CanCalculateBalance)
{
<p class="lead text-secondary mt-3">
If you broadcast this transaction, your balance will change:
<br>
@if (Model.Positive)
{
<span class="text-success">@Model.BalanceChange</span>
}
else
{
<span class="text-danger">@Model.BalanceChange</span>
}
</p>
}
else
{
<p class="lead text-secondary mt-3">This PSBT is already finalized. We can't properly detect which input or output belongs to you.</p>
}
</header>
<partial name="_StatusMessage" />
@if (Model.GlobalError != null)
@@ -20,39 +49,9 @@
</div>
}
<h2 class="mb-4">@ViewData["PageTitle"]</h2>
@if (Model.CanCalculateBalance)
{
<p>
If you broadcast this transaction, your balance will change:
@if (Model.Positive)
{
<span class="text-success">@Model.BalanceChange</span>
}
else
{
<span class="text-danger">@Model.BalanceChange</span>
}
</p>
<p>Do you want to continue?</p>
}
else
{
<p>This PSBT is already finalized. We can't properly detect which input or output belongs to you.</p>
}
</div>
</div>
<div class="row">
<div class="col-lg-3"></div>
<div class="col-lg-6">
<partial name="_PSBTInfo" model="Model" />
</div>
<div class="col-lg-3"></div>
</div>
<div class="row">
<div class="col-lg-12 text-center">
<form method="post" asp-action="WalletPSBTReady" asp-route-walletId="@Context.GetRouteValue("walletId")">
<form method="post" asp-action="WalletPSBTReady" asp-route-walletId="@Context.GetRouteValue("walletId")" class="text-center">
<input type="hidden" asp-for="SigningKey" />
<input type="hidden" asp-for="SigningKeyPath" />
<partial name="SigningContext" for="SigningContext" />
@@ -71,9 +70,4 @@
<span> or </span>
}
<button type="submit" class="btn btn-secondary" name="command" value="analyze-psbt">View PSBT</button>
</form>
</div>
</div>
</div>
</section>

View File

@@ -1,6 +1,6 @@
@model WalletPSBTReadyViewModel
<h4 class="mt-4 mb-n3">Inputs</h4>
<h4 class="mb-n3">Inputs</h4>
<table class="table table-sm table-responsive-lg">
<thead class="thead-inverse">
<tr>