diff --git a/BTCPayServer.Tests/UnitTest1.cs b/BTCPayServer.Tests/UnitTest1.cs index e94da670e..ade109d4b 100644 --- a/BTCPayServer.Tests/UnitTest1.cs +++ b/BTCPayServer.Tests/UnitTest1.cs @@ -1634,7 +1634,7 @@ namespace BTCPayServer.Tests Assert.Equal("paid", invoice.Status); }); var wallet = tester.PayTester.GetController(); - var psbt = wallet.CreatePSBT(btcNetwork, onchainBTC, new WalletSendLedgerModel() + var psbt = wallet.CreatePSBT(btcNetwork, onchainBTC, new WalletSendModel() { Amount = 0.5m, Destination = new Key().PubKey.GetAddress(btcNetwork.NBitcoinNetwork).ToString(), diff --git a/BTCPayServer/Controllers/WalletsController.cs b/BTCPayServer/Controllers/WalletsController.cs index c79dd6947..51ff32933 100644 --- a/BTCPayServer/Controllers/WalletsController.cs +++ b/BTCPayServer/Controllers/WalletsController.cs @@ -227,26 +227,24 @@ namespace BTCPayServer.Controllers if (!ModelState.IsValid) return View(vm); - var sendModel = new WalletSendLedgerModel() - { - Destination = vm.Destination, - Amount = vm.Amount.Value, - SubstractFees = vm.SubstractFees, - FeeSatoshiPerByte = vm.FeeSatoshiPerByte, - NoChange = vm.NoChange, - DisableRBF = vm.DisableRBF - }; + var storeData = (await Repository.FindStore(walletId.StoreId, GetUserId())); + var derivationScheme = GetPaymentMethod(walletId, storeData); + var psbt = await CreatePSBT(network, derivationScheme, vm, cancellation); + if (command == "ledger") { - return RedirectToAction(nameof(WalletSendLedger), sendModel); + return View("WalletSendLedger", new WalletSendLedgerModel() + { + PSBT = psbt.PSBT.ToBase64(), + HintChange = psbt.ChangeAddress?.ToString(), + WebsocketPath = this.Url.Action(nameof(LedgerConnection)), + SuccessPath = this.Url.Action(nameof(WalletSendLedgerSuccess)) + }); } else { - var storeData = (await Repository.FindStore(walletId.StoreId, GetUserId())); - var derivationScheme = GetPaymentMethod(walletId, storeData); try - { - var psbt = await CreatePSBT(network, derivationScheme, sendModel, cancellation); + { if (command == "analyze-psbt") return View(nameof(WalletPSBT), new WalletPSBTViewModel() { @@ -269,7 +267,7 @@ namespace BTCPayServer.Controllers } [NonAction] - public async Task CreatePSBT(BTCPayNetwork network, DerivationSchemeSettings derivationSettings, WalletSendLedgerModel sendModel, CancellationToken cancellationToken) + public async Task CreatePSBT(BTCPayNetwork network, DerivationSchemeSettings derivationSettings, WalletSendModel sendModel, CancellationToken cancellationToken) { var nbx = ExplorerClientProvider.GetExplorerClient(network); CreatePSBTRequest psbtRequest = new CreatePSBTRequest(); @@ -280,7 +278,7 @@ namespace BTCPayServer.Controllers psbtRequest.RBF = !sendModel.DisableRBF; } psbtDestination.Destination = BitcoinAddress.Create(sendModel.Destination, network.NBitcoinNetwork); - psbtDestination.Amount = Money.Coins(sendModel.Amount); + psbtDestination.Amount = Money.Coins(sendModel.Amount.Value); psbtRequest.FeePreference = new FeePreference(); psbtRequest.FeePreference.ExplicitFeeRate = new FeeRate(Money.Satoshis(sendModel.FeeSatoshiPerByte), 1); if (sendModel.NoChange) @@ -306,24 +304,6 @@ namespace BTCPayServer.Controllers return psbt; } - [HttpGet] - [Route("{walletId}/send/ledger")] - public async Task WalletSendLedger( - [ModelBinder(typeof(WalletIdModelBinder))] - WalletId walletId, WalletSendLedgerModel vm) - { - if (walletId?.StoreId == null) - return NotFound(); - var store = await Repository.FindStore(walletId.StoreId, GetUserId()); - DerivationSchemeSettings paymentMethod = GetPaymentMethod(walletId, store); - if (paymentMethod == null) - return NotFound(); - var network = this.NetworkProvider.GetNetwork(walletId?.CryptoCode); - if (network == null) - return NotFound(); - return View(vm); - } - private IDestination[] ParseDestination(string destination, Network network) { try @@ -496,14 +476,16 @@ namespace BTCPayServer.Controllers // getxpub int account = 0, // sendtoaddress - bool noChange = false, - string destination = null, string amount = null, string feeRate = null, bool substractFees = false, bool disableRBF = false + string psbt = null, + string hintChange = null ) { if (!HttpContext.WebSockets.IsWebSocketRequest) return NotFound(); - var cryptoCode = walletId.CryptoCode; + var network = NetworkProvider.GetNetwork(walletId.CryptoCode); + if (network == null) + throw new FormatException("Invalid value for crypto code"); var storeData = (await Repository.FindStore(walletId.StoreId, GetUserId())); var derivationSettings = GetPaymentMethod(walletId, storeData); @@ -518,50 +500,6 @@ namespace BTCPayServer.Controllers object result = null; try { - BTCPayNetwork network = null; - if (cryptoCode != null) - { - network = NetworkProvider.GetNetwork(cryptoCode); - if (network == null) - throw new FormatException("Invalid value for crypto code"); - } - - if (destination != null) - { - try - { - BitcoinAddress.Create(destination.Trim(), network.NBitcoinNetwork); - model.Destination = destination.Trim(); - } - catch { } - } - - - if (feeRate != null) - { - try - { - model.FeeSatoshiPerByte = int.Parse(feeRate, CultureInfo.InvariantCulture); - } - catch { } - if (model.FeeSatoshiPerByte <= 0) - throw new FormatException("Invalid value for fee rate"); - } - - if (amount != null) - { - try - { - model.Amount = Money.Parse(amount).ToDecimal(MoneyUnit.BTC); - } - catch { } - if (model.Amount <= 0m) - throw new FormatException("Invalid value for amount"); - } - - model.SubstractFees = substractFees; - model.NoChange = noChange; - model.DisableRBF = disableRBF; if (command == "test") { result = await hw.Test(normalOperationTimeout.Token); @@ -611,14 +549,18 @@ namespace BTCPayServer.Controllers await Repository.UpdateStore(storeData); } - var psbt = await CreatePSBT(network, derivationSettings, model, normalOperationTimeout.Token); + var psbtResponse = new CreatePSBTResponse() + { + PSBT = PSBT.Parse(psbt, network.NBitcoinNetwork), + ChangeAddress = string.IsNullOrEmpty(hintChange) ? null : BitcoinAddress.Create(hintChange, network.NBitcoinNetwork) + }; signTimeout.CancelAfter(TimeSpan.FromMinutes(5)); - psbt.PSBT = await hw.SignTransactionAsync(psbt.PSBT, psbt.ChangeAddress?.ScriptPubKey, signTimeout.Token); - if(!psbt.PSBT.TryFinalize(out var errors)) + psbtResponse.PSBT = await hw.SignTransactionAsync(psbtResponse.PSBT, psbtResponse.ChangeAddress?.ScriptPubKey, signTimeout.Token); + if(!psbtResponse.PSBT.TryFinalize(out var errors)) { throw new Exception($"Error while finalizing the transaction ({new PSBTException(errors).ToString()})"); } - var transaction = psbt.PSBT.ExtractTransaction(); + var transaction = psbtResponse.PSBT.ExtractTransaction(); try { var broadcastResult = await ExplorerClientProvider.GetExplorerClient(network).BroadcastAsync(transaction); diff --git a/BTCPayServer/Models/WalletViewModels/WalletSendLedgerModel.cs b/BTCPayServer/Models/WalletViewModels/WalletSendLedgerModel.cs index 6c4fbb131..2709fd591 100644 --- a/BTCPayServer/Models/WalletViewModels/WalletSendLedgerModel.cs +++ b/BTCPayServer/Models/WalletViewModels/WalletSendLedgerModel.cs @@ -7,11 +7,9 @@ namespace BTCPayServer.Models.WalletViewModels { public class WalletSendLedgerModel { - public int FeeSatoshiPerByte { get; set; } - public bool SubstractFees { get; set; } - public bool DisableRBF { get; set; } - public decimal Amount { get; set; } - public string Destination { get; set; } - public bool NoChange { get; set; } + public string WebsocketPath { get; set; } + public string PSBT { get; set; } + public string HintChange { get; set; } + public string SuccessPath { get; set; } } } diff --git a/BTCPayServer/Views/Wallets/WalletSendLedger.cshtml b/BTCPayServer/Views/Wallets/WalletSendLedger.cshtml index 0ac7c1869..7d9052c7c 100644 --- a/BTCPayServer/Views/Wallets/WalletSendLedger.cshtml +++ b/BTCPayServer/Views/Wallets/WalletSendLedger.cshtml @@ -12,12 +12,10 @@
- - - - - - + + + +

You can send money received by this store to an address with the help of your Ledger Wallet.
If you don't have a Ledger Wallet, use Electrum with your favorite hardware wallet to transfer crypto.
diff --git a/BTCPayServer/wwwroot/js/WalletSendLedger.js b/BTCPayServer/wwwroot/js/WalletSendLedger.js index a6c10acb5..b858e1524 100644 --- a/BTCPayServer/wwwroot/js/WalletSendLedger.js +++ b/BTCPayServer/wwwroot/js/WalletSendLedger.js @@ -1,11 +1,9 @@ $(function () { - var destination = $("#Destination").val(); - var amount = $("#Amount").val(); - var fee = $("#FeeSatoshiPerByte").val(); - var substractFee = $("#SubstractFees").val(); - var noChange = $("#NoChange").val(); - var disableRBF = $("#DisableRBF").val(); - + var psbt = $("#PSBT").val(); + var hintChange = $("#HintChange").val(); + var successPath = $("#SuccessPath").val(); + var websocketPath = $("#WebsocketPath").val(); + var loc = window.location, ws_uri; if (loc.protocol === "https:") { ws_uri = "wss:"; @@ -13,10 +11,7 @@ ws_uri = "ws:"; } ws_uri += "//" + loc.host; - ws_uri += loc.pathname + "/ws"; - - var successCallback = loc.protocol + "//" + loc.host + loc.pathname + "/success"; - + ws_uri += websocketPath; var ledgerDetected = false; var bridge = new ledgerwebsocket.LedgerWebSocketBridge(ws_uri); function WriteAlert(type, message) { @@ -44,19 +39,10 @@ return false; $(".crypto-info").css("display", "block"); var args = ""; - args += "&destination=" + destination; - args += "&amount=" + amount; - args += "&feeRate=" + fee; - args += "&substractFees=" + substractFee; - args += "&noChange=" + noChange; - args += "&disableRBF=" + disableRBF; - - if (noChange === "True") { - WriteAlert("warning", 'WARNING: Because you want to make sure no change UTXO is created, you will end up sending more than the chosen amount to your destination. Please validate the transaction on your ledger'); - } - else { - WriteAlert("warning", 'Please validate the transaction on your ledger'); - } + args += "&psbt=" + encodeURIComponent(psbt); + args += "&hintChange=" + encodeURIComponent(hintChange); + + WriteAlert("warning", 'Please validate the transaction on your ledger'); var confirmButton = $("#confirm-button"); confirmButton.prop("disabled", true); @@ -77,7 +63,7 @@ WriteAlert("danger", result.error); } else { WriteAlert("success", 'Transaction broadcasted (' + result.transactionId + ')'); - window.location.replace(successCallback + "?txid=" + result.transactionId); + window.location.replace(loc.protocol + "//" + loc.host + successPath + "?txid=" + result.transactionId); } }); };