Wallet transactions: Add label manager (#4796)

* Wallet transactions: Add label manager

* Update BTCPayServer/Views/UIWallets/WalletTransactions.cshtml

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

* Add rich label info

* Fixes

* support labels in wallet send

* add labels to tx info page

* Remove noscript parts

* Allow click on transaction label info

* update psbt info labelstyling

* revert red pixel fix as it broke all

---------

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
This commit is contained in:
d11n
2023-03-26 13:42:38 +02:00
committed by GitHub
parent 8635fcfe84
commit 95f3e429b4
18 changed files with 453 additions and 227 deletions

View File

@@ -7,6 +7,7 @@ using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.BIP78.Sender;
using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.ModelBinders;
using BTCPayServer.Models;
@@ -266,7 +267,7 @@ namespace BTCPayServer.Controllers
ModelState.Remove(nameof(vm.PSBT));
ModelState.Remove(nameof(vm.FileName));
ModelState.Remove(nameof(vm.UploadedPSBTFile));
await FetchTransactionDetails(derivationSchemeSettings, vm, network);
await FetchTransactionDetails(walletId,derivationSchemeSettings, vm, network);
return View("WalletPSBTDecoded", vm);
case "save-psbt":
@@ -320,7 +321,7 @@ namespace BTCPayServer.Controllers
return await _payjoinClient.RequestPayjoin(bip21, new PayjoinWallet(derivationSchemeSettings), psbt, cts.Token);
}
private async Task FetchTransactionDetails(DerivationSchemeSettings derivationSchemeSettings, WalletPSBTReadyViewModel vm, BTCPayNetwork network)
private async Task FetchTransactionDetails(WalletId walletId, DerivationSchemeSettings derivationSchemeSettings, WalletPSBTReadyViewModel vm, BTCPayNetwork network)
{
var psbtObject = PSBT.Parse(vm.SigningContext.PSBT, network.NBitcoinNetwork);
if (!psbtObject.IsAllFinalized())
@@ -371,17 +372,29 @@ namespace BTCPayServer.Controllers
vm.Positive = balanceChange >= Money.Zero;
}
vm.Inputs = new List<WalletPSBTReadyViewModel.InputViewModel>();
var inputToObjects = new Dictionary<uint, ObjectTypeId[]>();
var outputToObjects = new Dictionary<string, ObjectTypeId>();
foreach (var input in psbtObject.Inputs)
{
var inputVm = new WalletPSBTReadyViewModel.InputViewModel();
vm.Inputs.Add(inputVm);
var txOut = input.GetTxOut();
var mine = input.HDKeysFor(derivationSchemeSettings.AccountDerivation, signingKey, signingKeyPath).Any();
var balanceChange2 = input.GetTxOut()?.Value ?? Money.Zero;
var balanceChange2 = txOut?.Value ?? Money.Zero;
if (mine)
balanceChange2 = -balanceChange2;
inputVm.BalanceChange = ValueToString(balanceChange2, network);
inputVm.Positive = balanceChange2 >= Money.Zero;
inputVm.Index = (int)input.Index;
var walletObjectIds = new List<ObjectTypeId>();
walletObjectIds.Add(new ObjectTypeId(WalletObjectData.Types.Utxo, input.PrevOut.ToString()));
walletObjectIds.Add(new ObjectTypeId(WalletObjectData.Types.Tx, input.PrevOut.Hash.ToString()));
var address = txOut?.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork)?.ToString();
if(address != null)
walletObjectIds.Add(new ObjectTypeId(WalletObjectData.Types.Address, address));
inputToObjects.Add(input.Index, walletObjectIds.ToArray());
}
vm.Destinations = new List<WalletPSBTReadyViewModel.DestinationViewModel>();
foreach (var output in psbtObject.Outputs)
@@ -395,6 +408,10 @@ namespace BTCPayServer.Controllers
dest.Balance = ValueToString(balanceChange2, network);
dest.Positive = balanceChange2 >= Money.Zero;
dest.Destination = output.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork)?.ToString() ?? output.ScriptPubKey.ToString();
var address = output.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork)?.ToString();
if(address != null)
outputToObjects.Add(dest.Destination, new ObjectTypeId(WalletObjectData.Types.Address, address));
}
if (psbtObject.TryGetFee(out var fee))
@@ -420,6 +437,38 @@ namespace BTCPayServer.Controllers
{
vm.SetErrors(errors);
}
var combinedTypeIds = inputToObjects.Values.SelectMany(ids => ids).Concat(outputToObjects.Values)
.DistinctBy(id => $"{id.Type}:{id.Id}").ToArray();
var labelInfo = await WalletRepository.GetWalletTransactionsInfo(walletId, combinedTypeIds);
foreach (KeyValuePair<uint,ObjectTypeId[]> inputToObject in inputToObjects)
{
var keys = inputToObject.Value.Select(id => id.Id).ToArray();
WalletTransactionInfo ix = null;
foreach (var key in keys)
{
if (!labelInfo.TryGetValue(key, out var i)) continue;
if (ix is null)
{
ix = i;
}
else
{
ix.Merge(i);
}
}
if (ix is null) continue;
var input = vm.Inputs.First(model => model.Index == inputToObject.Key);
input.Labels = ix.LabelColors;
}
foreach (var outputToObject in outputToObjects)
{
if (!labelInfo.TryGetValue(outputToObject.Value.Id, out var ix)) continue;
var destination = vm.Destinations.First(model => model.Destination == outputToObject.Key);
destination.Labels = ix.LabelColors;
}
}
[HttpPost("{walletId}/psbt/ready")]
@@ -439,7 +488,7 @@ namespace BTCPayServer.Controllers
if (derivationSchemeSettings == null)
return NotFound();
await FetchTransactionDetails(derivationSchemeSettings, vm, network);
await FetchTransactionDetails(walletId,derivationSchemeSettings, vm, network);
switch (command)
{
@@ -570,7 +619,7 @@ namespace BTCPayServer.Controllers
BackUrl = vm.BackUrl
});
case "decode":
await FetchTransactionDetails(derivationSchemeSettings, vm, network);
await FetchTransactionDetails(walletId,derivationSchemeSettings, vm, network);
return View("WalletPSBTDecoded", vm);
default:
vm.Errors.Add("Unknown command");