mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-22 14:22:40 +01:00
Merge pull request #1597 from NicolasDorier/refactor/wallet-param
Refactor parameter passing in wallet functions
This commit is contained in:
commit
6e0c090622
20 changed files with 247 additions and 165 deletions
|
@ -5,7 +5,7 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NBitcoin" Version="5.0.35" />
|
||||
<PackageReference Include="NBitcoin" Version="5.0.39" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -4,6 +4,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="3.0.11" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="3.0.15" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
@ -67,8 +67,8 @@ namespace BTCPayServer.Tests
|
|||
CurrentBalance = 1.5m
|
||||
};
|
||||
var vmLedger = await walletController.WalletSend(walletId, sendModel, command: "ledger").AssertViewModelAsync<WalletSendLedgerModel>();
|
||||
PSBT.Parse(vmLedger.PSBT, user.SupportedNetwork.NBitcoinNetwork);
|
||||
BitcoinAddress.Create(vmLedger.HintChange, user.SupportedNetwork.NBitcoinNetwork);
|
||||
PSBT.Parse(vmLedger.SigningContext.PSBT, user.SupportedNetwork.NBitcoinNetwork);
|
||||
BitcoinAddress.Create(vmLedger.SigningContext.ChangeAddress, user.SupportedNetwork.NBitcoinNetwork);
|
||||
Assert.NotNull(vmLedger.WebsocketPath);
|
||||
|
||||
string redirectedPSBT = AssertRedirectedPSBT(await walletController.WalletSend(walletId, sendModel, command: "analyze-psbt"), nameof(walletController.WalletPSBT));
|
||||
|
@ -82,17 +82,23 @@ namespace BTCPayServer.Tests
|
|||
await walletController.WalletPSBT(walletId, vmPSBT, "ledger").AssertViewModelAsync<WalletSendLedgerModel>();
|
||||
var vmPSBT2 = await walletController.WalletPSBTReady(walletId, new WalletPSBTReadyViewModel()
|
||||
{
|
||||
PSBT = AssertRedirectedPSBT( await walletController.WalletPSBT(walletId, vmPSBT, "broadcast"), nameof(walletController.WalletPSBTReady))
|
||||
SigningContext = new SigningContextModel()
|
||||
{
|
||||
PSBT = AssertRedirectedPSBT( await walletController.WalletPSBT(walletId, vmPSBT, "broadcast"), nameof(walletController.WalletPSBTReady))
|
||||
}
|
||||
} ).AssertViewModelAsync<WalletPSBTReadyViewModel>();
|
||||
Assert.NotEmpty(vmPSBT2.Inputs.Where(i => i.Error != null));
|
||||
Assert.Equal(vmPSBT.PSBT, vmPSBT2.PSBT);
|
||||
Assert.Equal(vmPSBT.PSBT, vmPSBT2.SigningContext.PSBT);
|
||||
|
||||
var signedPSBT = unsignedPSBT.Clone();
|
||||
signedPSBT.SignAll(user.DerivationScheme, user.GenerateWalletResponseV.AccountHDKey, user.GenerateWalletResponseV.AccountKeyPath);
|
||||
vmPSBT.PSBT = signedPSBT.ToBase64();
|
||||
var psbtReady = await walletController.WalletPSBTReady(walletId, new WalletPSBTReadyViewModel()
|
||||
{
|
||||
PSBT = AssertRedirectedPSBT( await walletController.WalletPSBT(walletId, vmPSBT, "broadcast"), nameof(walletController.WalletPSBTReady))
|
||||
SigningContext = new SigningContextModel()
|
||||
{
|
||||
PSBT = AssertRedirectedPSBT(await walletController.WalletPSBT(walletId, vmPSBT, "broadcast"), nameof(walletController.WalletPSBTReady))
|
||||
}
|
||||
} ).AssertViewModelAsync<WalletPSBTReadyViewModel>();
|
||||
Assert.Equal(2 + 1, psbtReady.Destinations.Count); // The fee is a destination
|
||||
Assert.Contains(psbtReady.Destinations, d => d.Destination == sendDestination && !d.Positive);
|
||||
|
@ -120,8 +126,11 @@ namespace BTCPayServer.Tests
|
|||
Assert.True(signedPSBT2.TryFinalize(out _));
|
||||
Assert.Equal(signedPSBT, signedPSBT2);
|
||||
|
||||
var ready = (await walletController.WalletPSBTReady(walletId, signedPSBT.ToBase64())).AssertViewModel<WalletPSBTReadyViewModel>();
|
||||
Assert.Equal(signedPSBT.ToBase64(), ready.PSBT);
|
||||
var ready = (await walletController.WalletPSBTReady(walletId, new WalletPSBTReadyViewModel()
|
||||
{
|
||||
SigningContext = new SigningContextModel(signedPSBT)
|
||||
})).AssertViewModel<WalletPSBTReadyViewModel>();
|
||||
Assert.Equal(signedPSBT.ToBase64(), ready.SigningContext.PSBT);
|
||||
psbt = AssertRedirectedPSBT(await walletController.WalletPSBTReady(walletId, ready, command: "analyze-psbt"), nameof(walletController.WalletPSBT));
|
||||
Assert.Equal(signedPSBT.ToBase64(), psbt);
|
||||
redirect = Assert.IsType<RedirectToActionResult>(await walletController.WalletPSBTReady(walletId, ready, command: "broadcast"));
|
||||
|
@ -134,7 +143,7 @@ namespace BTCPayServer.Tests
|
|||
var postRedirectView = Assert.IsType<ViewResult>(view);
|
||||
var postRedirectViewModel = Assert.IsType<PostRedirectViewModel>(postRedirectView.Model);
|
||||
Assert.Equal(actionName, postRedirectViewModel.AspAction);
|
||||
var redirectedPSBT = postRedirectViewModel.Parameters.Single(p => p.Key == "psbt").Value;
|
||||
var redirectedPSBT = postRedirectViewModel.Parameters.Single(p => p.Key == "psbt" || p.Key == "SigningContext.PSBT").Value;
|
||||
return redirectedPSBT;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -187,6 +187,9 @@
|
|||
<Content Update="Views\Wallets\ListWallets.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletPSBT - Copy.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
<Content Update="Views\Wallets\WalletPSBTCombine.cshtml">
|
||||
<Pack>$(IncludeRazorContentInPack)</Pack>
|
||||
</Content>
|
||||
|
|
|
@ -100,8 +100,7 @@ namespace BTCPayServer.Controllers
|
|||
ModelState.AddModelError(nameof(vm.PSBT), "Invalid PSBT");
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
var res = await TryHandleSigningCommands(walletId, psbt, command, vm.PayJoinEndpointUrl, null);
|
||||
var res = await TryHandleSigningCommands(walletId, psbt, command, new SigningContextModel(psbt));
|
||||
if (res != null)
|
||||
{
|
||||
return res;
|
||||
|
@ -126,11 +125,18 @@ namespace BTCPayServer.Controllers
|
|||
return View(vm);
|
||||
}
|
||||
TempData[WellKnownTempData.SuccessMessage] = "PSBT updated!";
|
||||
return RedirectToWalletPSBT(psbt, vm.FileName);
|
||||
return RedirectToWalletPSBT(new WalletPSBTViewModel()
|
||||
{
|
||||
PSBT = psbt.ToBase64(),
|
||||
FileName = vm.FileName
|
||||
});
|
||||
|
||||
case "broadcast":
|
||||
{
|
||||
return RedirectToWalletPSBTReady(psbt.ToBase64());
|
||||
return RedirectToWalletPSBTReady(new WalletPSBTReadyViewModel()
|
||||
{
|
||||
SigningContext = new SigningContextModel(psbt)
|
||||
});
|
||||
}
|
||||
case "combine":
|
||||
ModelState.Remove(nameof(vm.PSBT));
|
||||
|
@ -156,18 +162,12 @@ namespace BTCPayServer.Controllers
|
|||
[Route("{walletId}/psbt/ready")]
|
||||
public async Task<IActionResult> WalletPSBTReady(
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, string psbt = null,
|
||||
string signingKey = null,
|
||||
string signingKeyPath = null,
|
||||
string originalPsbt = null,
|
||||
string payJoinEndpointUrl = null)
|
||||
WalletId walletId,
|
||||
WalletPSBTReadyViewModel vm)
|
||||
{
|
||||
if (vm is null)
|
||||
return NotFound();
|
||||
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
|
||||
var vm = new WalletPSBTReadyViewModel() { PSBT = psbt };
|
||||
vm.SigningKey = signingKey;
|
||||
vm.SigningKeyPath = signingKeyPath;
|
||||
vm.OriginalPSBT = originalPsbt;
|
||||
vm.PayJoinEndpointUrl = payJoinEndpointUrl;
|
||||
var derivationSchemeSettings = GetDerivationSchemeSettings(walletId);
|
||||
if (derivationSchemeSettings == null)
|
||||
return NotFound();
|
||||
|
@ -181,7 +181,7 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
private async Task FetchTransactionDetails(DerivationSchemeSettings derivationSchemeSettings, WalletPSBTReadyViewModel vm, BTCPayNetwork network)
|
||||
{
|
||||
var psbtObject = PSBT.Parse(vm.PSBT, network.NBitcoinNetwork);
|
||||
var psbtObject = PSBT.Parse(vm.SigningContext.PSBT, network.NBitcoinNetwork);
|
||||
if (!psbtObject.IsAllFinalized())
|
||||
psbtObject = await ExplorerClientProvider.UpdatePSBT(derivationSchemeSettings, psbtObject) ?? psbtObject;
|
||||
IHDKey signingKey = null;
|
||||
|
@ -288,13 +288,13 @@ namespace BTCPayServer.Controllers
|
|||
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);
|
||||
return await WalletPSBTReady(walletId, vm);
|
||||
PSBT psbt = null;
|
||||
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
|
||||
DerivationSchemeSettings derivationSchemeSettings = null;
|
||||
try
|
||||
{
|
||||
psbt = PSBT.Parse(vm.PSBT, network.NBitcoinNetwork);
|
||||
psbt = PSBT.Parse(vm.SigningContext.PSBT, network.NBitcoinNetwork);
|
||||
derivationSchemeSettings = GetDerivationSchemeSettings(walletId);
|
||||
if (derivationSchemeSettings == null)
|
||||
return NotFound();
|
||||
|
@ -312,16 +312,19 @@ namespace BTCPayServer.Controllers
|
|||
string error = null;
|
||||
try
|
||||
{
|
||||
var proposedPayjoin = await GetPayjoinProposedTX(vm.PayJoinEndpointUrl, psbt,
|
||||
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));
|
||||
vm.PSBT = proposedPayjoin.ToBase64();
|
||||
vm.OriginalPSBT = psbt.ToBase64();
|
||||
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()
|
||||
|
@ -357,7 +360,9 @@ namespace BTCPayServer.Controllers
|
|||
$"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"
|
||||
});
|
||||
return ViewVault(walletId, proposedPayjoin, vm.PayJoinEndpointUrl, psbt);
|
||||
vm.SigningContext.PSBT = proposedPayjoin.ToBase64();
|
||||
vm.SigningContext.OriginalPSBT = psbt.ToBase64();
|
||||
return ViewVault(walletId, vm.SigningContext);
|
||||
}
|
||||
}
|
||||
catch (PayjoinReceiverException ex)
|
||||
|
@ -395,7 +400,7 @@ namespace BTCPayServer.Controllers
|
|||
var broadcastResult = await ExplorerClientProvider.GetExplorerClient(network).BroadcastAsync(transaction);
|
||||
if (!broadcastResult.Success)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(vm.OriginalPSBT))
|
||||
if (!string.IsNullOrEmpty(vm.SigningContext.OriginalPSBT))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
|
@ -403,8 +408,8 @@ namespace BTCPayServer.Controllers
|
|||
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;
|
||||
vm.SigningContext.PSBT = vm.SigningContext.OriginalPSBT;
|
||||
vm.SigningContext.OriginalPSBT = null;
|
||||
return await WalletPSBTReady(walletId, vm, "broadcast");
|
||||
}
|
||||
|
||||
|
@ -425,7 +430,10 @@ namespace BTCPayServer.Controllers
|
|||
return RedirectToWalletTransaction(walletId, transaction);
|
||||
}
|
||||
case "analyze-psbt":
|
||||
return RedirectToWalletPSBT(psbt);
|
||||
return RedirectToWalletPSBT(new WalletPSBTViewModel()
|
||||
{
|
||||
PSBT = psbt.ToBase64()
|
||||
});
|
||||
default:
|
||||
vm.GlobalError = "Unknown command";
|
||||
return View(nameof(WalletPSBTReady),vm);
|
||||
|
@ -457,20 +465,24 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
sourcePSBT = sourcePSBT.Combine(psbt);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "PSBT Successfully combined!";
|
||||
return RedirectToWalletPSBT(sourcePSBT);
|
||||
return RedirectToWalletPSBT(new WalletPSBTViewModel()
|
||||
{
|
||||
PSBT = sourcePSBT.ToBase64()
|
||||
});
|
||||
}
|
||||
|
||||
private async Task<IActionResult> TryHandleSigningCommands(WalletId walletId, PSBT psbt, string command,
|
||||
string payjoinEndpointUrl, BitcoinAddress changeAddress)
|
||||
SigningContextModel signingContext)
|
||||
{
|
||||
signingContext.PSBT = psbt.ToBase64();
|
||||
switch (command )
|
||||
{
|
||||
case "vault":
|
||||
return ViewVault(walletId, psbt, payjoinEndpointUrl);
|
||||
return ViewVault(walletId, signingContext);
|
||||
case "ledger":
|
||||
return ViewWalletSendLedger(walletId, psbt, changeAddress);
|
||||
return ViewWalletSendLedger(walletId, signingContext);
|
||||
case "seed":
|
||||
return SignWithSeed(walletId, psbt.ToBase64(), payjoinEndpointUrl);
|
||||
return SignWithSeed(walletId, signingContext);
|
||||
case "nbx-seed":
|
||||
if (await CanUseHotWallet())
|
||||
{
|
||||
|
@ -478,9 +490,8 @@ namespace BTCPayServer.Controllers
|
|||
var extKey = await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode)
|
||||
.GetMetadataAsync<string>(derivationScheme.AccountDerivation,
|
||||
WellknownMetadataKeys.MasterHDKey);
|
||||
|
||||
return SignWithSeed(walletId,
|
||||
new SignWithSeedViewModel() {SeedOrKey = extKey, PSBT = psbt.ToBase64(), PayJoinEndpointUrl = payjoinEndpointUrl});
|
||||
new SignWithSeedViewModel() {SeedOrKey = extKey, SigningContext = signingContext });
|
||||
}
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
|
|
|
@ -53,6 +53,7 @@ namespace BTCPayServer.Controllers
|
|||
private readonly DelayedTransactionBroadcaster _broadcaster;
|
||||
private readonly PayjoinClient _payjoinClient;
|
||||
private readonly LabelFactory _labelFactory;
|
||||
|
||||
public RateFetcher RateFetcher { get; }
|
||||
|
||||
CurrencyNameTable _currencyTable;
|
||||
|
@ -96,7 +97,7 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
|
||||
// Borrowed from https://github.com/ManageIQ/guides/blob/master/labels.md
|
||||
string[] LabelColorScheme = new string[]
|
||||
string[] LabelColorScheme = new string[]
|
||||
{
|
||||
"#fbca04",
|
||||
"#0e8a16",
|
||||
|
@ -116,10 +117,10 @@ namespace BTCPayServer.Controllers
|
|||
// addlabelclick is if the user click on existing label. For some reason, reusing the same name attribute for both
|
||||
// does not work
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, string transactionId,
|
||||
string addlabel = null,
|
||||
WalletId walletId, string transactionId,
|
||||
string addlabel = null,
|
||||
string addlabelclick = null,
|
||||
string addcomment = null,
|
||||
string addcomment = null,
|
||||
string removelabel = null)
|
||||
{
|
||||
addlabel = addlabel ?? addlabelclick;
|
||||
|
@ -134,7 +135,7 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
catch { }
|
||||
/////////
|
||||
|
||||
|
||||
DerivationSchemeSettings paymentMethod = GetDerivationSchemeSettings(walletId);
|
||||
if (paymentMethod == null)
|
||||
return NotFound();
|
||||
|
@ -146,7 +147,7 @@ namespace BTCPayServer.Controllers
|
|||
var walletTransactionsInfo = await walletTransactionsInfoAsync;
|
||||
if (addlabel != null)
|
||||
{
|
||||
addlabel = addlabel.Trim().TrimStart('{').ToLowerInvariant().Replace(',',' ').Truncate(MaxLabelSize);
|
||||
addlabel = addlabel.Trim().TrimStart('{').ToLowerInvariant().Replace(',', ' ').Truncate(MaxLabelSize);
|
||||
var labels = _labelFactory.GetLabels(walletBlobInfo, Request);
|
||||
if (!walletTransactionsInfo.TryGetValue(transactionId, out var walletTransactionInfo))
|
||||
{
|
||||
|
@ -289,7 +290,7 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
if (walletTransactionsInfo.TryGetValue(tx.TransactionId.ToString(), out var transactionInfo))
|
||||
{
|
||||
var labels = _labelFactory.GetLabels(walletBlob, transactionInfo, Request);
|
||||
var labels = _labelFactory.GetLabels(walletBlob, transactionInfo, Request);
|
||||
vm.Labels.AddRange(labels);
|
||||
model.Labels.AddRange(labels);
|
||||
vm.Comment = transactionInfo.Comment;
|
||||
|
@ -358,7 +359,7 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
var address = cachedAddress.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork);
|
||||
ExplorerClientProvider.GetExplorerClient(network)
|
||||
.CancelReservation(cachedAddress.DerivationStrategy, new[] {cachedAddress.KeyPath});
|
||||
.CancelReservation(cachedAddress.DerivationStrategy, new[] { cachedAddress.KeyPath });
|
||||
this.TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
AllowDismiss = true,
|
||||
|
@ -372,7 +373,7 @@ namespace BTCPayServer.Controllers
|
|||
_WalletReceiveStateService.Set(walletId, reserve);
|
||||
break;
|
||||
}
|
||||
return RedirectToAction(nameof(WalletReceive), new {walletId});
|
||||
return RedirectToAction(nameof(WalletReceive), new { walletId });
|
||||
}
|
||||
|
||||
private async Task<bool> CanUseHotWallet()
|
||||
|
@ -383,7 +384,7 @@ namespace BTCPayServer.Controllers
|
|||
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>();
|
||||
return policies?.AllowHotWalletForAll is true;
|
||||
}
|
||||
|
||||
|
||||
[HttpGet]
|
||||
[Route("{walletId}/send")]
|
||||
public async Task<IActionResult> WalletSend(
|
||||
|
@ -404,7 +405,7 @@ namespace BTCPayServer.Controllers
|
|||
rateRules.Spread = 0.0m;
|
||||
var currencyPair = new Rating.CurrencyPair(paymentMethod.PaymentId.CryptoCode, GetCurrencyCode(storeData.DefaultLang) ?? "USD");
|
||||
double.TryParse(defaultAmount, out var amount);
|
||||
var model = new WalletSendModel()
|
||||
var model = new WalletSendModel()
|
||||
{
|
||||
Outputs = new List<WalletSendModel.TransactionOutput>()
|
||||
{
|
||||
|
@ -416,11 +417,11 @@ namespace BTCPayServer.Controllers
|
|||
},
|
||||
CryptoCode = walletId.CryptoCode
|
||||
};
|
||||
if (!string.IsNullOrEmpty(bip21))
|
||||
{
|
||||
LoadFromBIP21(model, bip21, network);
|
||||
}
|
||||
var feeProvider = _feeRateProvider.CreateFeeProvider(network);
|
||||
if (!string.IsNullOrEmpty(bip21))
|
||||
{
|
||||
LoadFromBIP21(model, bip21, network);
|
||||
}
|
||||
var feeProvider = _feeRateProvider.CreateFeeProvider(network);
|
||||
var recommendedFees =
|
||||
new[]
|
||||
{
|
||||
|
@ -432,7 +433,7 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
var result = await feeProvider.GetFeeRateAsync(
|
||||
(int)network.NBitcoinNetwork.Consensus.GetExpectedBlocksFor(time));
|
||||
return new WalletSendModel.FeeRateOption() {Target = time, FeeRate = result.SatoshiPerByte};
|
||||
return new WalletSendModel.FeeRateOption() { Target = time, FeeRate = result.SatoshiPerByte };
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
|
@ -445,7 +446,7 @@ namespace BTCPayServer.Controllers
|
|||
.GetMetadataAsync<string>(GetDerivationSchemeSettings(walletId).AccountDerivation,
|
||||
WellknownMetadataKeys.MasterHDKey));
|
||||
model.CurrentBalance = await balance;
|
||||
|
||||
|
||||
await Task.WhenAll(recommendedFees);
|
||||
model.RecommendedSatoshiPerByte =
|
||||
recommendedFees.Select(tuple => tuple.Result).Where(option => option != null).ToList();
|
||||
|
@ -473,7 +474,7 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
return View(model);
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[Route("{walletId}/send")]
|
||||
|
@ -497,11 +498,11 @@ namespace BTCPayServer.Controllers
|
|||
{
|
||||
LoadFromBIP21(vm, bip21, network);
|
||||
}
|
||||
|
||||
decimal transactionAmountSum = 0;
|
||||
|
||||
decimal transactionAmountSum = 0;
|
||||
if (command == "toggle-input-selection")
|
||||
{
|
||||
vm.InputSelection = !vm.InputSelection;
|
||||
vm.InputSelection = !vm.InputSelection;
|
||||
}
|
||||
if (vm.InputSelection)
|
||||
{
|
||||
|
@ -509,7 +510,7 @@ namespace BTCPayServer.Controllers
|
|||
var walletBlobAsync = await WalletRepository.GetWalletInfo(walletId);
|
||||
var walletTransactionsInfoAsync = await WalletRepository.GetWalletTransactionsInfo(walletId);
|
||||
|
||||
var utxos = await _walletProvider.GetWallet(network).GetUnspentCoins(schemeSettings.AccountDerivation, cancellation);
|
||||
var utxos = await _walletProvider.GetWallet(network).GetUnspentCoins(schemeSettings.AccountDerivation, cancellation);
|
||||
vm.InputsAvailable = utxos.Select(coin =>
|
||||
{
|
||||
walletTransactionsInfoAsync.TryGetValue(coin.OutPoint.Hash.ToString(), out var info);
|
||||
|
@ -518,7 +519,7 @@ namespace BTCPayServer.Controllers
|
|||
Outpoint = coin.OutPoint.ToString(),
|
||||
Amount = coin.Value.GetValue(network),
|
||||
Comment = info?.Comment,
|
||||
Labels = info == null? null : _labelFactory.GetLabels(walletBlobAsync, info, Request),
|
||||
Labels = info == null ? null : _labelFactory.GetLabels(walletBlobAsync, info, Request),
|
||||
Link = string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink, coin.OutPoint.Hash.ToString())
|
||||
};
|
||||
}).ToArray();
|
||||
|
@ -543,7 +544,7 @@ namespace BTCPayServer.Controllers
|
|||
if (command.StartsWith("remove-output", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
ModelState.Clear();
|
||||
var index = int.Parse(command.Substring(command.IndexOf(":",StringComparison.InvariantCultureIgnoreCase) + 1), CultureInfo.InvariantCulture);
|
||||
var index = int.Parse(command.Substring(command.IndexOf(":", StringComparison.InvariantCultureIgnoreCase) + 1), CultureInfo.InvariantCulture);
|
||||
vm.Outputs.RemoveAt(index);
|
||||
return View(vm);
|
||||
}
|
||||
|
@ -568,12 +569,12 @@ namespace BTCPayServer.Controllers
|
|||
|
||||
try
|
||||
{
|
||||
BitcoinAddress.Create(transactionOutput.DestinationAddress, network.NBitcoinNetwork);
|
||||
BitcoinAddress.Create(transactionOutput.DestinationAddress, network.NBitcoinNetwork);
|
||||
}
|
||||
catch
|
||||
{
|
||||
var inputName =
|
||||
string.Format(CultureInfo.InvariantCulture, "Outputs[{0}].", i.ToString(CultureInfo.InvariantCulture)) +
|
||||
var inputName =
|
||||
string.Format(CultureInfo.InvariantCulture, "Outputs[{0}].", i.ToString(CultureInfo.InvariantCulture)) +
|
||||
nameof(transactionOutput.DestinationAddress);
|
||||
|
||||
ModelState.AddModelError(inputName, "Invalid address");
|
||||
|
@ -598,7 +599,8 @@ namespace BTCPayServer.Controllers
|
|||
vm.AddModelError(model => model.Outputs[subtractFeesOutput].SubtractFeesFromOutput,
|
||||
"You can only subtract fees from one output", this);
|
||||
}
|
||||
}else if (vm.CurrentBalance == transactionAmountSum && !substractFees)
|
||||
}
|
||||
else if (vm.CurrentBalance == transactionAmountSum && !substractFees)
|
||||
{
|
||||
ModelState.AddModelError(string.Empty,
|
||||
"You are sending your entire balance, you should subtract the fees from an output");
|
||||
|
@ -632,7 +634,7 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
if (!ModelState.IsValid)
|
||||
return View(vm);
|
||||
|
||||
DerivationSchemeSettings derivationScheme = GetDerivationSchemeSettings(walletId);
|
||||
|
@ -653,24 +655,34 @@ namespace BTCPayServer.Controllers
|
|||
return View(vm);
|
||||
}
|
||||
derivationScheme.RebaseKeyPaths(psbt.PSBT);
|
||||
|
||||
|
||||
var res = await TryHandleSigningCommands(walletId, psbt.PSBT, command, vm.PayJoinEndpointUrl, psbt.ChangeAddress);
|
||||
|
||||
var signingContext = new SigningContextModel()
|
||||
{
|
||||
PayJoinEndpointUrl = vm.PayJoinEndpointUrl,
|
||||
EnforceLowR = psbt.Suggestions?.ShouldEnforceLowR,
|
||||
ChangeAddress = psbt.ChangeAddress?.ToString()
|
||||
};
|
||||
|
||||
var res = await TryHandleSigningCommands(walletId, psbt.PSBT, command, signingContext);
|
||||
if (res != null)
|
||||
{
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case "analyze-psbt":
|
||||
var name =
|
||||
$"Send-{string.Join('_', vm.Outputs.Select(output => $"{output.Amount}->{output.DestinationAddress}{(output.SubtractFeesFromOutput ? "-Fees" : string.Empty)}"))}.psbt";
|
||||
return RedirectToWalletPSBT(psbt.PSBT, name, vm.PayJoinEndpointUrl);
|
||||
return RedirectToWalletPSBT(new WalletPSBTViewModel()
|
||||
{
|
||||
PSBT = psbt.PSBT.ToBase64(),
|
||||
FileName = name
|
||||
});
|
||||
default:
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void LoadFromBIP21(WalletSendModel vm, string bip21, BTCPayNetwork network)
|
||||
|
@ -729,14 +741,12 @@ namespace BTCPayServer.Controllers
|
|||
ModelState.Clear();
|
||||
}
|
||||
|
||||
private IActionResult ViewVault(WalletId walletId, PSBT psbt, string payJoinEndpointUrl, PSBT originalPSBT = null)
|
||||
private IActionResult ViewVault(WalletId walletId, SigningContextModel signingContext)
|
||||
{
|
||||
return View(nameof(WalletSendVault), new WalletSendVaultModel()
|
||||
{
|
||||
PayJoinEndpointUrl = payJoinEndpointUrl,
|
||||
SigningContext = signingContext,
|
||||
WalletId = walletId.ToString(),
|
||||
OriginalPSBT = originalPSBT?.ToBase64(),
|
||||
PSBT = psbt.ToBase64(),
|
||||
WebsocketPath = this.Url.Action(nameof(VaultController.VaultBridgeConnection), "Vault", new { walletId = walletId.ToString() })
|
||||
});
|
||||
}
|
||||
|
@ -746,48 +756,57 @@ namespace BTCPayServer.Controllers
|
|||
public IActionResult WalletSendVault([ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, WalletSendVaultModel model)
|
||||
{
|
||||
return RedirectToWalletPSBTReady(model.PSBT, originalPsbt: model.OriginalPSBT, payJoinEndpointUrl: model.PayJoinEndpointUrl);
|
||||
return RedirectToWalletPSBTReady(new WalletPSBTReadyViewModel()
|
||||
{
|
||||
SigningContext = model.SigningContext
|
||||
});
|
||||
}
|
||||
private IActionResult RedirectToWalletPSBTReady(string psbt, string signingKey= null, string signingKeyPath = null, string originalPsbt = null, string payJoinEndpointUrl = null)
|
||||
private IActionResult RedirectToWalletPSBTReady(WalletPSBTReadyViewModel vm)
|
||||
{
|
||||
var vm = new PostRedirectViewModel()
|
||||
var redirectVm = new PostRedirectViewModel()
|
||||
{
|
||||
AspController = "Wallets",
|
||||
AspAction = nameof(WalletPSBTReady),
|
||||
Parameters =
|
||||
{
|
||||
new KeyValuePair<string, string>("psbt", psbt),
|
||||
new KeyValuePair<string, string>("originalPsbt", originalPsbt),
|
||||
new KeyValuePair<string, string>("payJoinEndpointUrl", payJoinEndpointUrl),
|
||||
new KeyValuePair<string, string>("SigningKey", signingKey),
|
||||
new KeyValuePair<string, string>("SigningKeyPath", signingKeyPath)
|
||||
new KeyValuePair<string, string>("SigningKey", vm.SigningKey),
|
||||
new KeyValuePair<string, string>("SigningKeyPath", vm.SigningKeyPath)
|
||||
}
|
||||
};
|
||||
return View("PostRedirect", vm);
|
||||
AddSigningContext(redirectVm, vm.SigningContext);
|
||||
return View("PostRedirect", redirectVm);
|
||||
}
|
||||
|
||||
private IActionResult RedirectToWalletPSBT(PSBT psbt, string fileName = null, string payJoinEndpointUrl = null)
|
||||
|
||||
private void AddSigningContext(PostRedirectViewModel redirectVm, SigningContextModel signingContext)
|
||||
{
|
||||
var vm = new PostRedirectViewModel()
|
||||
if (signingContext is null)
|
||||
return;
|
||||
redirectVm.Parameters.Add(new KeyValuePair<string, string>("SigningContext.PSBT", signingContext.PSBT));
|
||||
redirectVm.Parameters.Add(new KeyValuePair<string, string>("SigningContext.OriginalPSBT", signingContext.OriginalPSBT));
|
||||
redirectVm.Parameters.Add(new KeyValuePair<string, string>("SigningContext.PayJoinEndpointUrl", signingContext.PayJoinEndpointUrl));
|
||||
redirectVm.Parameters.Add(new KeyValuePair<string, string>("SigningContext.EnforceLowR", signingContext.EnforceLowR?.ToString(CultureInfo.InvariantCulture)));
|
||||
redirectVm.Parameters.Add(new KeyValuePair<string, string>("SigningContext.ChangeAddress", signingContext.ChangeAddress));
|
||||
}
|
||||
|
||||
private IActionResult RedirectToWalletPSBT(WalletPSBTViewModel vm)
|
||||
{
|
||||
var redirectVm = new PostRedirectViewModel()
|
||||
{
|
||||
AspController = "Wallets",
|
||||
AspAction = nameof(WalletPSBT),
|
||||
Parameters =
|
||||
{
|
||||
new KeyValuePair<string, string>("psbt", psbt.ToBase64())
|
||||
new KeyValuePair<string, string>("psbt", vm.PSBT),
|
||||
new KeyValuePair<string, string>("fileName", vm.FileName)
|
||||
}
|
||||
};
|
||||
if (!string.IsNullOrEmpty(fileName))
|
||||
vm.Parameters.Add(new KeyValuePair<string, string>("fileName", fileName));
|
||||
if (!string.IsNullOrEmpty(payJoinEndpointUrl))
|
||||
vm.Parameters.Add(new KeyValuePair<string, string>("payJoinEndpointUrl", payJoinEndpointUrl));
|
||||
return View("PostRedirect", vm);
|
||||
return View("PostRedirect", redirectVm);
|
||||
}
|
||||
|
||||
void SetAmbientPSBT(PSBT psbt)
|
||||
void SetAmbientPSBT(string psbt)
|
||||
{
|
||||
if (psbt != null)
|
||||
TempData["AmbientPSBT"] = psbt.ToBase64();
|
||||
TempData["AmbientPSBT"] = psbt;
|
||||
else
|
||||
TempData.Remove("AmbientPSBT");
|
||||
}
|
||||
|
@ -806,33 +825,34 @@ namespace BTCPayServer.Controllers
|
|||
return null;
|
||||
}
|
||||
|
||||
private ViewResult ViewWalletSendLedger(WalletId walletId, PSBT psbt, BitcoinAddress hintChange = null)
|
||||
private ViewResult ViewWalletSendLedger(WalletId walletId, SigningContextModel signingContext)
|
||||
{
|
||||
SetAmbientPSBT(psbt);
|
||||
SetAmbientPSBT(signingContext.PSBT);
|
||||
return View("WalletSendLedger", new WalletSendLedgerModel()
|
||||
{
|
||||
PSBT = psbt.ToBase64(),
|
||||
HintChange = hintChange?.ToString(),
|
||||
SigningContext = signingContext,
|
||||
WebsocketPath = this.Url.Action(nameof(LedgerConnection), new { walletId = walletId.ToString() })
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[HttpPost]
|
||||
[Route("{walletId}/ledger")]
|
||||
public IActionResult SubmitLedger([ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, WalletSendLedgerModel model)
|
||||
{
|
||||
return RedirectToWalletPSBTReady(model.PSBT);
|
||||
return RedirectToWalletPSBTReady(new WalletPSBTReadyViewModel()
|
||||
{
|
||||
SigningContext = model.SigningContext
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("{walletId}/psbt/seed")]
|
||||
public IActionResult SignWithSeed([ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId,string psbt, string payJoinEndpointUrl)
|
||||
WalletId walletId, SigningContextModel signingContext)
|
||||
{
|
||||
return View(nameof(SignWithSeed), new SignWithSeedViewModel()
|
||||
{
|
||||
PayJoinEndpointUrl = payJoinEndpointUrl,
|
||||
PSBT = psbt
|
||||
SigningContext = signingContext,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -856,11 +876,11 @@ namespace BTCPayServer.Controllers
|
|||
"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);
|
||||
var psbt = PSBT.Parse(viewModel.SigningContext.PSBT, network.NBitcoinNetwork);
|
||||
|
||||
if (!psbt.IsReadyToSign())
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.PSBT), "PSBT is not ready to be signed");
|
||||
ModelState.AddModelError(nameof(viewModel.SigningContext.PSBT), "PSBT is not ready to be signed");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
|
@ -878,7 +898,7 @@ namespace BTCPayServer.Controllers
|
|||
if (rootedKeyPath == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.SeedOrKey), "The master fingerprint and/or account key path of your seed are not set in the wallet settings.");
|
||||
return View("SignWithSeed", viewModel);
|
||||
return View(nameof(SignWithSeed), viewModel);
|
||||
}
|
||||
// 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())
|
||||
|
@ -892,17 +912,26 @@ namespace BTCPayServer.Controllers
|
|||
return View(viewModel);
|
||||
}
|
||||
|
||||
var changed = PSBTChanged(psbt, () => psbt.SignAll(settings.AccountDerivation, signingKey, rootedKeyPath));
|
||||
var changed = PSBTChanged(psbt, () => psbt.SignAll(settings.AccountDerivation, signingKey, rootedKeyPath, new SigningOptions()
|
||||
{
|
||||
EnforceLowR = !(viewModel.SigningContext?.EnforceLowR is false)
|
||||
}));
|
||||
if (!changed)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.SeedOrKey), "Impossible to sign the transaction. Probable cause: Incorrect account key path in wallet settings, PSBT already signed.");
|
||||
return View(viewModel);
|
||||
}
|
||||
ModelState.Remove(nameof(viewModel.PSBT));
|
||||
return RedirectToWalletPSBTReady(psbt.ToBase64(), signingKey.GetWif(network.NBitcoinNetwork).ToString(), rootedKeyPath?.ToString(), viewModel.OriginalPSBT, viewModel.PayJoinEndpointUrl);
|
||||
ModelState.Remove(nameof(viewModel.SigningContext.PSBT));
|
||||
viewModel.SigningContext.PSBT = psbt.ToBase64();
|
||||
return RedirectToWalletPSBTReady(new WalletPSBTReadyViewModel()
|
||||
{
|
||||
SigningKey = signingKey.GetWif(network.NBitcoinNetwork).ToString(),
|
||||
SigningKeyPath = rootedKeyPath?.ToString(),
|
||||
SigningContext = viewModel.SigningContext
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private bool PSBTChanged(PSBT psbt, Action act)
|
||||
{
|
||||
var before = psbt.ToBase64();
|
||||
|
@ -1244,14 +1273,15 @@ namespace BTCPayServer.Controllers
|
|||
}
|
||||
else if (command == "view-seed" && await CanUseHotWallet())
|
||||
{
|
||||
var seed = await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode)
|
||||
var seed = await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode)
|
||||
.GetMetadataAsync<string>(derivationScheme.AccountDerivation,
|
||||
WellknownMetadataKeys.Mnemonic, cancellationToken);
|
||||
if (string.IsNullOrEmpty(seed))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error, Message = "The seed was not found"
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = "The seed was not found"
|
||||
});
|
||||
}
|
||||
else
|
||||
|
@ -1262,7 +1292,7 @@ namespace BTCPayServer.Controllers
|
|||
Html = $"Please store your seed securely! <br/><code class=\"alert-link\">{seed}</code>"
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
return RedirectToAction(nameof(WalletSettings));
|
||||
}
|
||||
else
|
||||
|
|
|
@ -7,11 +7,10 @@ namespace BTCPayServer.Models.WalletViewModels
|
|||
{
|
||||
public class SignWithSeedViewModel
|
||||
{
|
||||
public string OriginalPSBT { get; set; }
|
||||
public string PayJoinEndpointUrl { get; set; }
|
||||
public SigningContextModel SigningContext { get; set; } = new SigningContextModel();
|
||||
|
||||
[Required]
|
||||
public string PSBT { get; set; }
|
||||
[Required][Display(Name = "BIP39 Seed (12/24 word mnemonic phrase) or HD private key (xprv...)")]
|
||||
[Display(Name = "BIP39 Seed (12/24 word mnemonic phrase) or HD private key (xprv...)")]
|
||||
public string SeedOrKey { get; set; }
|
||||
|
||||
[Display(Name = "Optional seed passphrase")]
|
||||
|
|
25
BTCPayServer/Models/WalletViewModels/SigningContextModel.cs
Normal file
25
BTCPayServer/Models/WalletViewModels/SigningContextModel.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Models.WalletViewModels
|
||||
{
|
||||
public class SigningContextModel
|
||||
{
|
||||
public SigningContextModel()
|
||||
{
|
||||
|
||||
}
|
||||
public SigningContextModel(PSBT psbt)
|
||||
{
|
||||
PSBT = psbt.ToBase64();
|
||||
}
|
||||
public string PSBT { get; set; }
|
||||
public string OriginalPSBT { get; set; }
|
||||
public string PayJoinEndpointUrl { get; set; }
|
||||
public bool? EnforceLowR { get; set; }
|
||||
public string ChangeAddress { get; set; }
|
||||
}
|
||||
}
|
|
@ -8,9 +8,7 @@ namespace BTCPayServer.Models.WalletViewModels
|
|||
{
|
||||
public class WalletPSBTReadyViewModel
|
||||
{
|
||||
public string PayJoinEndpointUrl { get; set; }
|
||||
public string OriginalPSBT { get; set; }
|
||||
public string PSBT { get; set; }
|
||||
public SigningContextModel SigningContext { get; set; } = new SigningContextModel();
|
||||
public string SigningKey { get; set; }
|
||||
public string SigningKeyPath { get; set; }
|
||||
public string GlobalError { get; set; }
|
||||
|
|
|
@ -10,7 +10,6 @@ namespace BTCPayServer.Models.WalletViewModels
|
|||
{
|
||||
public class WalletPSBTViewModel
|
||||
{
|
||||
public string PayJoinEndpointUrl { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string Decoded { get; set; }
|
||||
string _FileName;
|
||||
|
|
|
@ -8,7 +8,6 @@ namespace BTCPayServer.Models.WalletViewModels
|
|||
public class WalletSendLedgerModel
|
||||
{
|
||||
public string WebsocketPath { get; set; }
|
||||
public string PSBT { get; set; }
|
||||
public string HintChange { get; set; }
|
||||
public SigningContextModel SigningContext { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,10 +7,8 @@ namespace BTCPayServer.Models.WalletViewModels
|
|||
{
|
||||
public class WalletSendVaultModel
|
||||
{
|
||||
public string OriginalPSBT { get; set; }
|
||||
public string WalletId { get; set; }
|
||||
public string PSBT { get; set; }
|
||||
public string WebsocketPath { get; set; }
|
||||
public string PayJoinEndpointUrl { get; set; }
|
||||
public SigningContextModel SigningContext { get; set; } = new SigningContextModel();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ using BTCPayServer.Data;
|
|||
using NBitcoin.DataEncoders;
|
||||
using Amazon.S3.Model;
|
||||
using BTCPayServer.Logging;
|
||||
using NBitcoin.Crypto;
|
||||
|
||||
namespace BTCPayServer.Payments.PayJoin
|
||||
{
|
||||
|
@ -234,7 +235,7 @@ namespace BTCPayServer.Payments.PayJoin
|
|||
return BadRequest(CreatePayjoinError("invalid-transaction",
|
||||
$"Provided transaction isn't mempool eligible {mempool.RPCCodeMessage}"));
|
||||
}
|
||||
|
||||
var enforcedLowR = ctx.OriginalTransaction.Inputs.All(IsLowR);
|
||||
var paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
|
||||
bool paidSomething = false;
|
||||
Money due = null;
|
||||
|
@ -472,7 +473,10 @@ namespace BTCPayServer.Payments.PayJoin
|
|||
var coin = selectedUtxo.AsCoin(derivationSchemeSettings.AccountDerivation);
|
||||
signedInput.UpdateFromCoin(coin);
|
||||
var privateKey = accountKey.Derive(selectedUtxo.KeyPath).PrivateKey;
|
||||
signedInput.Sign(privateKey);
|
||||
signedInput.Sign(privateKey, new SigningOptions()
|
||||
{
|
||||
EnforceLowR = enforcedLowR
|
||||
});
|
||||
signedInput.FinalizeInput();
|
||||
newTx.Inputs[signedInput.Index].WitScript = newPsbt.Inputs[(int)signedInput.Index].FinalScriptWitness;
|
||||
}
|
||||
|
@ -623,5 +627,12 @@ namespace BTCPayServer.Payments.PayJoin
|
|||
}
|
||||
return (Array.Empty<UTXO>(), PayjoinUtxoSelectionType.Unavailable);
|
||||
}
|
||||
private static bool IsLowR(TxIn txin)
|
||||
{
|
||||
IEnumerable<byte[]> pushes = txin.WitScript.PushCount > 0 ? txin.WitScript.Pushes :
|
||||
txin.ScriptSig.IsPushOnly ? txin.ScriptSig.ToOps().Select(o => o.PushData) :
|
||||
Array.Empty<byte[]>();
|
||||
return pushes.Where(p => ECDSASignature.IsValidDER(p)).All(p => p.Length <= 71);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,9 +27,7 @@
|
|||
<div class="col-md-10">
|
||||
<div asp-validation-summary="All" class="text-danger"></div>
|
||||
<form method="post" asp-action="SignWithSeed" asp-route-walletId="@this.Context.GetRouteValue("walletId")">
|
||||
<input type="hidden" asp-for="OriginalPSBT" />
|
||||
<input type="hidden" asp-for="PSBT" />
|
||||
<input type="hidden" asp-for="PayJoinEndpointUrl" />
|
||||
<partial name="SigningContext" for="SigningContext" />
|
||||
<div class="form-group">
|
||||
<label asp-for="SeedOrKey"></label>
|
||||
<input asp-for="SeedOrKey" class="form-control" />
|
||||
|
|
10
BTCPayServer/Views/Wallets/SigningContext.cshtml
Normal file
10
BTCPayServer/Views/Wallets/SigningContext.cshtml
Normal file
|
@ -0,0 +1,10 @@
|
|||
@model BTCPayServer.Models.WalletViewModels.SigningContextModel
|
||||
|
||||
@if (Model != null)
|
||||
{
|
||||
<input type="hidden" asp-for="PSBT" />
|
||||
<input type="hidden" asp-for="OriginalPSBT" />
|
||||
<input type="hidden" asp-for="PayJoinEndpointUrl" />
|
||||
<input type="hidden" asp-for="EnforceLowR" />
|
||||
<input type="hidden" asp-for="ChangeAddress" />
|
||||
}
|
|
@ -29,13 +29,12 @@
|
|||
<h3>Decoded PSBT</h3>
|
||||
<div class="form-group">
|
||||
<form method="post" asp-action="WalletPSBT" asp-route-walletId="@this.Context.GetRouteValue("walletId")">
|
||||
<input type="hidden" asp-for="CryptoCode"/>
|
||||
<input type="hidden" asp-for="PayJoinEndpointUrl"/>
|
||||
<input type="hidden" asp-for="NBXSeedAvailable"/>
|
||||
<input type="hidden" asp-for="PSBT"/>
|
||||
<input type="hidden" asp-for="FileName"/>
|
||||
<input type="hidden" asp-for="CryptoCode" />
|
||||
<input type="hidden" asp-for="NBXSeedAvailable" />
|
||||
<input type="hidden" asp-for="PSBT" />
|
||||
<input type="hidden" asp-for="FileName" />
|
||||
|
||||
<partial name="WalletSigningMenu" model="@((Model.CryptoCode, Model.NBXSeedAvailable))"/>
|
||||
<partial name="WalletSigningMenu" model="@((Model.CryptoCode, Model.NBXSeedAvailable))" />
|
||||
<div class="dropdown d-inline-block">
|
||||
<button class="btn btn-secondary dropdown-toggle" type="button" id="OtherActions" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Other actions...
|
||||
|
|
|
@ -137,14 +137,12 @@
|
|||
<div class="row">
|
||||
<div class="col-lg-12 text-center">
|
||||
<form method="post" asp-action="WalletPSBTReady" asp-route-walletId="@this.Context.GetRouteValue("walletId")">
|
||||
<input type="hidden" asp-for="PSBT" value="@Model.PSBT"/>
|
||||
<input type="hidden" asp-for="OriginalPSBT"/>
|
||||
<input type="hidden" asp-for="SigningKey"/>
|
||||
<input type="hidden" asp-for="SigningKeyPath"/>
|
||||
<input type="hidden" asp-for="PayJoinEndpointUrl"/>
|
||||
<input type="hidden" asp-for="SigningKey" />
|
||||
<input type="hidden" asp-for="SigningKeyPath" />
|
||||
<partial name="SigningContext" for="SigningContext" />
|
||||
@if (!Model.HasErrors)
|
||||
{
|
||||
@if (!string.IsNullOrEmpty(Model.PayJoinEndpointUrl))
|
||||
@if (!string.IsNullOrEmpty(Model.SigningContext?.PayJoinEndpointUrl))
|
||||
{
|
||||
<button type="submit" class="btn btn-primary" name="command" value="payjoin">Broadcast (Payjoin)</button>
|
||||
<span> or </span>
|
||||
|
|
|
@ -21,8 +21,7 @@
|
|||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
<form id="broadcastForm" asp-action="SubmitLedger" asp-route-walletId="@this.Context.GetRouteValue("walletId")" method="post" style="display:none;">
|
||||
<input type="hidden" id="PSBT" asp-for="PSBT" value="@Model.PSBT"/>
|
||||
<input type="hidden" asp-for="HintChange" />
|
||||
<partial name="SigningContext" for="SigningContext" />
|
||||
<input type="hidden" asp-for="WebsocketPath" />
|
||||
</form>
|
||||
<p>
|
||||
|
|
|
@ -22,10 +22,8 @@
|
|||
<div id="body" class="col-md-10">
|
||||
<form id="broadcastForm" asp-action="WalletSendVault" asp-route-walletId="@this.Context.GetRouteValue("walletId")" method="post" style="display:none;">
|
||||
<input type="hidden" id="WalletId" asp-for="WalletId" />
|
||||
<input type="hidden" id="PSBT" asp-for="PSBT" value="@Model.PSBT"/>
|
||||
<input type="hidden" id="OriginalPSBT" asp-for="OriginalPSBT" value="@Model.OriginalPSBT"/>
|
||||
<input type="hidden" asp-for="WebsocketPath" />
|
||||
<input type="hidden" asp-for="PayJoinEndpointUrl" />
|
||||
<partial name="SigningContext" for="SigningContext" />
|
||||
</form>
|
||||
<div id="vaultPlaceholder"></div>
|
||||
<button id="vault-confirm" class="btn btn-primary" style="display:none;"></button>
|
||||
|
@ -51,13 +49,12 @@
|
|||
ws_uri += websocketPath;
|
||||
var html = $("#VaultConnection").html();
|
||||
$("#vaultPlaceholder").html(html);
|
||||
|
||||
var vaultUI = new vaultui.VaultBridgeUI(ws_uri);
|
||||
if (await vaultUI.askForDevice() && await vaultUI.askSignPSBT({
|
||||
walletId: $("#WalletId").val(),
|
||||
psbt: $("#PSBT").val()
|
||||
psbt: $("#SigningContext_PSBT").val()
|
||||
})) {
|
||||
$("#PSBT").val(vaultUI.psbt);
|
||||
$("#SigningContext_PSBT").val(vaultUI.psbt);
|
||||
$("#broadcastForm").submit();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
$(function () {
|
||||
var psbt = $("#PSBT").val();
|
||||
var hintChange = $("#HintChange").val();
|
||||
var hintChange = $("#SigningContext_ChangeAddress").val();
|
||||
var websocketPath = $("#WebsocketPath").val();
|
||||
|
||||
var loc = window.location, ws_uri;
|
||||
|
@ -38,7 +38,6 @@
|
|||
return false;
|
||||
$(".crypto-info").css("display", "block");
|
||||
var args = "";
|
||||
args += "&psbt=" + encodeURIComponent(psbt);
|
||||
args += "&hintChange=" + encodeURIComponent(hintChange);
|
||||
|
||||
WriteAlert("warning", 'Please validate the transaction on your ledger');
|
||||
|
@ -61,7 +60,7 @@
|
|||
if (result.error) {
|
||||
WriteAlert("danger", result.error);
|
||||
} else {
|
||||
$("#PSBT").val(result.psbt);
|
||||
$("#SigningContext_PSBT").val(result.psbt);
|
||||
$("#broadcastForm").submit();
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue