btcpayserver/BTCPayServer/Controllers/UIWalletsController.PSBT.cs

578 lines
28 KiB
C#
Raw Normal View History

2020-06-29 04:44:35 +02:00
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
2021-12-31 08:59:02 +01:00
using BTCPayServer.BIP78.Sender;
2020-04-29 09:09:16 +02:00
using BTCPayServer.HostedServices;
using BTCPayServer.ModelBinders;
2020-01-06 13:57:32 +01:00
using BTCPayServer.Models;
using BTCPayServer.Models.WalletViewModels;
using BTCPayServer.Payments.PayJoin.Sender;
using BTCPayServer.Services;
using Microsoft.AspNetCore.Mvc;
using NBitcoin;
2020-06-17 14:43:56 +02:00
using NBitcoin.Payment;
2020-01-21 09:33:12 +01:00
using NBXplorer;
using NBXplorer.Models;
namespace BTCPayServer.Controllers
{
2022-01-07 04:32:00 +01:00
public partial class UIWalletsController
{
[NonAction]
public async Task<CreatePSBTResponse> CreatePSBT(BTCPayNetwork network, DerivationSchemeSettings derivationSettings, WalletSendModel sendModel, CancellationToken cancellationToken)
{
var nbx = ExplorerClientProvider.GetExplorerClient(network);
CreatePSBTRequest psbtRequest = new CreatePSBTRequest();
if (sendModel.InputSelection)
{
2020-06-28 10:55:27 +02:00
psbtRequest.IncludeOnlyOutpoints = sendModel.SelectedInputs?.Select(OutPoint.Parse)?.ToList() ?? new List<OutPoint>();
}
foreach (var transactionOutput in sendModel.Outputs)
{
var psbtDestination = new CreatePSBTDestination();
psbtRequest.Destinations.Add(psbtDestination);
psbtDestination.Destination = BitcoinAddress.Create(transactionOutput.DestinationAddress, network.NBitcoinNetwork);
psbtDestination.Amount = Money.Coins(transactionOutput.Amount.Value);
psbtDestination.SubstractFees = transactionOutput.SubtractFeesFromOutput;
}
2020-06-28 10:55:27 +02:00
if (network.SupportRBF)
{
if (sendModel.AllowFeeBump is WalletSendModel.ThreeStateBool.Yes)
psbtRequest.RBF = true;
if (sendModel.AllowFeeBump is WalletSendModel.ThreeStateBool.No)
psbtRequest.RBF = false;
}
psbtRequest.AlwaysIncludeNonWitnessUTXO = sendModel.AlwaysIncludeNonWitnessUTXO;
2020-06-28 10:55:27 +02:00
psbtRequest.FeePreference = new FeePreference();
if (sendModel.FeeSatoshiPerByte is decimal v &&
v > decimal.Zero)
{
psbtRequest.FeePreference.ExplicitFeeRate = new FeeRate(v);
}
if (sendModel.NoChange)
{
psbtRequest.ExplicitChangeAddress = psbtRequest.Destinations.First().Destination;
}
2020-06-28 10:55:27 +02:00
var psbt = (await nbx.CreatePSBTAsync(derivationSettings.AccountDerivation, psbtRequest, cancellationToken));
if (psbt == null)
throw new NotSupportedException("You need to update your version of NBXplorer");
2019-07-25 12:38:29 +02:00
// Not supported by coldcard, remove when they do support it
psbt.PSBT.GlobalXPubs.Clear();
return psbt;
}
[HttpPost("{walletId}/cpfp")]
public async Task<IActionResult> WalletCPFP([ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId, string[] outpoints, string[] transactionHashes, string returnUrl)
{
outpoints ??= Array.Empty<string>();
transactionHashes ??= Array.Empty<string>();
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
var explorer = ExplorerClientProvider.GetExplorerClient(network);
var fr = _feeRateProvider.CreateFeeProvider(network);
var targetFeeRate = await fr.GetFeeRateAsync(1);
// Since we don't know the actual fee rate paid by a tx from NBX
// we just assume that it is 20 blocks
var assumedFeeRate = await fr.GetFeeRateAsync(20);
var settings = (this.GetCurrentStore().GetDerivationSchemeSettings(NetworkProvider, network.CryptoCode));
var derivationScheme = settings.AccountDerivation;
if (derivationScheme is null)
return NotFound();
var utxos = await explorer.GetUTXOsAsync(derivationScheme);
var outpointsHashet = outpoints.ToHashSet();
var transactionHashesSet = transactionHashes.ToHashSet();
var bumpableUTXOs = utxos.GetUnspentUTXOs().Where(u => u.Confirmations == 0 &&
(outpointsHashet.Contains(u.Outpoint.ToString()) ||
transactionHashesSet.Contains(u.Outpoint.Hash.ToString()))).ToArray();
if (bumpableUTXOs.Length == 0)
{
TempData[WellKnownTempData.ErrorMessage] = "There isn't any UTXO available to bump fee";
2022-05-13 03:26:20 +02:00
return LocalRedirect(returnUrl);
}
Money bumpFee = Money.Zero;
foreach (var txid in bumpableUTXOs.Select(u => u.TransactionHash).ToHashSet())
{
var tx = await explorer.GetTransactionAsync(txid);
var vsize = tx.Transaction.GetVirtualSize();
var assumedFeePaid = assumedFeeRate.GetFee(vsize);
var expectedFeePaid = targetFeeRate.GetFee(vsize);
bumpFee += Money.Max(Money.Zero, expectedFeePaid - assumedFeePaid);
}
var returnAddress = (await explorer.GetUnusedAsync(derivationScheme, NBXplorer.DerivationStrategy.DerivationFeature.Deposit)).Address;
TransactionBuilder builder = explorer.Network.NBitcoinNetwork.CreateTransactionBuilder();
builder.AddCoins(bumpableUTXOs.Select(utxo => utxo.AsCoin(derivationScheme)));
// The fee of the bumped transaction should pay for both, the fee
// of the bump transaction and those that are being bumped
builder.SendEstimatedFees(targetFeeRate);
builder.SendFees(bumpFee);
builder.SendAll(returnAddress);
try {
var psbt = builder.BuildPSBT(false);
psbt = (await explorer.UpdatePSBTAsync(new UpdatePSBTRequest()
{
PSBT = psbt,
DerivationScheme = derivationScheme
})).PSBT;
return View("PostRedirect", new PostRedirectViewModel
{
AspController = "UIWallets",
AspAction = nameof(UIWalletsController.WalletSign),
RouteParameters = {
{ "walletId", walletId.ToString() },
{ "returnUrl", returnUrl }
},
FormParameters =
{
{ "walletId", walletId.ToString() },
{ "psbt", psbt.ToHex() }
}
});
} catch (Exception ex) {
TempData[WellKnownTempData.ErrorMessage] = ex.Message;
2022-05-13 03:26:20 +02:00
return LocalRedirect(returnUrl);
}
}
[HttpPost("{walletId}/sign")]
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 });
2022-02-17 09:58:56 +01:00
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);
2022-02-17 09:58:56 +01:00
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)
{
2022-02-17 09:58:56 +01:00
return await SignWithSeed(walletId,
new SignWithSeedViewModel { SeedOrKey = extKey, SigningContext = vm.SigningContext });
}
}
}
return View("WalletSigningOptions", new WalletSigningOptionsModel(vm.SigningContext, returnUrl));
}
[HttpGet("{walletId}/psbt")]
public async Task<IActionResult> WalletPSBT([ModelBinder(typeof(WalletIdModelBinder))]
2022-02-17 09:58:56 +01:00
WalletId walletId)
{
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
2022-02-17 09:58:56 +01:00
var vm = new WalletPSBTViewModel();
vm.CryptoCode = network.CryptoCode;
var derivationSchemeSettings = GetDerivationSchemeSettings(walletId);
if (derivationSchemeSettings == null)
return NotFound();
2021-08-03 12:43:16 +02:00
vm.NBXSeedAvailable = await CanUseHotWallet() && derivationSchemeSettings.IsHotWallet;
return View(vm);
}
[HttpPost("{walletId}/psbt")]
2019-05-12 06:13:52 +02:00
public async Task<IActionResult> WalletPSBT(
[ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId,
2022-02-17 09:58:56 +01:00
WalletPSBTViewModel vm, string command)
{
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
vm.CryptoCode = network.CryptoCode;
var derivationSchemeSettings = GetDerivationSchemeSettings(walletId);
if (derivationSchemeSettings == null)
return NotFound();
2021-08-03 12:43:16 +02:00
vm.NBXSeedAvailable = await CanUseHotWallet() && derivationSchemeSettings.IsHotWallet;
var psbt = await vm.GetPSBT(network.NBitcoinNetwork);
2022-02-17 09:58:56 +01:00
if (vm.InvalidPSBT)
2019-05-12 06:13:52 +02:00
{
ModelState.AddModelError(nameof(vm.PSBT), "Invalid PSBT");
return View(vm);
}
2022-02-17 09:58:56 +01:00
if (psbt is null)
{
return View("WalletPSBT", vm);
}
switch (command)
{
case "sign":
2022-02-17 09:58:56 +01:00
return await WalletSign(walletId, vm);
case "decode":
ModelState.Remove(nameof(vm.PSBT));
ModelState.Remove(nameof(vm.FileName));
ModelState.Remove(nameof(vm.UploadedPSBTFile));
2021-07-27 17:01:00 +02:00
await FetchTransactionDetails(derivationSchemeSettings, vm, network);
return View("WalletPSBTDecoded", vm);
2021-12-31 08:59:02 +01:00
2021-08-03 12:43:16 +02:00
case "save-psbt":
return FilePSBT(psbt, vm.FileName);
case "update":
2020-03-29 17:28:22 +02:00
psbt = await ExplorerClientProvider.UpdatePSBT(derivationSchemeSettings, psbt);
if (psbt == null)
{
2021-08-03 12:43:16 +02:00
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
});
2021-12-31 08:59:02 +01:00
2021-08-03 12:43:16 +02:00
case "combine":
ModelState.Remove(nameof(vm.PSBT));
return View(nameof(WalletPSBTCombine), new WalletPSBTCombineViewModel { OtherPSBT = psbt.ToBase64() });
case "broadcast":
{
return RedirectToWalletPSBTReady(new WalletPSBTReadyViewModel
2020-06-28 10:55:27 +02:00
{
SigningContext = new SigningContextModel(psbt)
});
}
2021-12-31 08:59:02 +01:00
default:
2022-02-17 09:58:56 +01:00
return View("WalletPSBTDecoded", vm);
}
}
2020-06-17 14:43:56 +02:00
private async Task<PSBT> GetPayjoinProposedTX(BitcoinUrlBuilder bip21, PSBT psbt, DerivationSchemeSettings derivationSchemeSettings, BTCPayNetwork btcPayNetwork, CancellationToken cancellationToken)
2020-01-06 13:57:32 +01:00
{
var cloned = psbt.Clone();
cloned = cloned.Finalize();
2020-04-28 17:23:51 +02:00
await _broadcaster.Schedule(DateTimeOffset.UtcNow + TimeSpan.FromMinutes(2.0), cloned.ExtractTransaction(), btcPayNetwork);
using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(TimeSpan.FromSeconds(30));
return await _payjoinClient.RequestPayjoin(bip21, new PayjoinWallet(derivationSchemeSettings), psbt, cts.Token);
2020-01-06 13:57:32 +01:00
}
2020-06-28 10:55:27 +02:00
private async Task FetchTransactionDetails(DerivationSchemeSettings derivationSchemeSettings, WalletPSBTReadyViewModel vm, BTCPayNetwork network)
{
var psbtObject = PSBT.Parse(vm.SigningContext.PSBT, network.NBitcoinNetwork);
if (!psbtObject.IsAllFinalized())
2020-03-29 17:28:22 +02:00
psbtObject = await ExplorerClientProvider.UpdatePSBT(derivationSchemeSettings, psbtObject) ?? psbtObject;
IHDKey signingKey = null;
RootedKeyPath signingKeyPath = null;
try
{
signingKey = new BitcoinExtPubKey(vm.SigningKey, network.NBitcoinNetwork);
}
catch { }
try
{
signingKey = signingKey ?? new BitcoinExtKey(vm.SigningKey, network.NBitcoinNetwork);
}
catch { }
try
{
signingKeyPath = RootedKeyPath.Parse(vm.SigningKeyPath);
}
catch { }
if (signingKey == null || signingKeyPath == null)
{
var signingKeySettings = derivationSchemeSettings.GetSigningAccountKeySettings();
if (signingKey == null)
{
signingKey = signingKeySettings.AccountKey;
vm.SigningKey = signingKey.ToString();
}
if (vm.SigningKeyPath == null)
{
signingKeyPath = signingKeySettings.GetRootedKeyPath();
vm.SigningKeyPath = signingKeyPath?.ToString();
}
}
if (psbtObject.IsAllFinalized())
{
vm.CanCalculateBalance = false;
}
else
{
var balanceChange = psbtObject.GetBalance(derivationSchemeSettings.AccountDerivation, signingKey, signingKeyPath);
vm.BalanceChange = ValueToString(balanceChange, network);
vm.CanCalculateBalance = true;
vm.Positive = balanceChange >= Money.Zero;
}
vm.Inputs = new List<WalletPSBTReadyViewModel.InputViewModel>();
foreach (var input in psbtObject.Inputs)
{
var inputVm = new WalletPSBTReadyViewModel.InputViewModel();
vm.Inputs.Add(inputVm);
var mine = input.HDKeysFor(derivationSchemeSettings.AccountDerivation, signingKey, signingKeyPath).Any();
var balanceChange2 = input.GetTxOut()?.Value ?? Money.Zero;
if (mine)
balanceChange2 = -balanceChange2;
inputVm.BalanceChange = ValueToString(balanceChange2, network);
inputVm.Positive = balanceChange2 >= Money.Zero;
inputVm.Index = (int)input.Index;
}
vm.Destinations = new List<WalletPSBTReadyViewModel.DestinationViewModel>();
foreach (var output in psbtObject.Outputs)
{
var dest = new WalletPSBTReadyViewModel.DestinationViewModel();
vm.Destinations.Add(dest);
var mine = output.HDKeysFor(derivationSchemeSettings.AccountDerivation, signingKey, signingKeyPath).Any();
var balanceChange2 = output.Value;
if (!mine)
balanceChange2 = -balanceChange2;
dest.Balance = ValueToString(balanceChange2, network);
dest.Positive = balanceChange2 >= Money.Zero;
dest.Destination = output.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork)?.ToString() ?? output.ScriptPubKey.ToString();
}
if (psbtObject.TryGetFee(out var fee))
{
2021-09-01 17:31:42 +02:00
vm.Destinations.Add(new WalletPSBTReadyViewModel.DestinationViewModel
{
Positive = false,
2020-06-28 10:55:27 +02:00
Balance = ValueToString(-fee, network),
Destination = "Mining fees"
});
}
if (psbtObject.TryGetEstimatedFeeRate(out var feeRate))
{
vm.FeeRate = feeRate.ToString();
}
var sanityErrors = psbtObject.CheckSanity();
if (sanityErrors.Count != 0)
{
vm.SetErrors(sanityErrors);
}
else if (!psbtObject.IsAllFinalized() && !psbtObject.TryFinalize(out var errors))
{
vm.SetErrors(errors);
}
}
[HttpPost("{walletId}/psbt/ready")]
public async Task<IActionResult> WalletPSBTReady(
[ModelBinder(typeof(WalletIdModelBinder))]
2022-02-17 09:58:56 +01:00
WalletId walletId, WalletPSBTViewModel vm, string command, CancellationToken cancellationToken = default)
{
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
2022-02-17 09:58:56 +01:00
PSBT psbt = await vm.GetPSBT(network.NBitcoinNetwork);
if (vm.InvalidPSBT || psbt is null)
{
2022-02-17 09:58:56 +01:00
if (vm.InvalidPSBT)
vm.Errors.Add("Invalid PSBT");
return View(nameof(WalletPSBT), vm);
}
2022-02-17 09:58:56 +01:00
DerivationSchemeSettings derivationSchemeSettings = GetDerivationSchemeSettings(walletId);
if (derivationSchemeSettings == null)
return NotFound();
await FetchTransactionDetails(derivationSchemeSettings, vm, network);
2020-06-28 10:55:27 +02:00
switch (command)
{
case "payjoin":
2021-09-01 17:31:42 +02:00
string error;
try
{
2020-06-17 14:43:56 +02:00
var proposedPayjoin = await GetPayjoinProposedTX(new BitcoinUrlBuilder(vm.SigningContext.PayJoinBIP21, network.NBitcoinNetwork), psbt,
derivationSchemeSettings, network, cancellationToken);
try
{
2021-09-01 17:31:42 +02:00
proposedPayjoin.Settings.SigningOptions = new SigningOptions
{
EnforceLowR = !(vm.SigningContext?.EnforceLowR is false)
};
var extKey = ExtKey.Parse(vm.SigningKey, network.NBitcoinNetwork);
2020-03-29 17:28:22 +02:00
proposedPayjoin = proposedPayjoin.SignAll(derivationSchemeSettings.AccountDerivation,
extKey,
RootedKeyPath.Parse(vm.SigningKeyPath));
vm.SigningContext.PSBT = proposedPayjoin.ToBase64();
vm.SigningContext.OriginalPSBT = psbt.ToBase64();
proposedPayjoin.Finalize();
2020-04-29 09:09:16 +02:00
var hash = proposedPayjoin.ExtractTransaction().GetHash();
2020-06-01 06:10:21 +02:00
_EventAggregator.Publish(new UpdateTransactionLabel(walletId, hash, UpdateTransactionLabel.PayjoinLabelTemplate()));
2021-09-01 17:31:42 +02:00
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");
}
2020-03-29 17:28:22 +02:00
catch (Exception)
{
TempData.SetStatusMessageModel(new StatusMessageModel()
{
Severity = StatusMessageModel.StatusSeverity.Warning,
AllowDismiss = false,
Html =
2021-09-01 17:31:42 +02:00
"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}";
}
2020-06-28 10:55:27 +02:00
//we possibly exposed the tx to the receiver, so we need to broadcast straight away
psbt.Finalize();
2021-09-01 17:31:42 +02:00
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":
{
2020-06-28 10:55:27 +02:00
var transaction = psbt.ExtractTransaction();
try
{
2020-06-28 10:55:27 +02:00
var broadcastResult = await ExplorerClientProvider.GetExplorerClient(network).BroadcastAsync(transaction);
if (!broadcastResult.Success)
{
2020-06-28 10:55:27 +02:00
if (!string.IsNullOrEmpty(vm.SigningContext.OriginalPSBT))
{
2021-09-01 17:31:42 +02:00
TempData.SetStatusMessageModel(new StatusMessageModel
2020-06-28 10:55:27 +02:00
{
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.Errors.Add($"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);
}
}
2020-06-28 10:55:27 +02:00
catch (Exception ex)
{
vm.Errors.Add("Error while broadcasting: " + ex.Message);
return View(nameof(WalletPSBT), vm);
2020-06-28 10:55:27 +02:00
}
2020-06-28 10:55:27 +02:00
if (!TempData.HasStatusMessage())
{
TempData[WellKnownTempData.SuccessMessage] = $"Transaction broadcasted successfully ({transaction.GetHash()})";
}
var returnUrl = this.HttpContext.Request.Query["returnUrl"].FirstOrDefault();
if (returnUrl is not null)
{
2022-05-13 03:26:20 +02:00
return LocalRedirect(returnUrl);
}
return RedirectToAction(nameof(WalletTransactions), new { walletId = walletId.ToString() });
}
case "analyze-psbt":
return RedirectToWalletPSBT(new WalletPSBTViewModel()
{
PSBT = psbt.ToBase64()
});
2022-02-17 09:58:56 +01:00
case "decode":
await FetchTransactionDetails(derivationSchemeSettings, vm, network);
return View("WalletPSBTDecoded", vm);
default:
vm.Errors.Add("Unknown command");
return View(nameof(WalletPSBT), vm);
}
}
private IActionResult FilePSBT(PSBT psbt, string fileName)
{
return File(psbt.ToBytes(), "application/octet-stream", fileName);
}
2019-05-12 06:13:52 +02:00
[HttpPost("{walletId}/psbt/combine")]
2019-05-12 06:13:52 +02:00
public async Task<IActionResult> WalletPSBTCombine([ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId, WalletPSBTCombineViewModel vm)
{
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
2019-05-12 06:13:52 +02:00
var psbt = await vm.GetPSBT(network.NBitcoinNetwork);
if (psbt == null)
{
ModelState.AddModelError(nameof(vm.PSBT), "Invalid PSBT");
return View(vm);
}
var sourcePSBT = vm.GetSourcePSBT(network.NBitcoinNetwork);
if (sourcePSBT == null)
{
ModelState.AddModelError(nameof(vm.OtherPSBT), "Invalid PSBT");
return View(vm);
}
sourcePSBT = sourcePSBT.Combine(psbt);
TempData[WellKnownTempData.SuccessMessage] = "PSBT Successfully combined!";
return RedirectToWalletPSBT(new WalletPSBTViewModel()
{
PSBT = sourcePSBT.ToBase64()
});
2019-05-12 06:13:52 +02:00
}
}
}