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, 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 void ActivateLTC() { LTCExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_LTCRPCCONNECTION", "server=http://127.0.0.1:43783;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork <BTCPayNetwork>("LTC").NBitcoinNetwork); LTCExplorerClient = new ExplorerClient(NetworkProvider.GetNetwork <BTCPayNetwork>("LTC").NBXplorerNetwork, new Uri(GetEnvironment("TESTS_LTCNBXPLORERURL", "http://127.0.0.1:32838/"))); PayTester.Chains.Add("LTC"); PayTester.LTCNBXplorerUri = LTCExplorerClient.Address; }
public async Task <IActionResult> LedgerConnection( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, string command, // getinfo // getxpub int account = 0, // sendtoaddress string psbt = null, 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"); } 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); var model = new WalletSendLedgerModel(); 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); } var psbtResponse = new CreatePSBTResponse() { PSBT = PSBT.Parse(psbt, network.NBitcoinNetwork), ChangeAddress = string.IsNullOrEmpty(hintChange) ? null : BitcoinAddress.Create(hintChange, network.NBitcoinNetwork) }; derivationSettings.RebaseKeyPaths(psbtResponse.PSBT); signTimeout.CancelAfter(TimeSpan.FromMinutes(5)); psbtResponse.PSBT = await hw.SignTransactionAsync(psbtResponse.PSBT, accountKey.GetRootedKeyPath(), accountKey.AccountKey, psbtResponse.ChangeAddress?.ScriptPubKey, signTimeout.Token); result = new SendToAddressResult() { PSBT = psbtResponse.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> 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 void LoadArgs(IConfiguration conf) { NetworkType = DefaultConfiguration.GetNetworkType(conf); DataDir = conf.GetDataDir(NetworkType); Logs.Configuration.LogInformation("Network: " + NetworkType.ToString()); if (conf.GetOrDefault <bool>("launchsettings", false) && NetworkType != NetworkType.Regtest) { throw new ConfigException($"You need to run BTCPayServer with the run.sh or run.ps1 script"); } var supportedChains = conf.GetOrDefault <string>("chains", "btc") .Split(',', StringSplitOptions.RemoveEmptyEntries) .Select(t => t.ToUpperInvariant()).ToHashSet(); var networkProvider = new BTCPayNetworkProvider(NetworkType); var filtered = networkProvider.Filter(supportedChains.ToArray()); var elementsBased = filtered.GetAll().OfType <ElementsBTCPayNetwork>(); var parentChains = elementsBased.Select(network => network.NetworkbitcoinCode.ToUpperInvariant()).Distinct(); var allSubChains = networkProvider.GetAll().OfType <ElementsBTCPayNetwork>() .Where(network => parentChains.Contains(network.NetworkbitcoinCode)).Select(network => network.bitcoinCode.ToUpperInvariant()); supportedChains.AddRange(allSubChains); NetworkProvider = networkProvider.Filter(supportedChains.ToArray()); foreach (var chain in supportedChains) { if (NetworkProvider.GetNetwork <BTCPayNetworkBase>(chain) == null) { throw new ConfigException($"Invalid chains \"{chain}\""); } } var validChains = new List <string>(); foreach (var net in NetworkProvider.GetAll().OfType <BTCPayNetwork>()) { NBXplorerConnectionSetting setting = new NBXplorerConnectionSetting(); setting.bitcoinCode = net.bitcoinCode; setting.ExplorerUri = conf.GetOrDefault <Uri>($"{net.bitcoinCode}.explorer.url", net.NBXplorerNetwork.DefaultSettings.DefaultUrl); setting.CookieFile = conf.GetOrDefault <string>($"{net.bitcoinCode}.explorer.cookiefile", net.NBXplorerNetwork.DefaultSettings.DefaultCookieFile); NBXplorerConnectionSettings.Add(setting); { var lightning = conf.GetOrDefault <string>($"{net.bitcoinCode}.lightning", string.Empty); if (lightning.Length != 0) { if (!LightningConnectionString.TryParse(lightning, true, out var connectionString, out var error)) { Logs.Configuration.LogWarning($"Invalid setting {net.bitcoinCode}.lightning, " + Environment.NewLine + $"If you have a c-lightning server use: 'type=clightning;server=/root/.lightning/lightning-rpc', " + Environment.NewLine + $"If you have a lightning charge server: 'type=charge;server=https://charge.example.com;api-token=yourapitoken'" + Environment.NewLine + $"If you have a lnd server: 'type=lnd-rest;server=https://lnd:[email protected];macaroon=abf239...;certthumbprint=2abdf302...'" + Environment.NewLine + $" lnd server: 'type=lnd-rest;server=https://lnd:[email protected];macaroonfilepath=/root/.lnd/admin.macaroon;certthumbprint=2abdf302...'" + Environment.NewLine + $"If you have an eclair server: 'type=eclair;server=http://eclair.com:4570;password=eclairpassword;bitcoin-host=bitcoind:37393;bitcoin-auth=bitcoinrpcuser:bitcoinrpcpassword" + Environment.NewLine + $" eclair server: 'type=eclair;server=http://eclair.com:4570;password=eclairpassword;bitcoin-host=bitcoind:37393" + Environment.NewLine + $"Error: {error}" + Environment.NewLine + "This service will not be exposed through BTCPay Server"); } else { if (connectionString.IsLegacy) { Logs.Configuration.LogWarning($"Setting {net.bitcoinCode}.lightning is a deprecated format, it will work now, but please replace it for future versions with '{connectionString.ToString()}'"); } InternalLightningBybitcoinCode.Add(net.bitcoinCode, connectionString); } } } ExternalServices.Load(net.bitcoinCode, conf); }
public async Task <IActionResult> SignWithSeed([ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, SignWithSeedViewModel viewModel) { if (!ModelState.IsValid) { return(View(viewModel)); } var network = NetworkProvider.GetNetwork <BTCPayNetwork>(walletId.CryptoCode); if (network == null) { throw new FormatException("Invalid value for crypto code"); } ExtKey extKey = viewModel.GetExtKey(network.NBitcoinNetwork); if (extKey == null) { ModelState.AddModelError(nameof(viewModel.SeedOrKey), "Seed or Key was not in a valid format. It is either the 12/24 words or starts with xprv"); } var psbt = PSBT.Parse(viewModel.PSBT, network.NBitcoinNetwork); if (!psbt.IsReadyToSign()) { ModelState.AddModelError(nameof(viewModel.PSBT), "PSBT is not ready to be signed"); } if (!ModelState.IsValid) { return(View(viewModel)); } ExtKey signingKey = null; var settings = GetDerivationSchemeSettings(walletId); var signingKeySettings = settings.GetSigningAccountKeySettings(); if (signingKeySettings.RootFingerprint is null) { signingKeySettings.RootFingerprint = extKey.GetPublicKey().GetHDFingerPrint(); } RootedKeyPath rootedKeyPath = signingKeySettings.GetRootedKeyPath(); // The user gave the root key, let's try to rebase the PSBT, and derive the account private key if (rootedKeyPath?.MasterFingerprint == extKey.GetPublicKey().GetHDFingerPrint()) { psbt.RebaseKeyPaths(signingKeySettings.AccountKey, rootedKeyPath); signingKey = extKey.Derive(rootedKeyPath.KeyPath); } // The user maybe gave the account key, let's try to sign with it else { signingKey = extKey; } var balanceChange = psbt.GetBalance(settings.AccountDerivation, signingKey, rootedKeyPath); if (balanceChange == Money.Zero) { ModelState.AddModelError(nameof(viewModel.SeedOrKey), "This seed is unable to sign this transaction. Either the seed is incorrect, or the account path has not been properly configured in the Wallet Settings."); return(View(viewModel)); } psbt.SignAll(settings.AccountDerivation, signingKey, rootedKeyPath); ModelState.Remove(nameof(viewModel.PSBT)); return(await WalletPSBTReady(walletId, psbt.ToBase64(), signingKey.GetWif(network.NBitcoinNetwork).ToString(), rootedKeyPath?.ToString())); }
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": if (await CanUseHotWallet()) { 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() })); } return(View(vm)); 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, WalletPSBTReadyViewModel vm, string command = null) { if (command == null) { return(await WalletPSBTReady(walletId, vm.PSBT, vm.SigningKey, vm.SigningKeyPath)); } PSBT psbt = null; var network = NetworkProvider.GetNetwork <BTCPayNetwork>(walletId.CryptoCode); try { psbt = PSBT.Parse(vm.PSBT, network.NBitcoinNetwork); var derivationSchemeSettings = 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(RedirectToWalletTransaction(walletId, transaction)); } else if (command == "analyze-psbt") { return(RedirectToWalletPSBT(walletId, psbt)); } else { vm.GlobalError = "Unknown command"; return(View(vm)); } }
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 { 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(WalletPSBTReady), 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(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(new WalletPSBTViewModel() { PSBT = psbt.ToBase64() })); default: vm.GlobalError = "Unknown command"; return(View(nameof(WalletPSBTReady), vm)); } }
public async Task <IActionResult> LedgerConnection( string command, // getinfo string cryptoCode = null, // getxpub [ModelBinder(typeof(ModelBinders.DerivationSchemeModelBinder))] DerivationStrategyBase derivationScheme = null, int account = 0, // sendtoaddress string destination = null, string amount = null, string feeRate = null, string substractFees = null ) { if (!HttpContext.WebSockets.IsWebSocketRequest) { return(NotFound()); } var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); using (var normalOperationTimeout = new CancellationTokenSource()) using (var signTimeout = new CancellationTokenSource()) { normalOperationTimeout.CancelAfter(TimeSpan.FromMinutes(30)); var hw = new HardwareWalletService(webSocket); 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"); } } BitcoinAddress destinationAddress = null; if (destination != null) { try { destinationAddress = BitcoinAddress.Create(destination, network.NBitcoinNetwork); } catch { } if (destinationAddress == null) { throw new FormatException("Invalid value for destination"); } } FeeRate feeRateValue = null; if (feeRate != null) { try { feeRateValue = new FeeRate(Money.Satoshis(int.Parse(feeRate, CultureInfo.InvariantCulture)), 1); } catch { } if (feeRateValue == null || feeRateValue.FeePerK <= Money.Zero) { throw new FormatException("Invalid value for fee rate"); } } Money amountBTC = null; if (amount != null) { try { amountBTC = Money.Parse(amount); } catch { } if (amountBTC == null || amountBTC <= Money.Zero) { throw new FormatException("Invalid value for amount"); } } bool subsctractFeesValue = false; if (substractFees != null) { try { subsctractFeesValue = bool.Parse(substractFees); } catch { throw new FormatException("Invalid value for subtract fees"); } } if (command == "test") { result = await hw.Test(normalOperationTimeout.Token); } if (command == "getxpub") { var getxpubResult = await hw.GetExtPubKey(network, account, normalOperationTimeout.Token); result = getxpubResult; } if (command == "getinfo") { var strategy = GetDirectDerivationStrategy(derivationScheme); if (strategy == null || await hw.GetKeyPath(network, strategy, normalOperationTimeout.Token) == null) { throw new Exception($"This store is not configured to use this ledger"); } var feeProvider = _feeRateProvider.CreateFeeProvider(network); var recommendedFees = feeProvider.GetFeeRateAsync(); var balance = _walletProvider.GetWallet(network).GetBalance(derivationScheme); result = new GetInfoResult() { Balance = (double)(await balance).ToDecimal(MoneyUnit.BTC), RecommendedSatoshiPerByte = (int)(await recommendedFees).GetFee(1).Satoshi }; } if (command == "sendtoaddress") { if (!_dashboard.IsFullySynched(network.CryptoCode, out var summary)) { throw new Exception($"{network.CryptoCode}: not started or fully synched"); } var strategy = GetDirectDerivationStrategy(derivationScheme); var wallet = _walletProvider.GetWallet(network); var change = wallet.GetChangeAddressAsync(derivationScheme); var unspentCoins = await wallet.GetUnspentCoins(derivationScheme); var changeAddress = await change; var send = new[] { (
public async Task <IActionResult> LedgerConnection( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, string command, // getinfo // getxpub int account = 0, // sendtoaddress bool noChange = false, string destination = null, string amount = null, string feeRate = null, string substractFees = null ) { if (!HttpContext.WebSockets.IsWebSocketRequest) { return(NotFound()); } var cryptoCode = walletId.CryptoCode; var storeData = (await Repository.FindStore(walletId.StoreId, GetUserId())); var derivationScheme = GetPaymentMethod(walletId, storeData).DerivationStrategyBase; var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); using (var normalOperationTimeout = new CancellationTokenSource()) using (var signTimeout = new CancellationTokenSource()) { normalOperationTimeout.CancelAfter(TimeSpan.FromMinutes(30)); var hw = new HardwareWalletService(webSocket); 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"); } } BitcoinAddress destinationAddress = null; if (destination != null) { try { destinationAddress = BitcoinAddress.Create(destination.Trim(), network.NBitcoinNetwork); } catch { } if (destinationAddress == null) { throw new FormatException("Invalid value for destination"); } } FeeRate feeRateValue = null; if (feeRate != null) { try { feeRateValue = new FeeRate(Money.Satoshis(int.Parse(feeRate, CultureInfo.InvariantCulture)), 1); } catch { } if (feeRateValue == null || feeRateValue.FeePerK <= Money.Zero) { throw new FormatException("Invalid value for fee rate"); } } Money amountBTC = null; if (amount != null) { try { amountBTC = Money.Parse(amount); } catch { } if (amountBTC == null || amountBTC <= Money.Zero) { throw new FormatException("Invalid value for amount"); } } bool subsctractFeesValue = false; if (substractFees != null) { try { subsctractFeesValue = bool.Parse(substractFees); } catch { throw new FormatException("Invalid value for subtract fees"); } } 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 strategy = GetDirectDerivationStrategy(derivationScheme); var wallet = _walletProvider.GetWallet(network); var change = wallet.GetChangeAddressAsync(derivationScheme); var keypaths = new Dictionary <Script, KeyPath>(); List <Coin> availableCoins = new List <Coin>(); foreach (var c in await wallet.GetUnspentCoins(derivationScheme)) { keypaths.TryAdd(c.Coin.ScriptPubKey, c.KeyPath); availableCoins.Add(c.Coin); } var changeAddress = await change; var storeBlob = storeData.GetStoreBlob(); var paymentId = new Payments.PaymentMethodId(cryptoCode, Payments.PaymentTypes.BTCLike); var foundKeyPath = storeBlob.GetWalletKeyPathRoot(paymentId); // Some deployment have the wallet root key path saved in the store blob // If it does, we only have to make 1 call to the hw to check if it can sign the given strategy, if (foundKeyPath == null || !await hw.CanSign(network, strategy, foundKeyPath, normalOperationTimeout.Token)) { // If the saved wallet key path is not present or incorrect, let's scan the wallet to see if it can sign strategy foundKeyPath = await hw.FindKeyPath(network, strategy, normalOperationTimeout.Token); if (foundKeyPath == null) { throw new HardwareWalletException($"This store is not configured to use this ledger"); } storeBlob.SetWalletKeyPathRoot(paymentId, foundKeyPath); storeData.SetStoreBlob(storeBlob); await Repository.UpdateStore(storeData); } retry: var send = new[] { (