private async Task <PSBT> UpdatePSBT(DerivationSchemeSettings derivationSchemeSettings, PSBT psbt, BTCPayNetwork network) { var result = await ExplorerClientProvider.GetExplorerClient(network).UpdatePSBTAsync(new UpdatePSBTRequest() { PSBT = psbt, DerivationScheme = derivationSchemeSettings.AccountDerivation, }); if (result == null) { return(null); } derivationSchemeSettings.RebaseKeyPaths(result.PSBT); return(result.PSBT); }
public async Task <IActionResult> WalletSend( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, WalletSendModel vm, string command = "", CancellationToken cancellation = default) { if (walletId?.StoreId == null) { return(NotFound()); } var store = await Repository.FindStore(walletId.StoreId, GetUserId()); if (store == null) { return(NotFound()); } var network = this.NetworkProvider.GetNetwork <BTCPayNetwork>(walletId?.CryptoCode); if (network == null || network.ReadonlyWallet) { return(NotFound()); } vm.SupportRBF = network.SupportRBF; decimal transactionAmountSum = 0; if (command == "add-output") { ModelState.Clear(); vm.Outputs.Add(new WalletSendModel.TransactionOutput()); return(View(vm)); } if (command.StartsWith("remove-output", StringComparison.InvariantCultureIgnoreCase)) { ModelState.Clear(); var index = int.Parse(command.Substring(command.IndexOf(":", StringComparison.InvariantCultureIgnoreCase) + 1), CultureInfo.InvariantCulture); vm.Outputs.RemoveAt(index); return(View(vm)); } if (!vm.Outputs.Any()) { ModelState.AddModelError(string.Empty, "Please add at least one transaction output"); return(View(vm)); } var subtractFeesOutputsCount = new List <int>(); var substractFees = vm.Outputs.Any(o => o.SubtractFeesFromOutput); for (var i = 0; i < vm.Outputs.Count; i++) { var transactionOutput = vm.Outputs[i]; if (transactionOutput.SubtractFeesFromOutput) { subtractFeesOutputsCount.Add(i); } transactionOutput.DestinationAddress = transactionOutput.DestinationAddress?.Trim() ?? string.Empty; try { BitcoinAddress.Create(transactionOutput.DestinationAddress, network.NBitcoinNetwork); } catch { var inputName = string.Format(CultureInfo.InvariantCulture, "Outputs[{0}].", i.ToString(CultureInfo.InvariantCulture)) + nameof(transactionOutput.DestinationAddress); ModelState.AddModelError(inputName, "Invalid address"); } if (transactionOutput.Amount.HasValue) { transactionAmountSum += transactionOutput.Amount.Value; if (vm.CurrentBalance == transactionOutput.Amount.Value && !transactionOutput.SubtractFeesFromOutput) { vm.AddModelError(model => model.Outputs[i].SubtractFeesFromOutput, "You are sending your entire balance to the same destination, you should subtract the fees", this); } } } if (subtractFeesOutputsCount.Count > 1) { foreach (var subtractFeesOutput in subtractFeesOutputsCount) { vm.AddModelError(model => model.Outputs[subtractFeesOutput].SubtractFeesFromOutput, "You can only subtract fees from one output", this); } } else if (vm.CurrentBalance == transactionAmountSum && !substractFees) { ModelState.AddModelError(string.Empty, "You are sending your entire balance, you should subtract the fees from an output"); } if (vm.CurrentBalance < transactionAmountSum) { for (var i = 0; i < vm.Outputs.Count; i++) { vm.AddModelError(model => model.Outputs[i].Amount, "You are sending more than what you own", this); } } if (!ModelState.IsValid) { return(View(vm)); } DerivationSchemeSettings derivationScheme = GetDerivationSchemeSettings(walletId); CreatePSBTResponse psbt = null; try { psbt = await CreatePSBT(network, derivationScheme, vm, cancellation); } catch (NBXplorerException ex) { ModelState.AddModelError(string.Empty, ex.Error.Message); return(View(vm)); } catch (NotSupportedException) { ModelState.AddModelError(string.Empty, "You need to update your version of NBXplorer"); return(View(vm)); } derivationScheme.RebaseKeyPaths(psbt.PSBT); switch (command) { case "vault": return(ViewVault(walletId, psbt.PSBT)); case "ledger": return(ViewWalletSendLedger(psbt.PSBT, psbt.ChangeAddress)); case "seed": return(SignWithSeed(walletId, psbt.PSBT.ToBase64())); case "analyze-psbt": var name = $"Send-{string.Join('_', vm.Outputs.Select(output => $"{output.Amount}->{output.DestinationAddress}{(output.SubtractFeesFromOutput ? "-Fees" : string.Empty)}"))}.psbt"; return(RedirectToWalletPSBT(walletId, psbt.PSBT, name)); default: return(View(vm)); } }
public async Task <IActionResult> WalletSend( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, WalletSendModel vm, string command = null, CancellationToken cancellation = default) { if (walletId?.StoreId == null) { return(NotFound()); } var store = await Repository.FindStore(walletId.StoreId, GetUserId()); if (store == null) { return(NotFound()); } var network = this.NetworkProvider.GetNetwork(walletId?.CryptoCode); if (network == null) { return(NotFound()); } vm.SupportRBF = network.SupportRBF; var destination = ParseDestination(vm.Destination, network.NBitcoinNetwork); if (destination == null) { ModelState.AddModelError(nameof(vm.Destination), "Invalid address"); } if (vm.Amount.HasValue) { if (vm.CurrentBalance == vm.Amount.Value && !vm.SubstractFees) { ModelState.AddModelError(nameof(vm.Amount), "You are sending all your balance to the same destination, you should substract the fees"); } if (vm.CurrentBalance < vm.Amount.Value) { ModelState.AddModelError(nameof(vm.Amount), "You are sending more than what you own"); } } if (!ModelState.IsValid) { return(View(vm)); } DerivationSchemeSettings derivationScheme = await GetDerivationSchemeSettings(walletId); CreatePSBTResponse psbt = null; try { psbt = await CreatePSBT(network, derivationScheme, vm, cancellation); } catch (NBXplorerException ex) { ModelState.AddModelError(nameof(vm.Amount), ex.Error.Message); return(View(vm)); } catch (NotSupportedException) { ModelState.AddModelError(nameof(vm.Destination), "You need to update your version of NBXplorer"); return(View(vm)); } derivationScheme.RebaseKeyPaths(psbt.PSBT); if (command == "ledger") { return(ViewWalletSendLedger(psbt.PSBT, psbt.ChangeAddress)); } else if (command == "analyze-psbt") { return(ViewPSBT(psbt.PSBT, $"Send-{vm.Amount.Value}-{network.CryptoCode}-to-{destination[0].ToString()}.psbt")); } return(View(vm)); }
public async Task <IActionResult> WalletSend( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, WalletSendModel vm, string command = "", CancellationToken cancellation = default) { if (walletId?.StoreId == null) { return(NotFound()); } var store = await Repository.FindStore(walletId.StoreId, GetUserId()); if (store == null) { return(NotFound()); } var network = this.NetworkProvider.GetNetwork(walletId?.CryptoCode); if (network == null) { return(NotFound()); } vm.SupportRBF = network.SupportRBF; decimal transactionAmountSum = 0; if (command == "add-output") { ModelState.Clear(); vm.Outputs.Add(new WalletSendModel.TransactionOutput()); return(View(vm)); } if (command.StartsWith("remove-output", StringComparison.InvariantCultureIgnoreCase)) { ModelState.Clear(); var index = int.Parse(command.Substring(command.IndexOf(":", StringComparison.InvariantCultureIgnoreCase) + 1), CultureInfo.InvariantCulture); vm.Outputs.RemoveAt(index); return(View(vm)); } if (!vm.Outputs.Any()) { ModelState.AddModelError(string.Empty, "Por favor agregue al menos una salida de transacción"); return(View(vm)); } var subtractFeesOutputsCount = new List <int>(); var substractFees = vm.Outputs.Any(o => o.SubtractFeesFromOutput); for (var i = 0; i < vm.Outputs.Count; i++) { var transactionOutput = vm.Outputs[i]; if (transactionOutput.SubtractFeesFromOutput) { subtractFeesOutputsCount.Add(i); } var destination = ParseDestination(transactionOutput.DestinationAddress, network.NBitcoinNetwork); if (destination == null) { ModelState.AddModelError(nameof(transactionOutput.DestinationAddress), "Dirección inválida"); } if (transactionOutput.Amount.HasValue) { transactionAmountSum += transactionOutput.Amount.Value; if (vm.CurrentBalance == transactionOutput.Amount.Value && !transactionOutput.SubtractFeesFromOutput) { vm.AddModelError(model => model.Outputs[i].SubtractFeesFromOutput, "Está enviando todo su saldo al mismo destino, debe restar las tarifas", ModelState); } } } if (subtractFeesOutputsCount.Count > 1) { foreach (var subtractFeesOutput in subtractFeesOutputsCount) { vm.AddModelError(model => model.Outputs[subtractFeesOutput].SubtractFeesFromOutput, "Solo puedes restar tarifas de una salida", ModelState); } } else if (vm.CurrentBalance == transactionAmountSum && !substractFees) { ModelState.AddModelError(string.Empty, "Está enviando todo su saldo, debe restar las tarifas de una salida"); } if (vm.CurrentBalance < transactionAmountSum) { for (var i = 0; i < vm.Outputs.Count; i++) { vm.AddModelError(model => model.Outputs[i].Amount, "Estás enviando más de lo que tienes", ModelState); } } if (!ModelState.IsValid) { return(View(vm)); } DerivationSchemeSettings derivationScheme = await GetDerivationSchemeSettings(walletId); CreatePSBTResponse psbt = null; try { psbt = await CreatePSBT(network, derivationScheme, vm, cancellation); } catch (NBXplorerException ex) { ModelState.AddModelError(string.Empty, ex.Error.Message); return(View(vm)); } catch (NotSupportedException) { ModelState.AddModelError(string.Empty, "Necesitas actualizar tu versión de NBXplorer"); return(View(vm)); } derivationScheme.RebaseKeyPaths(psbt.PSBT); switch (command) { case "ledger": return(ViewWalletSendLedger(psbt.PSBT, psbt.ChangeAddress)); case "seed": return(SignWithSeed(walletId, psbt.PSBT.ToBase64())); case "analyze-psbt": var name = $"Send-{string.Join('_', vm.Outputs.Select(output => $"{output.Amount}->{output.DestinationAddress}{(output.SubtractFeesFromOutput ? "-Fees" : string.Empty)}"))}.psbt"; return(RedirectToAction(nameof(WalletPSBT), new { walletId = walletId, psbt = psbt.PSBT.ToBase64(), FileName = name })); default: return(View(vm)); } }