Refine views and use cases

This commit is contained in:
Dennis Reimann
2021-08-03 12:43:16 +02:00
committed by Andrew Camilleri
parent b654dfb237
commit 9bb74a17d8
4 changed files with 139 additions and 145 deletions

View File

@@ -81,8 +81,7 @@ namespace BTCPayServer.Controllers
if (derivationSchemeSettings == null) if (derivationSchemeSettings == null)
return NotFound(); return NotFound();
vm.NBXSeedAvailable = await CanUseHotWallet() && !string.IsNullOrEmpty(await ExplorerClientProvider.GetExplorerClient(network) vm.NBXSeedAvailable = await CanUseHotWallet() && derivationSchemeSettings.IsHotWallet;
.GetMetadataAsync<string>(derivationSchemeSettings.AccountDerivation, WellknownMetadataKeys.Mnemonic));
if (await vm.GetPSBT(network.NBitcoinNetwork) is PSBT psbt) if (await vm.GetPSBT(network.NBitcoinNetwork) is PSBT psbt)
{ {
@@ -111,8 +110,7 @@ namespace BTCPayServer.Controllers
if (derivationSchemeSettings == null) if (derivationSchemeSettings == null)
return NotFound(); return NotFound();
vm.NBXSeedAvailable = await CanUseHotWallet() && !string.IsNullOrEmpty(await ExplorerClientProvider.GetExplorerClient(network) vm.NBXSeedAvailable = await CanUseHotWallet() && derivationSchemeSettings.IsHotWallet;
.GetMetadataAsync<string>(derivationSchemeSettings.AccountDerivation, WellknownMetadataKeys.Mnemonic));
var psbt = await vm.GetPSBT(network.NBitcoinNetwork); var psbt = await vm.GetPSBT(network.NBitcoinNetwork);
if (psbt == null) if (psbt == null)
{ {
@@ -121,17 +119,14 @@ namespace BTCPayServer.Controllers
} }
vm.PSBTHex = psbt.ToHex(); vm.PSBTHex = psbt.ToHex();
var routeBack = new Dictionary<string, string> var res = await TryHandleSigningCommands(walletId, psbt, command, vm.SigningContext, nameof(WalletPSBT));
{
{"action", nameof(WalletPSBT)}, {"walletId", walletId.ToString()}
};
var res = await TryHandleSigningCommands(walletId, psbt, command, vm.SigningContext, routeBack);
if (res != null) if (res != null)
{ {
return res; return res;
} }
switch (command) switch (command)
{ {
case "export":
case "decode": case "decode":
ModelState.Remove(nameof(vm.PSBT)); ModelState.Remove(nameof(vm.PSBT));
ModelState.Remove(nameof(vm.FileName)); ModelState.Remove(nameof(vm.FileName));
@@ -142,18 +137,14 @@ namespace BTCPayServer.Controllers
await FetchTransactionDetails(derivationSchemeSettings, vm, network); await FetchTransactionDetails(derivationSchemeSettings, vm, network);
return View("WalletPSBTDecoded", vm); return View("WalletPSBTDecoded", vm);
case "export": case "save-psbt":
vm.PSBT = vm.SigningContext.PSBT; return FilePSBT(psbt, vm.FileName);
vm.PSBTHex = psbt.ToHex();
vm.Decoded = psbt.ToString();
await FetchTransactionDetails(derivationSchemeSettings, vm, network);
return View("WalletPSBTExport", vm);
case "update": case "update":
psbt = await ExplorerClientProvider.UpdatePSBT(derivationSchemeSettings, psbt); psbt = await ExplorerClientProvider.UpdatePSBT(derivationSchemeSettings, psbt);
if (psbt == null) if (psbt == null)
{ {
ModelState.AddModelError(nameof(vm.PSBT), "You need to update your version of NBXplorer"); TempData[WellKnownTempData.ErrorMessage] = "You need to update your version of NBXplorer";
return View(vm); return View(vm);
} }
TempData[WellKnownTempData.SuccessMessage] = "PSBT updated!"; TempData[WellKnownTempData.SuccessMessage] = "PSBT updated!";
@@ -163,6 +154,10 @@ namespace BTCPayServer.Controllers
FileName = vm.FileName FileName = vm.FileName
}); });
case "combine":
ModelState.Remove(nameof(vm.PSBT));
return View(nameof(WalletPSBTCombine), new WalletPSBTCombineViewModel { OtherPSBT = psbt.ToBase64() });
case "broadcast": case "broadcast":
{ {
return RedirectToWalletPSBTReady(new WalletPSBTReadyViewModel return RedirectToWalletPSBTReady(new WalletPSBTReadyViewModel
@@ -170,12 +165,6 @@ namespace BTCPayServer.Controllers
SigningContext = new SigningContextModel(psbt) SigningContext = new SigningContextModel(psbt)
}); });
} }
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: default:
var viewName = string.IsNullOrEmpty(vm.PSBT) ? "WalletPSBT" : "WalletPSBTDecoded"; var viewName = string.IsNullOrEmpty(vm.PSBT) ? "WalletPSBT" : "WalletPSBTDecoded";
@@ -470,12 +459,16 @@ namespace BTCPayServer.Controllers
} }
private async Task<IActionResult> TryHandleSigningCommands(WalletId walletId, PSBT psbt, string command, private async Task<IActionResult> TryHandleSigningCommands(WalletId walletId, PSBT psbt, string command,
SigningContextModel signingContext, Dictionary<string, string> routeBack) SigningContextModel signingContext, string actionBack)
{ {
signingContext.PSBT = psbt.ToBase64(); signingContext.PSBT = psbt.ToBase64();
switch (command) switch (command)
{ {
case "sign": case "sign":
var routeBack = new Dictionary<string, string>
{
{"action", actionBack }, {"walletId", walletId.ToString()}
};
return View("WalletSigningOptions", new WalletSigningOptionsModel(signingContext, routeBack)); return View("WalletSigningOptions", new WalletSigningOptionsModel(signingContext, routeBack));
case "vault": case "vault":
return ViewVault(walletId, signingContext); return ViewVault(walletId, signingContext);

View File

@@ -722,12 +722,7 @@ namespace BTCPayServer.Controllers
ChangeAddress = psbtResponse.ChangeAddress?.ToString() ChangeAddress = psbtResponse.ChangeAddress?.ToString()
}; };
var routeBack = new Dictionary<string, string> var res = await TryHandleSigningCommands(walletId, psbt, command, signingContext, nameof(WalletSend));
{
{"action", nameof(WalletSend)}, {"walletId", walletId.ToString()}
};
var res = await TryHandleSigningCommands(walletId, psbt, command, signingContext, routeBack);
if (res != null) if (res != null)
{ {
return res; return res;

View File

@@ -2,10 +2,33 @@
@addTagHelper *, BundlerMinifier.TagHelpers @addTagHelper *, BundlerMinifier.TagHelpers
@{ @{
var isReady = !Model.HasErrors; var isReady = !Model.HasErrors;
var isSignable = !isReady && Model.NBXSeedAvailable;
var needsExport = !isSignable && !isReady;
Layout = "_LayoutWizard"; Layout = "_LayoutWizard";
ViewData.SetActivePageAndTitle(WalletsNavPages.PSBT, isReady ? "Transaction Broadcasting" : "Transaction Details", Context.GetStoreData().StoreName); ViewData.SetActivePageAndTitle(WalletsNavPages.PSBT, isReady ? "Transaction Broadcasting" : "Transaction Details", 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 the PSBT with your wallet", @Json.Serialize(Model.PSBTHex), "scan-qr-modal");
initCameraScanningApp("Scan the PSBT from your wallet", function (data){
$("textarea[name=PSBT]").val(data);
$("#Decode").click();
}, "scanModal");
});
</script>
}
@section Navbar { @section Navbar {
<a asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" onclick="history.back();return false;"> <a asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" onclick="history.back();return false;">
<vc:icon symbol="back" /> <vc:icon symbol="back" />
@@ -21,30 +44,28 @@
<partial name="_PSBTInfo" model="Model" /> <partial name="_PSBTInfo" model="Model" />
@if (!isReady) @if (isSignable)
{ {
<form method="post" asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" class="mt-5"> <form method="post" asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" class="my-5">
<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"/>
<input type="hidden" asp-for="FileName"/> <input type="hidden" asp-for="FileName"/>
<div class="d-flex align-items-center justify-content-center"> <div class="d-flex flex-column flex-sm-row flex-wrap justify-content-center align-items-sm-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" id="SignTransaction" name="command" value="nbx-seed" class="btn btn-primary">Sign transaction</button>
<button type="submit" name="command" value="update" class="btn btn-secondary me-2">Update PSBT</button>
<button type="submit" name="command" value="combine" class="btn btn-secondary">Combine PSBT</button>
</div> </div>
</form> </form>
} }
else else if (isReady)
{ {
<form method="post" asp-action="WalletPSBTReady" asp-route-walletId="@Context.GetRouteValue("walletId")" class="mt-5"> <form method="post" asp-action="WalletPSBTReady" asp-route-walletId="@Context.GetRouteValue("walletId")" class="my-5">
<input type="hidden" asp-for="SigningKey" /> <input type="hidden" asp-for="SigningKey" />
<input type="hidden" asp-for="SigningKeyPath" /> <input type="hidden" asp-for="SigningKeyPath" />
<partial name="SigningContext" for="SigningContext" /> <partial name="SigningContext" for="SigningContext" />
<div class="d-flex align-items-center justify-content-center"> <div class="d-flex flex-column flex-sm-row flex-wrap justify-content-center align-items-sm-center">
@if (!string.IsNullOrEmpty(Model.SigningContext?.PayJoinBIP21)) @if (!string.IsNullOrEmpty(Model.SigningContext?.PayJoinBIP21))
{ {
<button type="submit" class="btn btn-primary" name="command" value="payjoin">Broadcast (Payjoin)</button> <button type="submit" class="btn btn-primary mb-3 mb-sm-0 me-sm-2" name="command" value="payjoin">Broadcast (Payjoin)</button>
<span class="mx-2">or</span> <span class="mx-2">or</span>
<button type="submit" class="btn btn-secondary" name="command" value="broadcast">Broadcast (Simple)</button> <button type="submit" class="btn btn-secondary" name="command" value="broadcast">Broadcast (Simple)</button>
} }
@@ -55,3 +76,93 @@ else
</div> </div>
</form> </form>
} }
else
{
<p class="lead text-secondary mt-5">
Export the PSBT for your wallet. Sign it with your wallet and
import the signed PSBT version here for finalization and broadcasting.
</p>
}
<div class="accordion" id="PSBTOptions">
<div class="accordion-item">
<h2 class="accordion-header" id="PSBTOptionsExportHeader">
<button type="button" class="accordion-button @(needsExport ? "" : "collapsed")" data-bs-toggle="collapse" data-bs-target="#PSBTOptionsExportContent" aria-controls="PSBTOptionsExportContent" aria-expanded="@(needsExport ? "true" : "false")">
<span class="h5">Export PSBT @(isReady ? "" : "for signing")</span>
<vc:icon symbol="caret-down"/>
</button>
</h2>
<div id="PSBTOptionsExportContent" class="accordion-collapse collapse @(needsExport ? "show" : "")" aria-labelledby="PSBTOptionsExportHeader" data-bs-parent="#PSBTOptions">
<div class="accordion-body">
<form method="post" asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" class="mb-2">
<input type="hidden" asp-for="CryptoCode"/>
<input type="hidden" asp-for="PSBT"/>
<div class="d-flex flex-column flex-sm-row flex-wrap align-items-sm-center">
<button name="command" type="submit" class="btn btn-primary mb-3 mb-sm-0 me-sm-2" value="save-psbt">Download PSBT file</button>
<button name="command" type="button" class="btn btn-primary mb-3 mb-sm-0 me-sm-2 only-for-js" data-bs-toggle="modal" data-bs-target="#scan-qr-modal">Show QR for wallet camera</button>
<a href="#ExportOptions" data-bs-toggle="collapse" class="btn btn-link text-secondary">Show raw versions</a>
</div>
</form>
<div id="ExportOptions" class="collapse">
<div class="pt-4">
<h6>Base64</h6>
<pre class="mb-4"><code class="text">@Model.PSBT</code></pre>
<h6>JSON</h6>
<pre class="mb-0"><code class="json">@Model.Decoded</code></pre>
</div>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="PSBTOptionsImportHeader">
<button type="button" class="accordion-button collapsed" data-bs-toggle="collapse" data-bs-target="#PSBTOptionsImportContent" aria-controls="PSBTOptionsImportContent" aria-expanded="false">
<span class="h5">Import PSBT</span>
<vc:icon symbol="caret-down"/>
</button>
</h2>
<div id="PSBTOptionsImportContent" class="accordion-collapse collapse" aria-labelledby="PSBTOptionsImportHeader" data-bs-parent="#PSBTOptions">
<div class="accordion-body">
<form method="post" asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" enctype="multipart/form-data" class="mb-2">
<div class="form-group">
<label for="ImportedPSBT" class="form-label">PSBT content</label>
<textarea id="ImportedPSBT" name="PSBT" class="form-control" rows="5"></textarea>
</div>
<div class="form-group">
<label asp-for="UploadedPSBTFile" class="form-label"></label>
<input asp-for="UploadedPSBTFile" type="file" class="form-control">
</div>
<div class="d-flex flex-column flex-sm-row flex-wrap align-items-sm-center">
<button type="submit" name="command" value="decode" class="btn btn-primary mb-3 mb-sm-0 me-sm-2" id="Decode">Decode PSBT</button>
<button type="button" id="scanqrcode" class="btn btn-primary only-for-js" data-bs-toggle="modal" data-bs-target="#scanModal">Scan wallet QR with camera</button>
</div>
</form>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="PSBTOptionsAdvancedHeader">
<button type="button" class="accordion-button collapsed" data-bs-toggle="collapse" data-bs-target="#PSBTOptionsAdvancedContent" aria-controls="PSBTOptionsAdvancedContent" aria-expanded="false">
<span class="h5">Update or combine PSBT (advanced)</span>
<vc:icon symbol="caret-down"/>
</button>
</h2>
<div id="PSBTOptionsAdvancedContent" class="accordion-collapse collapse" aria-labelledby="PSBTOptionsAdvancedHeader" data-bs-parent="#PSBTOptions">
<div class="accordion-body">
<form method="post" asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" class="mb-2">
<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 flex-column flex-sm-row flex-wrap align-items-sm-center">
<button type="submit" name="command" value="update" class="btn btn-secondary mb-3 mb-sm-0 me-sm-2">Update PSBT</button>
<button type="submit" name="command" value="combine" class="btn btn-secondary">Combine PSBT</button>
</div>
</form>
</div>
</div>
</div>
</div>
<partial name="ShowQR"/>
<partial name="CameraScanner"/>

View File

@@ -1,105 +0,0 @@
@model WalletPSBTViewModel
@addTagHelper *, BundlerMinifier.TagHelpers
@{
Layout = "_LayoutWizard";
ViewData.SetActivePageAndTitle(WalletsNavPages.PSBT, "Transaction Signing", 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 the PSBT with your wallet", @Json.Serialize(Model.PSBTHex), "scan-qr-modal");
initCameraScanningApp("Scan the PSBT from your wallet", 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 mb-3">
<h1>@ViewData["PageTitle"]</h1>
</header>
<partial name="_PSBTInfo" model="Model" />
<p class="lead text-secondary mt-5">
Export the PSBT for your wallet. Sign it with your wallet and
import the signed PSBT version here for finalization and broadcasting.
</p>
<div class="accordion" id="PSBTOptions">
<div class="accordion-item">
<h2 class="accordion-header" id="PSBTOptionsExportHeader">
<button type="button" class="accordion-button" data-bs-toggle="collapse" data-bs-target="#PSBTOptionsExportContent" aria-controls="PSBTOptionsExportContent" aria-expanded="true">
<span class="h5">Export PSBT for signing</span>
<vc:icon symbol="caret-down"/>
</button>
</h2>
<div id="PSBTOptionsExportContent" class="accordion-collapse collapse show" aria-labelledby="PSBTOptionsExportHeader" data-bs-parent="#PSBTOptions">
<div class="accordion-body">
<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="form-group d-flex align-items-center">
<button name="command" type="submit" class="btn btn-primary" value="save-psbt">Download PSBT file</button>
<button name="command" type="button" class="btn btn-primary only-for-js ms-2" data-bs-toggle="modal" data-bs-target="#scan-qr-modal">Show QR for wallet camera</button>
<a href="#ExportOptions" data-bs-toggle="collapse" class="ms-3 text-secondary">Show raw versions</a>
</div>
</form>
<div id="ExportOptions" class="collapse">
<pre><code class="json">@Model.Decoded</code></pre>
<pre><code class="text">@Model.PSBT</code></pre>
</div>
</div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="PSBTOptionsImportHeader">
<button type="button" class="accordion-button collapsed" data-bs-toggle="collapse" data-bs-target="#PSBTOptionsImportContent" aria-controls="PSBTOptionsImportContent" aria-expanded="false">
<span class="h5">Import signed PSBT</span>
<vc:icon symbol="caret-down"/>
</button>
</h2>
<div id="PSBTOptionsImportContent" class="accordion-collapse collapse" aria-labelledby="PSBTOptionsImportHeader" data-bs-parent="#PSBTOptions">
<div class="accordion-body">
<form class="form-group" method="post" asp-action="WalletPSBT" asp-route-walletId="@Context.GetRouteValue("walletId")" enctype="multipart/form-data">
<div class="form-group">
<label for="ImportedPSBT" class="form-label">PSBT content</label>
<textarea id="ImportedPSBT" name="PSBT" class="form-control" rows="5"></textarea>
</div>
<div class="form-group">
<label asp-for="UploadedPSBTFile" class="form-label"></label>
<input asp-for="UploadedPSBTFile" type="file" class="form-control">
</div>
<div class="d-flex align-items-center">
<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>
</div>
</form>
</div>
</div>
</div>
</div>
<partial name="ShowQR"/>
<partial name="CameraScanner"/>