public async Task <IActionResult> WalletPSBT( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, WalletPSBTViewModel vm, string command = null) { var network = NetworkProvider.GetNetwork(walletId.CryptoCode); var psbt = vm.GetPSBT(network.NBitcoinNetwork); if (psbt == null) { ModelState.AddModelError(nameof(vm.PSBT), "Invalid PSBT"); return(View(vm)); } if (command == null) { vm.Decoded = psbt.ToString(); vm.FileName = string.Empty; return(View(vm)); } else if (command == "ledger") { return(ViewWalletSendLedger(psbt)); } else if (command == "broadcast") { if (!psbt.IsAllFinalized() && !psbt.TryFinalize(out var errors)) { return(ViewPSBT(psbt, errors)); } var transaction = psbt.ExtractTransaction(); try { var broadcastResult = await ExplorerClientProvider.GetExplorerClient(network).BroadcastAsync(transaction); if (!broadcastResult.Success) { return(ViewPSBT(psbt, new[] { $"RPC Error while broadcasting: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}" })); } } catch (Exception ex) { return(ViewPSBT(psbt, "Error while broadcasting: " + ex.Message)); } return(await RedirectToWalletTransaction(walletId, transaction)); } else if (command == "combine") { ModelState.Remove(nameof(vm.PSBT)); return(View(nameof(WalletPSBTCombine), new WalletPSBTCombineViewModel() { OtherPSBT = psbt.ToBase64() })); } else if (command == "save-psbt") { return(FilePSBT(psbt, vm.FileName)); } return(View(vm)); }
public async Task <IActionResult> WalletPSBT([ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, WalletPSBTViewModel vm) { var network = NetworkProvider.GetNetwork <BTCPayNetwork>(walletId.CryptoCode); vm.CryptoCode = network.CryptoCode; var derivationSchemeSettings = GetDerivationSchemeSettings(walletId); if (derivationSchemeSettings == null) { return(NotFound()); } vm.NBXSeedAvailable = await CanUseHotWallet() && !string.IsNullOrEmpty(await ExplorerClientProvider.GetExplorerClient(network) .GetMetadataAsync <string>(derivationSchemeSettings.AccountDerivation, WellknownMetadataKeys.Mnemonic)); if (await vm.GetPSBT(network.NBitcoinNetwork) is PSBT psbt) { vm.Decoded = psbt.ToString(); vm.PSBT = psbt.ToBase64(); vm.PSBTHex = psbt.ToHex(); } return(View(nameof(WalletPSBT), vm ?? new WalletPSBTViewModel() { CryptoCode = walletId.CryptoCode })); }
public async Task <IActionResult> WalletPSBT([ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, WalletPSBTViewModel vm) { var network = NetworkProvider.GetNetwork <BTCPayNetwork>(walletId.CryptoCode); vm.CryptoCode = network.CryptoCode; var derivationSchemeSettings = GetDerivationSchemeSettings(walletId); if (derivationSchemeSettings == null) { return(NotFound()); } vm.NBXSeedAvailable = await CanUseHotWallet() && derivationSchemeSettings.IsHotWallet; if (await vm.GetPSBT(network.NBitcoinNetwork) is PSBT psbt) { vm.PSBT = vm.SigningContext.PSBT = psbt.ToBase64(); vm.PSBTHex = psbt.ToHex(); vm.Decoded = psbt.ToString(); await FetchTransactionDetails(derivationSchemeSettings, vm, network); return(View("WalletPSBTDecoded", vm)); } return(View(vm)); }
public IActionResult WalletPSBT([ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, WalletPSBTViewModel vm) { var network = NetworkProvider.GetNetwork(walletId.CryptoCode); if (vm?.PSBT != null) { vm.Decoded = vm.GetPSBT(network.NBitcoinNetwork)?.ToString(); } return(View(vm ?? new WalletPSBTViewModel())); }
public async Task <IActionResult> WalletPSBT([ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, WalletPSBTViewModel vm) { var network = NetworkProvider.GetNetwork <BTCPayNetwork>(walletId.CryptoCode); if (await vm.GetPSBT(network.NBitcoinNetwork) is PSBT psbt) { vm.Decoded = psbt.ToString(); vm.PSBT = psbt.ToBase64(); } return(View(vm ?? new WalletPSBTViewModel())); }
public async Task <IActionResult> WalletSign([ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, WalletPSBTViewModel vm, string returnUrl = null, string command = null) { var network = NetworkProvider.GetNetwork <BTCPayNetwork>(walletId.CryptoCode); if (returnUrl is null) { returnUrl = Url.Action(nameof(WalletTransactions), new { walletId }); } var psbt = await vm.GetPSBT(network.NBitcoinNetwork); if (psbt is null || vm.InvalidPSBT) { ModelState.AddModelError(nameof(vm.PSBT), "Invalid PSBT"); return(View("WalletSigningOptions", new WalletSigningOptionsModel(vm.SigningContext, returnUrl))); } switch (command) { case "vault": return(ViewVault(walletId, vm.SigningContext)); case "seed": return(SignWithSeed(walletId, vm.SigningContext)); case "decode": return(await WalletPSBT(walletId, vm, "decode")); default: break; } if (await CanUseHotWallet()) { var derivationScheme = GetDerivationSchemeSettings(walletId); if (derivationScheme.IsHotWallet) { var extKey = await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode) .GetMetadataAsync <string>(derivationScheme.AccountDerivation, WellknownMetadataKeys.MasterHDKey); if (extKey != null) { return(await SignWithSeed(walletId, new SignWithSeedViewModel { SeedOrKey = extKey, SigningContext = vm.SigningContext })); } } } return(View("WalletSigningOptions", new WalletSigningOptionsModel(vm.SigningContext, returnUrl))); }
public async Task <IActionResult> WalletPSBT( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, WalletPSBTViewModel vm, string command = null) { var network = NetworkProvider.GetNetwork(walletId.CryptoCode); var psbt = await vm.GetPSBT(network.NBitcoinNetwork); if (psbt == null) { ModelState.AddModelError(nameof(vm.PSBT), "Invalid PSBT"); return(View(vm)); } switch (command) { case null: vm.Decoded = psbt.ToString(); ModelState.Remove(nameof(vm.PSBT)); ModelState.Remove(nameof(vm.FileName)); ModelState.Remove(nameof(vm.UploadedPSBTFile)); vm.PSBT = psbt.ToBase64(); vm.FileName = vm.UploadedPSBTFile?.FileName; return(View(vm)); case "ledger": return(ViewWalletSendLedger(psbt)); case "seed": return(SignWithSeed(walletId, psbt.ToBase64())); case "broadcast": { return(await WalletPSBTReady(walletId, psbt.ToBase64())); } 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: return(View(vm)); } }
public async Task <IActionResult> WalletPSBTReady( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, WalletPSBTViewModel vm, string command, CancellationToken cancellationToken = default) { var network = NetworkProvider.GetNetwork <BTCPayNetwork>(walletId.CryptoCode); PSBT psbt = await vm.GetPSBT(network.NBitcoinNetwork); if (vm.InvalidPSBT || psbt is null) { if (vm.InvalidPSBT) { vm.GlobalError = "Invalid PSBT"; } return(View(nameof(WalletPSBT), vm)); } DerivationSchemeSettings derivationSchemeSettings = GetDerivationSchemeSettings(walletId); if (derivationSchemeSettings == null) { return(NotFound()); } await FetchTransactionDetails(derivationSchemeSettings, vm, network); switch (command) { case "payjoin": string error; try { var proposedPayjoin = await GetPayjoinProposedTX(new BitcoinUrlBuilder(vm.SigningContext.PayJoinBIP21, network.NBitcoinNetwork), psbt, derivationSchemeSettings, network, cancellationToken); try { proposedPayjoin.Settings.SigningOptions = new SigningOptions { EnforceLowR = !(vm.SigningContext?.EnforceLowR is false) }; var extKey = ExtKey.Parse(vm.SigningKey, network.NBitcoinNetwork); proposedPayjoin = proposedPayjoin.SignAll(derivationSchemeSettings.AccountDerivation, extKey, RootedKeyPath.Parse(vm.SigningKeyPath)); vm.SigningContext.PSBT = proposedPayjoin.ToBase64(); vm.SigningContext.OriginalPSBT = psbt.ToBase64(); proposedPayjoin.Finalize(); var hash = proposedPayjoin.ExtractTransaction().GetHash(); _EventAggregator.Publish(new UpdateTransactionLabel(walletId, hash, UpdateTransactionLabel.PayjoinLabelTemplate())); TempData.SetStatusMessageModel(new StatusMessageModel { Severity = StatusMessageModel.StatusSeverity.Success, AllowDismiss = false, Html = $"The payjoin transaction has been successfully broadcasted ({proposedPayjoin.ExtractTransaction().GetHash()})" }); return(await WalletPSBTReady(walletId, vm, "broadcast")); } catch (Exception) { TempData.SetStatusMessageModel(new StatusMessageModel() { Severity = StatusMessageModel.StatusSeverity.Warning, AllowDismiss = false, Html = "This transaction has been coordinated between the receiver and you to create a <a href='https://en.bitcoin.it/wiki/PayJoin' target='_blank'>payjoin transaction</a> by adding inputs from the receiver.<br/>" + "The amount being sent may appear higher but is in fact almost same.<br/><br/>" + "If you cancel or refuse to sign this transaction, the payment will proceed without payjoin" }); vm.SigningContext.PSBT = proposedPayjoin.ToBase64(); vm.SigningContext.OriginalPSBT = psbt.ToBase64(); return(ViewVault(walletId, vm.SigningContext)); } } catch (PayjoinReceiverException ex) { error = $"The payjoin receiver could not complete the payjoin: {ex.Message}"; } catch (PayjoinSenderException ex) { error = $"We rejected the receiver's payjoin proposal: {ex.Message}"; } catch (Exception ex) { error = $"Unexpected payjoin error: {ex.Message}"; } //we possibly exposed the tx to the receiver, so we need to broadcast straight away psbt.Finalize(); TempData.SetStatusMessageModel(new StatusMessageModel { Severity = StatusMessageModel.StatusSeverity.Warning, AllowDismiss = false, Html = $"The payjoin transaction could not be created.<br/>" + $"The original transaction was broadcasted instead. ({psbt.ExtractTransaction().GetHash()})<br/><br/>" + $"{error}" }); return(await WalletPSBTReady(walletId, vm, "broadcast")); case "broadcast" when !psbt.IsAllFinalized() && !psbt.TryFinalize(out var errors): vm.SetErrors(errors); return(View(nameof(WalletPSBT), vm)); case "broadcast": { var transaction = psbt.ExtractTransaction(); try { var broadcastResult = await ExplorerClientProvider.GetExplorerClient(network).BroadcastAsync(transaction); if (!broadcastResult.Success) { if (!string.IsNullOrEmpty(vm.SigningContext.OriginalPSBT)) { TempData.SetStatusMessageModel(new StatusMessageModel { Severity = StatusMessageModel.StatusSeverity.Warning, AllowDismiss = false, Html = $"The payjoin transaction could not be broadcasted.<br/>({broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}).<br/>The transaction has been reverted back to its original format and has been broadcast." }); vm.SigningContext.PSBT = vm.SigningContext.OriginalPSBT; vm.SigningContext.OriginalPSBT = null; return(await WalletPSBTReady(walletId, vm, "broadcast")); } vm.GlobalError = $"RPC Error while broadcasting: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}"; return(View(nameof(WalletPSBT), vm)); } else { var wallet = _walletProvider.GetWallet(network); var derivationSettings = GetDerivationSchemeSettings(walletId); wallet.InvalidateCache(derivationSettings.AccountDerivation); } } catch (Exception ex) { vm.GlobalError = "Error while broadcasting: " + ex.Message; return(View(nameof(WalletPSBT), vm)); } if (!TempData.HasStatusMessage()) { TempData[WellKnownTempData.SuccessMessage] = $"Transaction broadcasted successfully ({transaction.GetHash()})"; } var returnUrl = this.HttpContext.Request.Query["returnUrl"].FirstOrDefault(); if (returnUrl is not null) { return(Redirect(returnUrl)); } return(RedirectToAction(nameof(WalletTransactions), new { walletId = walletId.ToString() })); } case "analyze-psbt": return(RedirectToWalletPSBT(new WalletPSBTViewModel() { PSBT = psbt.ToBase64() })); case "decode": await FetchTransactionDetails(derivationSchemeSettings, vm, network); return(View("WalletPSBTDecoded", vm)); default: vm.GlobalError = "Unknown command"; return(View(nameof(WalletPSBT), vm)); } }
public async Task <IActionResult> WalletPSBT( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, WalletPSBTViewModel vm, string command) { var network = NetworkProvider.GetNetwork <BTCPayNetwork>(walletId.CryptoCode); vm.CryptoCode = network.CryptoCode; var derivationSchemeSettings = GetDerivationSchemeSettings(walletId); if (derivationSchemeSettings == null) { return(NotFound()); } vm.NBXSeedAvailable = await CanUseHotWallet() && derivationSchemeSettings.IsHotWallet; var psbt = await vm.GetPSBT(network.NBitcoinNetwork); if (vm.InvalidPSBT) { ModelState.AddModelError(nameof(vm.PSBT), "Invalid PSBT"); return(View(vm)); } if (psbt is null) { return(View("WalletPSBT", vm)); } switch (command) { case "sign": return(await WalletSign(walletId, vm)); case "decode": ModelState.Remove(nameof(vm.PSBT)); ModelState.Remove(nameof(vm.FileName)); ModelState.Remove(nameof(vm.UploadedPSBTFile)); await FetchTransactionDetails(derivationSchemeSettings, vm, network); return(View("WalletPSBTDecoded", vm)); case "save-psbt": return(FilePSBT(psbt, vm.FileName)); case "update": psbt = await ExplorerClientProvider.UpdatePSBT(derivationSchemeSettings, psbt); if (psbt == null) { TempData[WellKnownTempData.ErrorMessage] = "You need to update your version of NBXplorer"; return(View(vm)); } TempData[WellKnownTempData.SuccessMessage] = "PSBT updated!"; return(RedirectToWalletPSBT(new WalletPSBTViewModel { PSBT = psbt.ToBase64(), FileName = vm.FileName })); case "combine": ModelState.Remove(nameof(vm.PSBT)); return(View(nameof(WalletPSBTCombine), new WalletPSBTCombineViewModel { OtherPSBT = psbt.ToBase64() })); case "broadcast": { return(RedirectToWalletPSBTReady(new WalletPSBTReadyViewModel { SigningContext = new SigningContextModel(psbt) })); } default: return(View("WalletPSBTDecoded", vm)); } }
public async Task <IActionResult> WalletPSBT( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, WalletPSBTViewModel vm, string command = null) { if (command == null) { return(await WalletPSBT(walletId, vm)); } var network = NetworkProvider.GetNetwork <BTCPayNetwork>(walletId.CryptoCode); vm.CryptoCode = network.CryptoCode; var psbt = await vm.GetPSBT(network.NBitcoinNetwork); if (psbt == null) { ModelState.AddModelError(nameof(vm.PSBT), "Invalid PSBT"); return(View(vm)); } switch (command) { case "decode": vm.Decoded = psbt.ToString(); ModelState.Remove(nameof(vm.PSBT)); ModelState.Remove(nameof(vm.FileName)); ModelState.Remove(nameof(vm.UploadedPSBTFile)); vm.PSBT = psbt.ToBase64(); vm.FileName = vm.UploadedPSBTFile?.FileName; return(View(vm)); case "vault": return(ViewVault(walletId, psbt)); case "ledger": return(ViewWalletSendLedger(walletId, psbt)); case "update": var derivationSchemeSettings = GetDerivationSchemeSettings(walletId); psbt = await UpdatePSBT(derivationSchemeSettings, psbt, network); if (psbt == null) { ModelState.AddModelError(nameof(vm.PSBT), "You need to update your version of NBXplorer"); return(View(vm)); } TempData[WellKnownTempData.SuccessMessage] = "PSBT updated!"; return(RedirectToWalletPSBT(walletId, psbt, vm.FileName)); case "seed": return(SignWithSeed(walletId, psbt.ToBase64())); case "nbx-seed": var derivationScheme = GetDerivationSchemeSettings(walletId); var extKey = await ExplorerClientProvider.GetExplorerClient(network) .GetMetadataAsync <string>(derivationScheme.AccountDerivation, WellknownMetadataKeys.MasterHDKey); return(await SignWithSeed(walletId, new SignWithSeedViewModel() { SeedOrKey = extKey, PSBT = psbt.ToBase64() })); case "broadcast": { return(await WalletPSBTReady(walletId, psbt.ToBase64())); } 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: return(View(vm)); } }
public async Task <IActionResult> WalletPSBT( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, WalletPSBTViewModel vm, string command = null) { if (command == null) { return(await WalletPSBT(walletId, vm)); } var network = NetworkProvider.GetNetwork <BTCPayNetwork>(walletId.CryptoCode); vm.CryptoCode = network.CryptoCode; vm.NBXSeedAvailable = await CanUseHotWallet() && !string.IsNullOrEmpty(await ExplorerClientProvider.GetExplorerClient(network) .GetMetadataAsync <string>(GetDerivationSchemeSettings(walletId).AccountDerivation, WellknownMetadataKeys.Mnemonic)); var psbt = await vm.GetPSBT(network.NBitcoinNetwork); if (psbt == null) { ModelState.AddModelError(nameof(vm.PSBT), "Invalid PSBT"); return(View(vm)); } var res = await TryHandleSigningCommands(walletId, psbt, command, new SigningContextModel(psbt)); if (res != null) { return(res); } switch (command) { case "decode": vm.Decoded = psbt.ToString(); ModelState.Remove(nameof(vm.PSBT)); ModelState.Remove(nameof(vm.FileName)); ModelState.Remove(nameof(vm.UploadedPSBTFile)); vm.PSBT = psbt.ToBase64(); vm.FileName = vm.UploadedPSBTFile?.FileName; return(View(vm)); case "update": var derivationSchemeSettings = GetDerivationSchemeSettings(walletId); psbt = await ExplorerClientProvider.UpdatePSBT(derivationSchemeSettings, psbt); if (psbt == null) { ModelState.AddModelError(nameof(vm.PSBT), "You need to update your version of NBXplorer"); return(View(vm)); } TempData[WellKnownTempData.SuccessMessage] = "PSBT updated!"; return(RedirectToWalletPSBT(new WalletPSBTViewModel() { PSBT = psbt.ToBase64(), FileName = vm.FileName })); case "broadcast": { return(RedirectToWalletPSBTReady(new WalletPSBTReadyViewModel() { 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: return(View(vm)); } }
public async Task <IActionResult> WalletPSBT( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, WalletPSBTViewModel vm, string command = null) { var network = NetworkProvider.GetNetwork <BTCPayNetwork>(walletId.CryptoCode); var psbt = await vm.GetPSBT(network.NBitcoinNetwork); if (psbt == null) { ModelState.AddModelError(nameof(vm.PSBT), "Invalid PSBT"); return(View(vm)); } switch (command) { case null: vm.Decoded = psbt.ToString(); ModelState.Remove(nameof(vm.PSBT)); ModelState.Remove(nameof(vm.FileName)); ModelState.Remove(nameof(vm.UploadedPSBTFile)); vm.PSBT = psbt.ToBase64(); vm.FileName = vm.UploadedPSBTFile?.FileName; return(View(vm)); case "ledger": return(ViewWalletSendLedger(psbt)); case "update": var derivationSchemeSettings = await GetDerivationSchemeSettings(walletId); psbt = await UpdatePSBT(derivationSchemeSettings, psbt, network); if (psbt == null) { ModelState.AddModelError(nameof(vm.PSBT), "You need to update your version of NBXplorer"); return(View(vm)); } StatusMessage = "PSBT updated!"; return(RedirectToAction(nameof(WalletPSBT), new { walletId = walletId, psbt = psbt.ToBase64(), FileName = vm.FileName })); case "seed": return(SignWithSeed(walletId, psbt.ToBase64())); case "broadcast": { return(await WalletPSBTReady(walletId, psbt.ToBase64())); } 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: return(View(vm)); } }