btcpayserver/BTCPayServer/Views/Wallets/WalletSend.cshtml
Andrew Camilleri 4176f3659b
Add QR code scan/show for PSBT + Import wallet via QR (#1931)
* Add PSBT QR code scan/show

This PR introduces support to show and read PSBTs in BC-UR format via animated QR codes.  This allows you to use BTCPay with HW devices such as Cobo Vault and Blue wallet to sign transactions without ever exposing the keys outside of that device.
Spec: https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2020-005-ur.md
I've also bumped the QR code library we sue as it had a bug with large datasets.

* Reuse same code for all and allow wallet import via QR code scan

* remove unecessary js vendor files

* Allow export wallet from settings via QR

* formatting

* bundle

* fix wallet receive bundle
2020-10-21 14:03:11 +02:00

238 lines
14 KiB
Text

@addTagHelper *, BundlerMinifier.TagHelpers
@using Microsoft.AspNetCore.Mvc.ModelBinding
@using BTCPayServer.Views
@model WalletSendModel
@{
Layout = "../Shared/_NavLayout.cshtml";
ViewData["Title"] = "Manage wallet";
ViewData.SetActivePageAndTitle(WalletsNavPages.Send);
}
@if (TempData.HasStatusMessage())
{
<div class="row">
<div class="col-md-10 text-center">
<partial name="_StatusMessage" />
</div>
</div>
}
<partial name="CameraScanner"/>
<div class="row">
<div class="@(!Model.InputSelection && Model.Outputs.Count==1? "col-lg-7 transaction-output-form": "col-lg-8")">
<form method="post" asp-action="WalletSend" asp-route-walletId="@this.Context.GetRouteValue("walletId")">
<input type="hidden" asp-for="InputSelection" />
<input type="hidden" asp-for="FiatDivisibility" />
<input type="hidden" asp-for="CryptoDivisibility" />
<input type="hidden" asp-for="NBXSeedAvailable" />
<input type="hidden" asp-for="Fiat" />
<input type="hidden" asp-for="Rate" />
<input type="hidden" asp-for="CurrentBalance" />
<input type="hidden" asp-for="CryptoCode" />
<input type="hidden" name="BIP21" id="BIP21" />
<ul class="text-danger">
@foreach (var errors in ViewData.ModelState.Where(pair => pair.Key == string.Empty && pair.Value.ValidationState == ModelValidationState.Invalid))
{
foreach (var error in
errors.Value.Errors)
{
<li>@error.ErrorMessage</li>
}
}
</ul>
@if (Model.InputSelection)
{
<div class="form-group hide-when-js">
<label asp-for="SelectedInputs"></label>
<select multiple="multiple" asp-for="SelectedInputs" class="form-control">
@foreach (var input in Model.InputsAvailable)
{
<option value="@input.Outpoint" class="text-truncate" asp-selected="@(Model.SelectedInputs?.Contains(input.Outpoint)??false)">(@input.Amount) @input.Outpoint</option>
}
</select>
</div>
<partial name="CoinSelection" />
}
@if (Model.Outputs.Count == 1)
{
<div class="form-group">
<label asp-for="Outputs[0].DestinationAddress"></label>
<input asp-for="Outputs[0].DestinationAddress" class="form-control text-monospace" />
<span asp-validation-for="Outputs[0].DestinationAddress" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Outputs[0].Amount"></label>
<div class="input-group">
<input asp-for="Outputs[0].Amount" type="number" step="any" min="0" asp-format="{0}" class="form-control output-amount" />
<div class="input-group-append fiat-value" style="display:none;">
<span class="input-group-text" >=</span>
<input type="number" class="input-group-text fiat-value-edit-input" min="0" step="any" style="max-width:100px" />
<span class="input-group-text" >@Model.Fiat</span>
</div>
</div>
<span asp-validation-for="Outputs[0].Amount" class="text-danger"></span>
<p class="form-text text-secondary crypto-info">
Your current balance is
<button type="button" class="crypto-balance-link btn btn-link p-0 align-baseline">@Model.CurrentBalance</button> <span>@Model.CryptoCode</span>.
</p>
</div>
}
else
{
<div class="list-group-item">
<h5 class="mb-0">Destinations</h5>
</div>
<div class="list-group mb-4">
@for (var index = 0; index < Model.Outputs.Count; index++)
{
<div class="list-group-item transaction-output-form">
<div class="row">
<div class="col-sm-12 col-lg-10">
<div class="form-group">
<label asp-for="Outputs[index].DestinationAddress" class="control-label"></label>
<input asp-for="Outputs[index].DestinationAddress" class="form-control" />
<span asp-validation-for="Outputs[index].DestinationAddress" class="text-danger"></span>
</div>
<div class="form-group">
<label asp-for="Outputs[index].Amount" class="control-label"></label>
<div class="input-group">
<input asp-for="Outputs[index].Amount" type="number" step="any" asp-format="{0}" class="form-control output-amount" />
<div class="input-group-append">
<span class="input-group-text fiat-value" style="display:none;"></span>
</div>
</div>
<p class="form-text text-secondary crypto-info mb-2">
Your current balance is
<button type="button" class="crypto-balance-link btn btn-link p-0 align-baseline">@Model.CurrentBalance</button> <span>@Model.CryptoCode</span>.
</p>
<span asp-validation-for="Outputs[index].Amount" class="text-danger"></span>
</div>
<div class="form-group mb-0">
<div class="form-check">
<input type="checkbox" asp-for="Outputs[index].SubtractFeesFromOutput" class="form-check-input subtract-fees" />
<label asp-for="Outputs[index].SubtractFeesFromOutput" class="form-check-label"></label>
<span asp-validation-for="Outputs[index].SubtractFeesFromOutput" class="text-danger"></span>
</div>
</div>
</div>
<div class="col-sm-12 col-lg-2">
<button type="submit" title="Remove Destination" name="command" value="@($"remove-output:{index}")"
class="d-inline-block d-lg-none d-xl-none btn btn-danger mt-4 mb-2">
Remove Destination
</button>
<button type="submit" title="Remove Destination" name="command" value="@($"remove-output:{index}")"
class="d-none d-lg-inline-block remove-destination-btn btn text-danger fa fa-times pull-right m-0"></button>
</div>
</div>
</div>
}
</div>
}
<div class="form-group my-4">
<label asp-for="FeeSatoshiPerByte"></label>
<input asp-for="FeeSatoshiPerByte" type="number" step="any" class="form-control" style="max-width:14ch;" />
<span asp-validation-for="FeeSatoshiPerByte" class="text-danger"></span>
<span id="FeeRate-Error" class="text-danger"></span>
@if (Model.RecommendedSatoshiPerByte.Any())
{
<div class="text-left mt-2 d-flex align-items-center">
<span class="text-secondary mr-2">
Confirm in the next
</span>
<div class="btn-group btn-group-toggle feerate-options" role="group" data-toggle="buttons">
@for (var index = 0; index < Model.RecommendedSatoshiPerByte.Count; index++)
{
var feeRateOption = Model.RecommendedSatoshiPerByte[index];
<button type="button" class="btn btn-sm btn-outline-primary crypto-fee-link" value="@feeRateOption.FeeRate" data-toggle="tooltip" title="@feeRateOption.FeeRate sat/b">
@feeRateOption.Target.TimeString()
</button>
<input type="hidden" asp-for="RecommendedSatoshiPerByte[index].Target" />
<input type="hidden" asp-for="RecommendedSatoshiPerByte[index].FeeRate" />
}
</div>
</div>
}
</div>
@if (Model.Outputs.Count == 1)
{
<div class="form-group">
<div class="form-check">
<input type="checkbox" asp-for="Outputs[0].SubtractFeesFromOutput" class="form-check-input subtract-fees" />
<label asp-for="Outputs[0].SubtractFeesFromOutput" class="form-check-label"></label>
<span asp-validation-for="Outputs[0].SubtractFeesFromOutput" class="text-danger"></span>
</div>
</div>
}
<div class="card border-0 my-4">
<button id="advancedSettings" class="btn btn-link text-secondary collapsed text-left" type="button" data-toggle="collapse" data-target="#accordian-advanced" aria-expanded="false" aria-controls="accordian-advanced">
Advanced settings
</button>
<div id="accordian-advanced" class="collapse" aria-labelledby="accordian-advanced-header" data-parent="#accordian-advanced">
<div class="card-body">
<div class="form-group">
<div class="form-check">
<input asp-for="NoChange" class="form-check-input" />
<label asp-for="NoChange" class="form-check-label"></label>
<a href="https://docs.btcpayserver.org/Wallet/#dont-create-utxo-change" target="_blank">
<span class="fa fa-question-circle-o" title="More information..."></span>
</a>
</div>
</div>
<div class="form-group">
<div class="form-check">
<input asp-for="AlwaysIncludeNonWitnessUTXO" class="form-check-input"/>
<label asp-for="AlwaysIncludeNonWitnessUTXO" class="form-check-label"></label>
<a href="https://medium.com/@@jmacato/wasabi-wallets-advisory-for-trezor-users-7d942c727f92" target="_blank">
<span class="fa fa-question-circle-o" title="More information..."></span>
</a>
</div>
</div>
@if (Model.SupportRBF)
{
<div class="form-group">
<label asp-for="AllowFeeBump"></label>
<a href="https://docs.btcpayserver.org/Wallet/#rbf-replace-by-fee" target="_blank">
<span class="fa fa-question-circle-o" title="More information..."></span>
</a>
<select asp-for="AllowFeeBump" class="form-control w-auto">
<option value="Maybe">Randomize for higher privacy</option>
<option value="Yes">Yes</option>
<option value="No">No</option>
</select>
</div>
}
@if (!string.IsNullOrEmpty(Model.PayJoinBIP21))
{
<div class="form-group">
<label asp-for="PayJoinBIP21" class="control-label"></label>
<input asp-for="PayJoinBIP21" class="form-control" />
<span asp-validation-for="PayJoinBIP21" class="text-danger"></span>
</div>
}
<div class="form-group mt-4 mb-0">
<button id="toggleInputSelection" type="submit" name="command" value="toggle-input-selection" class="btn btn-secondary">Toggle coin selection</button>
</div>
</div>
</div>
</div>
<div class="form-group d-flex mt-2">
<partial name="WalletSigningMenu" model="@((Model.CryptoCode, Model.NBXSeedAvailable))"/>
<button type="submit" name="command" value="add-output" class="ml-2 btn btn-secondary">Add another destination</button>
<button type="button" id="bip21parse" class="ml-2 btn btn-secondary" title="Paste BIP21/Address"><i class="fa fa-paste"></i></button>
<button type="button" id="scanqrcode" class="ml-2 btn btn-secondary only-for-js" data-toggle="modal" data-target="#scanModal" title="Scan BIP21/Address with camera"><i class="fa fa-camera"></i></button>
</div>
</form>
</div>
</div>
@section HeadScripts
{
<bundle name="wwwroot/bundles/wallet-send-bundle.min.js"></bundle>
<link href="~/vendor/vue-qrcode-reader/vue-qrcode-reader.css" rel="stylesheet" asp-append-version="true"/>
<style>
.remove-destination-btn { font-size: 1.5rem; }
.remove-destination-btn:hover { border-color: var(--btcpay-color-danger); }
</style>
}