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

View file

@ -75,7 +75,15 @@
</div>
</div>
<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>
</form>
</div>