public static async Task <PSBT> SignTxAsync(HardwareWalletInfo hardwareWalletInfo, PSBT psbt) { var psbtString = psbt.ToBase64(); var networkString = Network == Network.Main ? "" : "--testnet"; JToken jtok = await SendCommandAsync($"{networkString} --device-type \"{hardwareWalletInfo.Type.ToString().ToLowerInvariant()}\" --device-path \"{hardwareWalletInfo.Path}\" signtx {psbtString}"); JObject json = jtok as JObject; var signedPsbtString = json.Value <string>("psbt"); var signedPsbt = PSBT.Parse(signedPsbtString, Network); signedPsbt.Finalize(); return(signedPsbt); }
public async Task <WalletProcessPSBTResponse> WalletProcessPSBTAsync(PSBT psbt, bool sign = true, SigHash sighashType = SigHash.All, bool bip32derivs = false) { if (psbt == null) { throw new ArgumentNullException(nameof(psbt)); } var response = await SendCommandAsync(RPCOperations.walletprocesspsbt, psbt.ToBase64(), sign, SigHashToString(sighashType), bip32derivs).ConfigureAwait(false); var result = (JObject)response.Result; var psbt2 = PSBT.Parse(result.Property("psbt").Value.Value <string>(), Network.Main); var complete = result.Property("complete").Value.Value <bool>(); return(new WalletProcessPSBTResponse(psbt2, complete)); }
public async Task <PSBT> SignPSBTAsync(PSBT psbt, CancellationToken cancellationToken = default) { if (psbt == null) { throw new ArgumentNullException(nameof(psbt)); } var psbtString = psbt.ToBase64(); var response = await SendCommandAsync( command : HwiCommands.SignTx, commandArguments : new string[] { psbtString }, cancellationToken : cancellationToken).ConfigureAwait(false); return(HwiParser.ParsePsbt(response, HwiClient.Network)); }
private async Task <IActionResult> TryHandleSigningCommands(WalletId walletId, PSBT psbt, string command, SigningContextModel signingContext, string actionBack) { signingContext.PSBT = psbt.ToBase64(); switch (command) { case "sign": var routeBack = new Dictionary <string, string> { { "action", actionBack }, { "walletId", walletId.ToString() } }; return(View("WalletSigningOptions", new WalletSigningOptionsModel(signingContext, routeBack))); case "vault": return(ViewVault(walletId, signingContext)); case "seed": return(SignWithSeed(walletId, signingContext)); case "nbx-seed": if (await CanUseHotWallet()) { var derivationScheme = GetDerivationSchemeSettings(walletId); if (derivationScheme.IsHotWallet) { var extKey = await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode) .GetMetadataAsync <string>(derivationScheme.AccountDerivation, WellknownMetadataKeys.MasterHDKey); return(SignWithSeed(walletId, new SignWithSeedViewModel { SeedOrKey = extKey, SigningContext = signingContext })); } } TempData.SetStatusMessageModel(new StatusMessageModel { Severity = StatusMessageModel.StatusSeverity.Error, Message = "NBX seed functionality is not available" }); break; } return(null); }
private IActionResult RedirectToWalletPSBT(WalletId walletId, PSBT psbt, string fileName = null) { var vm = new PostRedirectViewModel() { AspController = "Wallets", AspAction = nameof(WalletPSBT), Parameters = { new KeyValuePair <string, string>("psbt", psbt.ToBase64()) } }; if (!string.IsNullOrEmpty(fileName)) { vm.Parameters.Add(new KeyValuePair <string, string>("fileName", fileName)); } return(View("PostRedirect", vm)); }
private async Task <PSBT> SignTxImplAsync(HardwareWalletModels?deviceType, string devicePath, HDFingerprint?fingerprint, PSBT psbt, CancellationToken cancel) { var psbtString = psbt.ToBase64(); var response = await SendCommandAsync( options : BuildOptions(deviceType, devicePath, fingerprint), command : HwiCommands.SignTx, commandArguments : psbtString, openConsole : false, cancel).ConfigureAwait(false); PSBT signedPsbt = HwiParser.ParsePsbt(response, Network); if (!signedPsbt.IsAllFinalized()) { signedPsbt.Finalize(); } return(signedPsbt); }
public async Task <IActionResult> LedgerConnection( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, string command, // getinfo // getxpub int account = 0, // sendtoaddress string hintChange = null ) { if (!HttpContext.WebSockets.IsWebSocketRequest) { return(NotFound()); } var storeData = CurrentStore; var network = NetworkProvider.GetNetwork <BTCPayNetwork>(walletId.CryptoCode); if (network == null) { throw new FormatException("Invalid value for crypto code"); } PSBT psbt = GetAmbientPSBT(network.NBitcoinNetwork, true); var derivationSettings = GetDerivationSchemeSettings(walletId); var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); using (var normalOperationTimeout = new CancellationTokenSource()) using (var signTimeout = new CancellationTokenSource()) { normalOperationTimeout.CancelAfter(TimeSpan.FromMinutes(30)); var hw = new LedgerHardwareWalletService(webSocket); object result = null; try { if (command == "test") { result = await hw.Test(normalOperationTimeout.Token); } if (command == "sendtoaddress") { if (!_dashboard.IsFullySynched(network.CryptoCode, out var summary)) { throw new Exception($"{network.CryptoCode}: not started or fully synched"); } var accountKey = derivationSettings.GetSigningAccountKeySettings(); // Some deployment does not have the AccountKeyPath set, let's fix this... if (accountKey.AccountKeyPath == null) { // If the saved wallet key path is not present or incorrect, let's scan the wallet to see if it can sign strategy var foundKeyPath = await hw.FindKeyPathFromDerivation(network, derivationSettings.AccountDerivation, normalOperationTimeout.Token); accountKey.AccountKeyPath = foundKeyPath ?? throw new HardwareWalletException($"This store is not configured to use this ledger"); storeData.SetSupportedPaymentMethod(derivationSettings); await Repository.UpdateStore(storeData); } // If it has already the AccountKeyPath, we did not looked up for it, so we need to check if we are on the right ledger else { // Checking if ledger is right with the RootFingerprint is faster as it does not need to make a query to the parent xpub, // but some deployment does not have it, so let's use AccountKeyPath instead if (accountKey.RootFingerprint == null) { var actualPubKey = await hw.GetExtPubKey(network, accountKey.AccountKeyPath, normalOperationTimeout.Token); if (!derivationSettings.AccountDerivation.GetExtPubKeys().Any(p => p.GetPublicKey() == actualPubKey.GetPublicKey())) { throw new HardwareWalletException($"This store is not configured to use this ledger"); } } // We have the root fingerprint, we can check the root from it else { var actualPubKey = await hw.GetPubKey(network, new KeyPath(), normalOperationTimeout.Token); if (actualPubKey.GetHDFingerPrint() != accountKey.RootFingerprint.Value) { throw new HardwareWalletException($"This store is not configured to use this ledger"); } } } // Some deployment does not have the RootFingerprint set, let's fix this... if (accountKey.RootFingerprint == null) { accountKey.RootFingerprint = (await hw.GetPubKey(network, new KeyPath(), normalOperationTimeout.Token)).GetHDFingerPrint(); storeData.SetSupportedPaymentMethod(derivationSettings); await Repository.UpdateStore(storeData); } derivationSettings.RebaseKeyPaths(psbt); var changeAddress = string.IsNullOrEmpty(hintChange) ? null : BitcoinAddress.Create(hintChange, network.NBitcoinNetwork); signTimeout.CancelAfter(TimeSpan.FromMinutes(5)); psbt = await hw.SignTransactionAsync(psbt, accountKey.GetRootedKeyPath(), accountKey.AccountKey, changeAddress?.ScriptPubKey, signTimeout.Token); SetAmbientPSBT(null); result = new SendToAddressResult() { PSBT = psbt.ToBase64() }; } } catch (OperationCanceledException) { result = new LedgerTestResult() { Success = false, Error = "Timeout" }; } catch (Exception ex) { result = new LedgerTestResult() { Success = false, Error = ex.Message }; } finally { hw.Dispose(); } try { if (result != null) { UTF8Encoding UTF8NOBOM = new UTF8Encoding(false); var bytes = UTF8NOBOM.GetBytes(JsonConvert.SerializeObject(result, _serializerSettings)); await webSocket.SendAsync(new ArraySegment <byte>(bytes), WebSocketMessageType.Text, true, new CancellationTokenSource(2000).Token); } } catch { } finally { await webSocket.CloseSocket(); } } return(new EmptyResult()); }
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 TransactionBroadcasterViewModel() : base("Transaction Broadcaster") { Global = Locator.Current.GetService <Global>(); ButtonText = "Broadcast Transaction"; PasteCommand = ReactiveCommand.CreateFromTask(async() => { if (!string.IsNullOrEmpty(TransactionString)) { return; } var textToPaste = await Application.Current.Clipboard.GetTextAsync(); TransactionString = textToPaste; }); IObservable <bool> broadcastTransactionCanExecute = this .WhenAny(x => x.TransactionString, (transactionString) => !string.IsNullOrWhiteSpace(transactionString.Value)) .ObserveOn(RxApp.MainThreadScheduler); BroadcastTransactionCommand = ReactiveCommand.CreateFromTask( async() => await OnDoTransactionBroadcastAsync(), broadcastTransactionCanExecute); ImportTransactionCommand = ReactiveCommand.CreateFromTask( async() => { try { var ofd = new OpenFileDialog { AllowMultiple = false, Title = "Import Transaction" }; if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) { var initialDirectory = Path.Combine("/media", Environment.UserName); if (!Directory.Exists(initialDirectory)) { initialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Personal); } ofd.Directory = initialDirectory; } else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) { ofd.Directory = Environment.GetFolderPath(Environment.SpecialFolder.Personal); } var window = (Application.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime).MainWindow; var selected = await ofd.ShowAsync(window, fallBack: true); if (selected != null && selected.Any()) { var path = selected.First(); var psbtBytes = await File.ReadAllBytesAsync(path); PSBT psbt = null; Transaction transaction = null; try { psbt = PSBT.Load(psbtBytes, Global.Network); } catch { var text = await File.ReadAllTextAsync(path); text = text.Trim(); try { psbt = PSBT.Parse(text, Global.Network); } catch { transaction = Transaction.Parse(text, Global.Network); } } if (psbt != null) { if (!psbt.IsAllFinalized()) { psbt.Finalize(); } TransactionString = psbt.ToBase64(); } else { TransactionString = transaction.ToHex(); } } } catch (Exception ex) { Logger.LogError(ex); NotificationHelpers.Error(ex.ToUserFriendlyString()); } }, outputScheduler: RxApp.MainThreadScheduler); Observable .Merge(PasteCommand.ThrownExceptions) .Merge(BroadcastTransactionCommand.ThrownExceptions) .Merge(ImportTransactionCommand.ThrownExceptions) .ObserveOn(RxApp.TaskpoolScheduler) .Subscribe(ex => { NotificationHelpers.Error(ex.ToUserFriendlyString()); Logger.LogError(ex); }); }
public async Task <IActionResult> WalletPSBTReady( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, WalletPSBTReadyViewModel vm, string command = null, CancellationToken cancellationToken = default) { if (command == null) { return(await WalletPSBTReady(walletId, vm)); } PSBT psbt = null; var network = NetworkProvider.GetNetwork <BTCPayNetwork>(walletId.CryptoCode); DerivationSchemeSettings derivationSchemeSettings = null; try { psbt = PSBT.Parse(vm.SigningContext.PSBT, network.NBitcoinNetwork); derivationSchemeSettings = GetDerivationSchemeSettings(walletId); if (derivationSchemeSettings == null) { return(NotFound()); } await FetchTransactionDetails(derivationSchemeSettings, vm, network); } catch { vm.GlobalError = "Invalid PSBT"; return(View(nameof(WalletPSBTReady), vm)); } switch (command) { case "payjoin": string error = null; try { var proposedPayjoin = await GetPayjoinProposedTX(new BitcoinUrlBuilder(vm.SigningContext.PayJoinBIP21, network.NBitcoinNetwork), psbt, derivationSchemeSettings, network, cancellationToken); try { var extKey = ExtKey.Parse(vm.SigningKey, network.NBitcoinNetwork); proposedPayjoin = proposedPayjoin.SignAll(derivationSchemeSettings.AccountDerivation, extKey, RootedKeyPath.Parse(vm.SigningKeyPath), new SigningOptions() { EnforceLowR = !(vm.SigningContext?.EnforceLowR is false) }); 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)); } }
public SigningContextModel(PSBT psbt) { PSBT = psbt.ToBase64(); }
public async Task <IActionResult> WalletPSBTReady( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, WalletPSBTReadyViewModel vm, string command = null, CancellationToken cancellationToken = default) { if (command == null) { return(await WalletPSBTReady(walletId, vm.PSBT, vm.SigningKey, vm.SigningKeyPath, vm.OriginalPSBT, vm.PayJoinEndpointUrl)); } PSBT psbt = null; var network = NetworkProvider.GetNetwork <BTCPayNetwork>(walletId.CryptoCode); DerivationSchemeSettings derivationSchemeSettings = null; try { psbt = PSBT.Parse(vm.PSBT, network.NBitcoinNetwork); derivationSchemeSettings = GetDerivationSchemeSettings(walletId); if (derivationSchemeSettings == null) { return(NotFound()); } await FetchTransactionDetails(derivationSchemeSettings, vm, network); } catch { vm.GlobalError = "Invalid PSBT"; return(View(nameof(WalletPSBTReady), vm)); } switch (command) { case "payjoin": string error = null; try { var proposedPayjoin = await GetPayjoinProposedTX(vm.PayJoinEndpointUrl, psbt, derivationSchemeSettings, network, cancellationToken); try { var extKey = ExtKey.Parse(vm.SigningKey, network.NBitcoinNetwork); proposedPayjoin = proposedPayjoin.SignAll(derivationSchemeSettings.AccountDerivation, extKey, RootedKeyPath.Parse(vm.SigningKeyPath)); vm.PSBT = proposedPayjoin.ToBase64(); vm.OriginalPSBT = psbt.ToBase64(); proposedPayjoin.Finalize(); 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 refuse to sign this transaction, the payment will proceed without payjoin" }); return(ViewVault(walletId, proposedPayjoin, vm.PayJoinEndpointUrl, psbt)); } } 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(WalletPSBTReady), vm)); case "broadcast": { var transaction = psbt.ExtractTransaction(); try { var broadcastResult = await ExplorerClientProvider.GetExplorerClient(network).BroadcastAsync(transaction); if (!broadcastResult.Success) { if (!string.IsNullOrEmpty(vm.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.PSBT = vm.OriginalPSBT; vm.OriginalPSBT = null; return(await WalletPSBTReady(walletId, vm, "broadcast")); } vm.GlobalError = $"RPC Error while broadcasting: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}"; return(View(nameof(WalletPSBTReady), vm)); } } catch (Exception ex) { vm.GlobalError = "Error while broadcasting: " + ex.Message; return(View(nameof(WalletPSBTReady), vm)); } if (!TempData.HasStatusMessage()) { TempData[WellKnownTempData.SuccessMessage] = $"Transaction broadcasted successfully ({transaction.GetHash()})"; } return(RedirectToWalletTransaction(walletId, transaction)); } case "analyze-psbt": return(RedirectToWalletPSBT(psbt)); default: vm.GlobalError = "Unknown command"; return(View(nameof(WalletPSBTReady), vm)); } }
public async Task <IActionResult> WalletPSBTReady( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, WalletPSBTReadyViewModel vm, string command = null) { PSBT psbt = null; var network = NetworkProvider.GetNetwork <BTCPayNetwork>(walletId.CryptoCode); try { psbt = PSBT.Parse(vm.PSBT, network.NBitcoinNetwork); var derivationSchemeSettings = await GetDerivationSchemeSettings(walletId); if (derivationSchemeSettings == null) { return(NotFound()); } await FetchTransactionDetails(derivationSchemeSettings, vm, network); } catch { vm.GlobalError = "Invalid PSBT"; return(View(vm)); } if (command == "broadcast") { if (!psbt.IsAllFinalized() && !psbt.TryFinalize(out var errors)) { vm.SetErrors(errors); return(View(vm)); } var transaction = psbt.ExtractTransaction(); try { var broadcastResult = await ExplorerClientProvider.GetExplorerClient(network).BroadcastAsync(transaction); if (!broadcastResult.Success) { vm.GlobalError = $"RPC Error while broadcasting: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}"; return(View(vm)); } } catch (Exception ex) { vm.GlobalError = "Error while broadcasting: " + ex.Message; return(View(vm)); } return(await RedirectToWalletTransaction(walletId, transaction)); } else if (command == "analyze-psbt") { return(RedirectToAction(nameof(WalletPSBT), new { walletId = walletId, psbt = psbt.ToBase64() })); } else { vm.GlobalError = "Unknown command"; return(View(vm)); } }
public static async Task <PSBT> UTXOUpdatePSBT(this RPCClient rpcClient, PSBT psbt) { if (psbt == null) { throw new ArgumentNullException(nameof(psbt)); } var response = await rpcClient.SendCommandAsync("utxoupdatepsbt", new object[] { psbt.ToBase64() }); response.ThrowIfError(); if (response.Error == null && response.Result is JValue rpcResult && rpcResult.Value is string psbtStr) { return(PSBT.Parse(psbtStr, psbt.Network)); } throw new Exception("This should never happen"); }
public async Task <IActionResult> WalletPSBTReady( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, WalletPSBTReadyViewModel vm, string command = null, CancellationToken cancellationToken = default) { if (command == null) { return(await WalletPSBTReady(walletId, vm)); } PSBT psbt = null; var network = NetworkProvider.GetNetwork <BTCPayNetwork>(walletId.CryptoCode); DerivationSchemeSettings derivationSchemeSettings = null; try { psbt = PSBT.Parse(vm.SigningContext.PSBT, network.NBitcoinNetwork); derivationSchemeSettings = GetDerivationSchemeSettings(walletId); if (derivationSchemeSettings == null) { return(NotFound()); } await FetchTransactionDetails(derivationSchemeSettings, vm, network); } catch { vm.GlobalError = "Invalid PSBT"; return(View(nameof(WalletPSBTReady), vm)); } switch (command) { case "payjoin": string error = null; try { var proposedPayjoin = await GetPayjoinProposedTX(vm.SigningContext.PayJoinEndpointUrl, psbt, derivationSchemeSettings, network, cancellationToken); try { var extKey = ExtKey.Parse(vm.SigningKey, network.NBitcoinNetwork); proposedPayjoin = proposedPayjoin.SignAll(derivationSchemeSettings.AccountDerivation, extKey, RootedKeyPath.Parse(vm.SigningKeyPath), new SigningOptions() { EnforceLowR = !(vm.SigningContext?.EnforceLowR is false) }); vm.SigningContext.PSBT = proposedPayjoin.ToBase64(); vm.SigningContext.OriginalPSBT = psbt.ToBase64(); proposedPayjoin.Finalize(); var hash = proposedPayjoin.ExtractTransaction().GetHash(); _EventAggregator.Publish(new UpdateTransactionLabel() { WalletId = walletId, TransactionLabels = new Dictionary <uint256, List <(string color, string label)> >() { { hash, new List <(string color, string label)> { 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")); }
public async Task <IActionResult> WalletPSBTReady( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, WalletPSBTReadyViewModel vm, string command = null) { PSBT psbt = null; var network = NetworkProvider.GetNetwork(walletId.CryptoCode); try { psbt = PSBT.Parse(vm.PSBT, network.NBitcoinNetwork); await FetchTransactionDetails(walletId, vm, network); } catch { vm.Errors = new List <string>(); vm.Errors.Add("Invalid PSBT"); return(View(vm)); } if (command == "broadcast") { if (!psbt.IsAllFinalized() && !psbt.TryFinalize(out var errors)) { vm.Errors = new List <string>(); vm.Errors.AddRange(errors.Select(e => e.ToString())); return(View(vm)); } var transaction = psbt.ExtractTransaction(); try { var broadcastResult = await ExplorerClientProvider.GetExplorerClient(network).BroadcastAsync(transaction); if (!broadcastResult.Success) { vm.Errors = new List <string>(); vm.Errors.Add($"RPC Error while broadcasting: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}"); return(View(vm)); } } catch (Exception ex) { vm.Errors = new List <string>(); vm.Errors.Add("Error while broadcasting: " + ex.Message); return(View(vm)); } return(await RedirectToWalletTransaction(walletId, transaction)); } else if (command == "analyze-psbt") { return(RedirectToAction(nameof(WalletPSBT), new { walletId = walletId, psbt = psbt.ToBase64() })); } else { vm.Errors = new List <string>(); vm.Errors.Add("Unknown command"); return(View(vm)); } }