Add PSBT support in the send screen

This commit is contained in:
nicolas.dorier 2019-05-08 14:39:37 +09:00
parent 2a145f4350
commit 03713f9bd8
No known key found for this signature in database
GPG key ID: 6618763EF09186FE
2 changed files with 70 additions and 49 deletions

View file

@ -202,7 +202,7 @@ namespace BTCPayServer.Controllers
[Route("{walletId}/send")] [Route("{walletId}/send")]
public async Task<IActionResult> WalletSend( public async Task<IActionResult> WalletSend(
[ModelBinder(typeof(WalletIdModelBinder))] [ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId, WalletSendModel vm) WalletId walletId, WalletSendModel vm, string command = null, CancellationToken cancellation = default)
{ {
if (walletId?.StoreId == null) if (walletId?.StoreId == null)
return NotFound(); return NotFound();
@ -227,14 +227,58 @@ namespace BTCPayServer.Controllers
if (!ModelState.IsValid) if (!ModelState.IsValid)
return View(vm); return View(vm);
return RedirectToAction(nameof(WalletSendLedger), new WalletSendLedgerModel() var sendModel = new WalletSendLedgerModel()
{ {
Destination = vm.Destination, Destination = vm.Destination,
Amount = vm.Amount.Value, Amount = vm.Amount.Value,
SubstractFees = vm.SubstractFees, SubstractFees = vm.SubstractFees,
FeeSatoshiPerByte = vm.FeeSatoshiPerByte, FeeSatoshiPerByte = vm.FeeSatoshiPerByte,
NoChange = vm.NoChange NoChange = vm.NoChange
}); };
if (command == "ledger")
{
return RedirectToAction(nameof(WalletSendLedger), sendModel);
}
else
{
var storeData = (await Repository.FindStore(walletId.StoreId, GetUserId()));
var derivationScheme = GetPaymentMethod(walletId, storeData).DerivationStrategyBase;
var psbt = await CreatePSBT(network, derivationScheme, sendModel, cancellation);
return File(psbt.PSBT.ToBytes(), "application/octet-stream", $"Send-{vm.Amount.Value}-{network.CryptoCode}-to-{destination[0].ToString()}.psbt");
}
}
private async Task<CreatePSBTResponse> CreatePSBT(BTCPayNetwork network, DerivationStrategyBase derivationScheme, WalletSendLedgerModel sendModel, CancellationToken cancellationToken)
{
var nbx = ExplorerClientProvider.GetExplorerClient(network);
CreatePSBTRequest psbtRequest = new CreatePSBTRequest();
CreatePSBTDestination psbtDestination = new CreatePSBTDestination();
psbtRequest.Destinations.Add(psbtDestination);
psbtDestination.Destination = BitcoinAddress.Create(sendModel.Destination, network.NBitcoinNetwork);
psbtDestination.Amount = Money.Coins(sendModel.Amount);
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;
var psbt = (await nbx.CreatePSBTAsync(derivationScheme, psbtRequest, cancellationToken));
if (psbt == null)
throw new NotSupportedException("You need to update your version of NBXplorer");
if (network.MinFee != null)
{
psbt.PSBT.TryGetFee(out var fee);
if (fee < network.MinFee)
{
psbtRequest.FeePreference = new FeePreference() { ExplicitFee = network.MinFee };
psbt = (await nbx.CreatePSBTAsync(derivationScheme, psbtRequest, cancellationToken));
}
}
return psbt;
} }
[HttpGet] [HttpGet]
@ -403,7 +447,7 @@ namespace BTCPayServer.Controllers
int account = 0, int account = 0,
// sendtoaddress // sendtoaddress
bool noChange = false, bool noChange = false,
string destination = null, string amount = null, string feeRate = null, string substractFees = null string destination = null, string amount = null, string feeRate = null, bool substractFees = false
) )
{ {
if (!HttpContext.WebSockets.IsWebSocketRequest) if (!HttpContext.WebSockets.IsWebSocketRequest)
@ -413,7 +457,6 @@ namespace BTCPayServer.Controllers
var storeData = (await Repository.FindStore(walletId.StoreId, GetUserId())); var storeData = (await Repository.FindStore(walletId.StoreId, GetUserId()));
var derivationScheme = GetPaymentMethod(walletId, storeData).DerivationStrategyBase; var derivationScheme = GetPaymentMethod(walletId, storeData).DerivationStrategyBase;
var psbtRequest = new CreatePSBTRequest();
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
using (var normalOperationTimeout = new CancellationTokenSource()) using (var normalOperationTimeout = new CancellationTokenSource())
@ -421,6 +464,7 @@ namespace BTCPayServer.Controllers
{ {
normalOperationTimeout.CancelAfter(TimeSpan.FromMinutes(30)); normalOperationTimeout.CancelAfter(TimeSpan.FromMinutes(30));
var hw = new HardwareWalletService(webSocket); var hw = new HardwareWalletService(webSocket);
var model = new WalletSendLedgerModel();
object result = null; object result = null;
try try
{ {
@ -432,33 +476,25 @@ namespace BTCPayServer.Controllers
throw new FormatException("Invalid value for crypto code"); throw new FormatException("Invalid value for crypto code");
} }
CreatePSBTDestination destinationPSBT = null;
if (destination != null) if (destination != null)
{ {
try try
{ {
destinationPSBT = new CreatePSBTDestination() BitcoinAddress.Create(destination.Trim(), network.NBitcoinNetwork);
{ model.Destination = destination.Trim();
Destination = BitcoinAddress.Create(destination.Trim(), network.NBitcoinNetwork)
};
psbtRequest.Destinations.Add(destinationPSBT);
} }
catch { } catch { }
if (destinationPSBT == null)
throw new FormatException("Invalid value for destination");
} }
if (feeRate != null) if (feeRate != null)
{ {
psbtRequest.FeePreference = new FeePreference();
try try
{ {
psbtRequest.FeePreference.ExplicitFeeRate = new FeeRate(Money.Satoshis(int.Parse(feeRate, CultureInfo.InvariantCulture)), 1); model.FeeSatoshiPerByte = int.Parse(feeRate, CultureInfo.InvariantCulture);
} }
catch { } catch { }
if (psbtRequest.FeePreference.ExplicitFeeRate == null || if (model.FeeSatoshiPerByte <= 0)
psbtRequest.FeePreference.ExplicitFeeRate.FeePerK <= Money.Zero)
throw new FormatException("Invalid value for fee rate"); throw new FormatException("Invalid value for fee rate");
} }
@ -466,21 +502,15 @@ namespace BTCPayServer.Controllers
{ {
try try
{ {
destinationPSBT.Amount = Money.Parse(amount); model.Amount = Money.Parse(amount).ToDecimal(MoneyUnit.BTC);
} }
catch { } catch { }
if (destinationPSBT.Amount == null || destinationPSBT.Amount <= Money.Zero) if (model.Amount <= 0m)
throw new FormatException("Invalid value for amount"); throw new FormatException("Invalid value for amount");
} }
if (substractFees != null) model.SubstractFees = substractFees;
{ model.NoChange = noChange;
try
{
destinationPSBT.SubstractFees = bool.Parse(substractFees);
}
catch { throw new FormatException("Invalid value for subtract fees"); }
}
if (command == "test") if (command == "test")
{ {
result = await hw.Test(normalOperationTimeout.Token); result = await hw.Test(normalOperationTimeout.Token);
@ -489,27 +519,10 @@ namespace BTCPayServer.Controllers
{ {
if (!_dashboard.IsFullySynched(network.CryptoCode, out var summary)) if (!_dashboard.IsFullySynched(network.CryptoCode, out var summary))
throw new Exception($"{network.CryptoCode}: not started or fully synched"); throw new Exception($"{network.CryptoCode}: not started or fully synched");
var psbt = await CreatePSBT(network, derivationScheme, model, normalOperationTimeout.Token);
var strategy = GetDirectDerivationStrategy(derivationScheme); var strategy = GetDirectDerivationStrategy(derivationScheme);
var nbx = ExplorerClientProvider.GetExplorerClient(network);
if (noChange)
{
psbtRequest.ExplicitChangeAddress = destinationPSBT.Destination;
}
var psbt = (await nbx.CreatePSBTAsync(derivationScheme, psbtRequest, normalOperationTimeout.Token));
if (psbt == null)
throw new Exception("You need to update your version of NBXplorer");
if (network.MinFee != null)
{
psbt.PSBT.TryGetFee(out var fee);
if (fee < network.MinFee)
{
psbtRequest.FeePreference = new FeePreference() { ExplicitFee = network.MinFee };
psbt = (await nbx.CreatePSBTAsync(derivationScheme, psbtRequest, normalOperationTimeout.Token));
}
}
var storeBlob = storeData.GetStoreBlob(); var storeBlob = storeData.GetStoreBlob();
var paymentId = new Payments.PaymentMethodId(cryptoCode, Payments.PaymentTypes.BTCLike); var paymentId = new Payments.PaymentMethodId(cryptoCode, Payments.PaymentTypes.BTCLike);
var foundKeyPath = storeBlob.GetWalletKeyPathRoot(paymentId); var foundKeyPath = storeBlob.GetWalletKeyPathRoot(paymentId);
@ -549,7 +562,7 @@ namespace BTCPayServer.Controllers
var transaction = psbt.PSBT.ExtractTransaction(); var transaction = psbt.PSBT.ExtractTransaction();
try try
{ {
var broadcastResult = await nbx.BroadcastAsync(transaction); var broadcastResult = await ExplorerClientProvider.GetExplorerClient(network).BroadcastAsync(transaction);
if (!broadcastResult.Success) if (!broadcastResult.Success)
{ {
throw new Exception($"RPC Error while broadcasting: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}"); throw new Exception($"RPC Error while broadcasting: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}");

View file

@ -75,7 +75,15 @@
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<button name="command" type="submit" class="btn btn-primary" style="margin-top:16px;">Confirm</button> <div class="dropdown" style="margin-top:16px;">
<button class="btn btn-primary dropdown-toggle" type="button" id="SendMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Sign with...
</button>
<div class="dropdown-menu" aria-labelledby="SendMenu">
<button name="command" type="submit" class="dropdown-item" value="ledger">... your Ledger Wallet device</button>
<button name="command" type="submit" class="dropdown-item" value="save-psbt">... a wallet supporting PSBT</button>
</div>
</div>
</div> </div>
</form> </form>
</div> </div>