@@ -211,10 +249,10 @@
}
-
-
+
+
All Requests
-
+
-
+
@context.CreationDatetime.Humanize()
@@ -303,21 +341,19 @@
+ SignedPSBT="@_psbt"/>
+ OnSubmit="@RejectOrCancelRequest"/>
+ OnSubmit="@ApproveOperationSubmitConfirmationModal"/>
+
+
@inject IChannelOperationRequestRepository ChannelOperationRequestRepository
@inject IChannelOperationRequestPSBTRepository ChannelOperationRequestPsbtRepository
@inject IToastService ToastService
@inject ILightningService LightningService
-@inject ISchedulerFactory SchedulerFactory
+@inject ISchedulerFactory SchedulerFactory
@inject IWalletRepository WalletRepository
@inject INodeRepository NodeRepository
-@inject NavigationManager uriHelper;
+@inject ICoinSelectionService CoinSelectionService
@code {
private List? _channelRequests;
@@ -361,6 +400,10 @@
private bool _isNodeManager = false;
private ConfirmationModal _approveOperationConfirmationModal;
private ConfirmationModal _markRequestAsFailedConfirmationModal;
+ private UTXOSelectorModal _utxoSelectorModalRef;
+ private List SelectedUTXOs = new();
+ private MempoolRecommendedFeesTypes FeesSelection;
+ private long FeeAmount;
// New Request integration
private List _allWallets = new List();
@@ -369,10 +412,10 @@
private List? _manageableNodes;
private int _selectedSourceNodeId;
private Node? _selectedDestNode;
- private int _selectedWalletId;
+ private int? _selectedWalletId;
private string? _destNodeName;
private long _amount = Constants.MINIMUM_CHANNEL_CAPACITY_SATS;
- private bool _selectedPrivate = false;
+ private bool _selectedPrivate = false;
//Validation
private Validation? _walletValidation;
@@ -390,7 +433,7 @@
private ApplicationUser? LoggedUser { get; set; }
[CascadingParameter]
- private ClaimsPrincipal? ClaimsPrincipal {get; set; }
+ private ClaimsPrincipal? ClaimsPrincipal { get; set; }
protected override async Task OnInitializedAsync()
{
@@ -408,6 +451,14 @@
await LoadData();
}
+ private async Task OnShowNewChannelRequestModal()
+ {
+ _utxoSelectorModalRef.ClearModal();
+ FeesSelection = 0;
+ FeeAmount = 0;
+ await datagridRef.New();
+ }
+
private async Task ResetChannelRequestRejectModal()
{
await datagridRef.Edit(null);
@@ -458,13 +509,11 @@
private void OnSelectedSourceNode(int nodeId)
{
_selectedSourceNodeId = nodeId;
-
}
private void OnSelectedWallet(int walletId)
{
-
- _selectedWalletId = walletId;
+ _selectedWalletId = walletId == 0 ? null : walletId;
}
private async Task SearchNode()
@@ -475,17 +524,15 @@
var foundNode = await LightningService.GetNodeInfo(_destNodeName);
if (foundNode != null)
{
-
_selectedDestNode = (await NodeRepository.GetByPubkey(_destNodeName));
- //if not found we create it..
+ //if not found we create it..
if (_selectedDestNode == null)
{
_selectedDestNode = new Node
{
Name = foundNode.Alias,
PubKey = _destNodeName,
-
};
var nodeAddResult = await NodeRepository.AddAsync(_selectedDestNode);
@@ -496,7 +543,7 @@
}
}
- // Refresh the list of available source nodes and take out the one selected
+ // Refresh the list of available source nodes and take out the one selected
_manageableNodes = await NodeRepository.GetAllManagedByUser(LoggedUser.Id);
_manageableNodes = _manageableNodes.Where(node => node.Id != _selectedDestNode?.Id).ToList();
_destNodeValidation.Clear();
@@ -508,15 +555,20 @@
{
if (LoggedUser == null) return;
- Validation?[] validators = {_destNodeValidation, _sourceNodeValidation, _walletValidation, _capacityValidation};
+ List validators = new() { _destNodeValidation, _sourceNodeValidation, _walletValidation };
+ if (SelectedUTXOs.Count == 0)
+ {
+ validators.Add(_capacityValidation);
+ }
if (validators.All(v => v != null && (int)v.Validate() == 1))
{
if (_selectedDestNode?.Id != _selectedSourceNodeId)
{
+ var amount = SelectedUTXOs.Count > 0 ? SelectedUTXOsValue() : _amount;
var request = new ChannelOperationRequest()
{
- SatsAmount = _amount,
+ SatsAmount = amount,
RequestType = OperationRequestType.Open,
Description = "Created by user via Funds Manager",
WalletId = _selectedWalletId,
@@ -524,10 +576,13 @@
UserId = LoggedUser.Id,
SourceNodeId = _selectedSourceNodeId,
DestNodeId = _selectedDestNode?.Id,
- IsChannelPrivate = _selectedPrivate
+ IsChannelPrivate = _selectedPrivate,
+ Changeless = SelectedUTXOs.Count > 0
};
- var selectedWallet = await WalletRepository.GetById(_selectedWalletId);
+ var selectedWallet = await WalletRepository.GetById(_selectedWalletId.Value);
+
+
if (selectedWallet.IsHotWallet)
{
_selectedRequest = request;
@@ -542,11 +597,17 @@
if (createChannelResult.Item1)
{
ToastService.ShowSuccess("Open channel request created!");
+
+ if (SelectedUTXOs.Count > 0)
+ {
+ await CoinSelectionService.LockUTXOs(SelectedUTXOs, request, BitcoinRequestType.ChannelOperation);
+ }
}
else
{
ToastService.ShowError(createChannelResult.Item2);
}
+ _utxoSelectorModalRef.ClearModal();
}
else
{
@@ -560,7 +621,6 @@
#endregion
-
private static bool RequestPendingFilter(ChannelOperationRequest req)
{
return req.Status.Equals(ChannelOperationRequestStatus.Pending) || req.Status == ChannelOperationRequestStatus.PSBTSignaturesPending;
@@ -575,10 +635,14 @@
{
_selectedRequest = req;
_selectedStatus = status;
- switch(_selectedStatus)
+ switch (_selectedStatus)
{
- case ChannelOperationRequestStatus.Rejected: _selectedStatusActionString = "Reject"; break;
- case ChannelOperationRequestStatus.Cancelled: _selectedStatusActionString = "Cancel"; break;
+ case ChannelOperationRequestStatus.Rejected:
+ _selectedStatusActionString = "Reject";
+ break;
+ case ChannelOperationRequestStatus.Cancelled:
+ _selectedStatusActionString = "Cancel";
+ break;
}
await _rejectCancelModalRef.ShowModal();
}
@@ -611,28 +675,25 @@
{
_selectedRequest = channelOperationRequest;
_psbt = string.Empty;
- if (_selectedRequest != null && !_selectedRequest.AreAllRequiredHumanSignaturesCollected) {
+ if (_selectedRequest != null && !_selectedRequest.AreAllRequiredHumanSignaturesCollected)
+ {
var (templatePsbt,noUtxosAvailable) = (await LightningService.GenerateTemplatePSBT(_selectedRequest));
if (templatePsbt != null)
{
_templatePSBTString = templatePsbt.ToBase64();
await _psbtSignRef.ShowModal();
-
}
else
{
if (noUtxosAvailable)
{
ToastService.ShowError("No UTXOs found for this wallet, please wait for other requests to be confirmed or fund the wallet with more UTXOs");
-
}
else
{
ToastService.ShowError("Something went wrong");
-
}
}
-
}
}
@@ -644,13 +705,13 @@
{
ToastService.ShowError("Error: Not all fields were set");
}
- else {
+ else
+ {
ChannelOperationRequestPSBT channelOperationRequestPsbt = new()
{
ChannelOperationRequestId = _selectedRequest.Id,
PSBT = _psbtSignRef.SignedPSBT,
UserSignerId = LoggedUser.Id,
-
};
var addResult = await ChannelOperationRequestPsbtRepository.AddAsync(channelOperationRequestPsbt);
@@ -668,28 +729,17 @@
else
{
ToastService.ShowError("Invalid PSBT");
-
}
}
else
{
ToastService.ShowError("Error while saving the signature");
-
}
await FetchRequests();
await _psbtSignRef.HideModal();
StateHasChanged();
-
}
-
-
- }
-
- private bool IsStatusCancellable(ChannelOperationRequestStatus status)
- {
- return status is ChannelOperationRequestStatus.Pending
- or ChannelOperationRequestStatus.Approved;
}
private void RejectReasonValidator(ValidatorEventArgs e)
@@ -721,10 +771,16 @@
else
{
ToastService.ShowError(createChannelResult.Item2);
+ _utxoSelectorModalRef.ClearModal();
await _approveOperationConfirmationModal.CloseModal();
return;
}
+ if (SelectedUTXOs.Count > 0)
+ {
+ await CoinSelectionService.LockUTXOs(SelectedUTXOs, _selectedRequest, BitcoinRequestType.ChannelOperation);
+ }
+
var (templatePsbt, noUtxosAvailable) = (await LightningService.GenerateTemplatePSBT(_selectedRequest));
if (templatePsbt == null)
{
@@ -738,6 +794,7 @@
}
_selectedRequest.Status = ChannelOperationRequestStatus.Failed;
ChannelOperationRequestRepository.Update(_selectedRequest);
+ _utxoSelectorModalRef.ClearModal();
await _approveOperationConfirmationModal.CloseModal();
await FetchRequests();
StateHasChanged();
@@ -756,9 +813,10 @@
{
ToastService.ShowError("Invalid PSBT");
}
-
+
await FetchRequests();
+ _utxoSelectorModalRef.ClearModal();
await _approveOperationConfirmationModal.CloseModal();
}
}
@@ -767,7 +825,7 @@
{
try
{
- //TODO Async notifications when the channel has opened -> event / notifications system
+ //TODO Async notifications when the channel has opened -> event / notifications system
var scheduler = await SchedulerFactory.GetScheduler();
var map = new JobDataMap();
@@ -777,7 +835,7 @@
var job = RetriableJob.Create(map, _selectedRequest.Id.ToString(), retryList);
await scheduler.ScheduleJob(job.Job, job.Trigger);
- // TODO: Check job id
+ // TODO: Check job id
_selectedRequest.JobId = job.Job.Key.ToString();
var jobUpdateResult = ChannelOperationRequestRepository.Update(_selectedRequest);
@@ -818,7 +876,7 @@
}
catch (Exception? e)
{
- ToastService.ShowError("Error while marking request as failed");
+ ToastService.ShowError("Error while marking request as failed");
}
finally
{
@@ -832,4 +890,31 @@
_selectedRequestForMarkingAsFailed = context;
await _markRequestAsFailedConfirmationModal.ShowModal();
}
+
+ private async Task OpenCoinSelectionModal()
+ {
+ _utxoSelectorModalRef.ShowModal(_selectedWalletId.Value);
+ }
+
+ private void OnCloseCoinSelectionModal(List selectedUTXOs)
+ {
+ SelectedUTXOs = selectedUTXOs;
+ if (SelectedUTXOs.Count > 0)
+ {
+ _amount = SelectedUTXOsValue();
+ }
+ StateHasChanged();
+ }
+
+ private long SelectedUTXOsValue()
+ {
+ return SelectedUTXOs.Sum(x => ((Money)x.Value).Satoshi);
+ }
+
+ private void ClearSelectedUTXOs()
+ {
+ _utxoSelectorModalRef.ClearModal();
+ SelectedUTXOs.Clear();
+ StateHasChanged();
+ }
}
\ No newline at end of file
diff --git a/src/Pages/Channels.razor b/src/Pages/Channels.razor
index b97dbd1d..424c73ba 100644
--- a/src/Pages/Channels.razor
+++ b/src/Pages/Channels.razor
@@ -199,7 +199,7 @@
-
+
@@ -498,11 +498,11 @@
await ClearManagementModal();
_selectedChannel = channel;
-
+
var destinationNode = await NodeRepository.GetById(channel.DestinationNodeId);
var sourceNode = await NodeRepository.GetById(channel.SourceNodeId);
var node = String.IsNullOrEmpty(sourceNode.ChannelAdminMacaroon) ? destinationNode : sourceNode;
-
+
//If there is a liquidity rule for this channel, we load it, the first one
_currentLiquidityRule = _selectedChannel?.LiquidityRules.FirstOrDefault() ?? new LiquidityRule
{
@@ -567,7 +567,7 @@
arg1.Status = ValidationStatus.Success;
//If the minimum local balance is 0 this cannot be 0
- if ((_currentLiquidityRule.MinimumLocalBalance == 0 || _currentLiquidityRule.MinimumLocalBalance == null)
+ if ((_currentLiquidityRule.MinimumLocalBalance == 0 || _currentLiquidityRule.MinimumLocalBalance == null)
&& (_currentLiquidityRule.MinimumRemoteBalance == 0 || _currentLiquidityRule.MinimumRemoteBalance == null))
{
arg1.Status = ValidationStatus.Error;
@@ -685,7 +685,7 @@
await FetchData();
}
-
+
private bool checkDisableCloseChannelButton(Channel channel)
{
ChannelOperationRequest? lastRequest = channel.ChannelOperationRequests.LastOrDefault();
diff --git a/src/Pages/Nodes.razor b/src/Pages/Nodes.razor
index 0d97bf9e..39e122f7 100644
--- a/src/Pages/Nodes.razor
+++ b/src/Pages/Nodes.razor
@@ -102,7 +102,7 @@
-
+
@context.CreationDatetime.Humanize()
@@ -261,7 +261,7 @@
cts.Cancel(); // Cancel Task.Delay
cts.Dispose();
}
-
+
private async Task ShowDeleteDialog(Node node)
{
if (await MessageService.Confirm($"Are you sure you want to delete { node.Name }?", "Confirmation"))
@@ -279,14 +279,14 @@
_nodes = await NodeRepository.GetAll();
}
-
+
}
}
await GetData();
}
-
+
private async Task CreateJobs(Node node)
{
try
@@ -305,10 +305,10 @@
map = new JobDataMap();
map.Put("managedNodeId", node.Id.ToString());
-
+
var acceptorJob = SimpleJob.Create(map, node.Id.ToString());
await scheduler.ScheduleJob(acceptorJob.Job, acceptorJob.Trigger);
-
+
ToastService.ShowSuccess("Node subscription job created");
}
catch
diff --git a/src/Pages/Wallets.razor b/src/Pages/Wallets.razor
index 86a96cd4..d0272672 100644
--- a/src/Pages/Wallets.razor
+++ b/src/Pages/Wallets.razor
@@ -121,7 +121,7 @@
-
+
@context.CreationDatetime.Humanize()
diff --git a/src/Pages/Withdrawals.razor b/src/Pages/Withdrawals.razor
index b42d738f..e1bc7326 100644
--- a/src/Pages/Withdrawals.razor
+++ b/src/Pages/Withdrawals.razor
@@ -204,7 +204,7 @@
@context?.Status.Humanize()
-
+
@context.CreationDatetime.Humanize()
@@ -319,7 +319,7 @@
@context?.Status.Humanize()
-
+
@context.CreationDatetime.Humanize()
diff --git a/src/Services/CoinSelectionService.cs b/src/Services/CoinSelectionService.cs
index 75dc7c19..79ac209f 100644
--- a/src/Services/CoinSelectionService.cs
+++ b/src/Services/CoinSelectionService.cs
@@ -41,7 +41,14 @@ public interface ICoinSelectionService
///
///
///
- public Task LockUTXOs(List selectedUTXOs, IBitcoinRequest bitcoinRequest, IBitcoinRequestRepository bitcoinRequestRepository);
+ public Task LockUTXOs(List selectedUTXOs, IBitcoinRequest bitcoinRequest, BitcoinRequestType requestType);
+
+ ///
+ /// Gets the locked UTXOs from a request
+ ///
+ ///
+ ///
+ public Task> GetLockedUTXOsForRequest(IBitcoinRequest bitcoinRequest, BitcoinRequestType requestType);
public Task<(List coins, List selectedUTXOs)> GetTxInputCoins(
List availableUTXOs,
@@ -55,33 +62,65 @@ public class CoinSelectionService: ICoinSelectionService
private readonly IMapper _mapper;
private readonly IFMUTXORepository _fmutxoRepository;
private readonly INBXplorerService _nbXplorerService;
+ private readonly IChannelOperationRequestRepository _channelOperationRequestRepository;
+ private readonly IWalletWithdrawalRequestRepository _walletWithdrawalRequestRepository;
public CoinSelectionService(
ILogger logger,
IMapper mapper,
IFMUTXORepository fmutxoRepository,
- INBXplorerService nbXplorerService
+ INBXplorerService nbXplorerService,
+ IChannelOperationRequestRepository channelOperationRequestRepository,
+ IWalletWithdrawalRequestRepository walletWithdrawalRequestRepository
)
{
_logger = logger;
_mapper = mapper;
_fmutxoRepository = fmutxoRepository;
_nbXplorerService = nbXplorerService;
+ _channelOperationRequestRepository = channelOperationRequestRepository;
+ _walletWithdrawalRequestRepository = walletWithdrawalRequestRepository;
+ }
+
+ private IBitcoinRequestRepository GetRepository(BitcoinRequestType requestType)
+ {
+ return requestType switch
+ {
+ BitcoinRequestType.ChannelOperation => _channelOperationRequestRepository,
+ BitcoinRequestType.WalletWithdrawal => _walletWithdrawalRequestRepository,
+ _ => throw new NotImplementedException()
+ };
}
- public async Task LockUTXOs(List selectedUTXOs, IBitcoinRequest bitcoinRequest, IBitcoinRequestRepository bitcoinRequestRepository)
+ public async Task LockUTXOs(List selectedUTXOs, IBitcoinRequest bitcoinRequest, BitcoinRequestType requestType)
{
// We "lock" the PSBT to the channel operation request by adding to its UTXOs collection for later checking
var utxos = selectedUTXOs.Select(x => _mapper.Map(x)).ToList();
- var addUTXOSOperation = await bitcoinRequestRepository.AddUTXOs(bitcoinRequest, utxos);
- if (!addUTXOSOperation.Item1)
+ var addUTXOsOperation = await GetRepository(requestType).AddUTXOs(bitcoinRequest, utxos);
+ if (!addUTXOsOperation.Item1)
{
_logger.LogError(
$"Could not add the following utxos({utxos.Humanize()}) to op request:{bitcoinRequest.Id}");
}
}
+ public async Task> GetLockedUTXOsForRequest(IBitcoinRequest bitcoinRequest, BitcoinRequestType requestType)
+ {
+ var getUTXOsOperation = await GetRepository(requestType).GetUTXOs(bitcoinRequest);
+ if (!getUTXOsOperation.Item1)
+ {
+ _logger.LogError(
+ $"Could not get utxos from {requestType.ToString()} request:{bitcoinRequest.Id}");
+ return new();
+ }
+
+ // TODO: Convert from fmutxo to utxo by calling nbxplorer api with the list of txids
+ var lockedUTXOsList = getUTXOsOperation.Item2.Select(utxo => utxo.TxId);
+ var utxos = await _nbXplorerService.GetUTXOsAsync(bitcoinRequest.Wallet.GetDerivationStrategy());
+ return utxos.Confirmed.UTXOs.Where(utxo => lockedUTXOsList.Contains(utxo.Outpoint.Hash.ToString())).ToList();
+ }
+
public async Task> GetAvailableUTXOsAsync(DerivationStrategyBase derivationStrategy)
{
var lockedUTXOs = await _fmutxoRepository.GetLockedUTXOs();
@@ -118,9 +157,6 @@ public async Task> GetAvailableUTXOsAsync(DerivationStrategyBase deri
IBitcoinRequest request,
DerivationStrategyBase derivationStrategy)
{
- var utxoChanges = await _nbXplorerService.GetUTXOsAsync(derivationStrategy, default);
- utxoChanges.RemoveDuplicateUTXOs();
-
var satsAmount = request.SatsAmount;
var selectedUTXOs = await LightningHelper.SelectUTXOsByOldest(request.Wallet, satsAmount, availableUTXOs, _logger);
diff --git a/src/Services/LightningService.cs b/src/Services/LightningService.cs
index 4b12cfe4..ff697a21 100644
--- a/src/Services/LightningService.cs
+++ b/src/Services/LightningService.cs
@@ -17,6 +17,7 @@
*
*/
+using System.Runtime.InteropServices;
using FundsManager.Data.Models;
using FundsManager.Data.Repositories.Interfaces;
using Google.Protobuf;
@@ -27,7 +28,6 @@
using NBXplorer.DerivationStrategy;
using NBXplorer.Models;
using System.Security.Cryptography;
-using AutoMapper;
using FundsManager.Data;
using FundsManager.Helpers;
using Microsoft.EntityFrameworkCore;
@@ -116,9 +116,6 @@ public class LightningService : ILightningService
private readonly IChannelOperationRequestRepository _channelOperationRequestRepository;
private readonly INodeRepository _nodeRepository;
private readonly IDbContextFactory _dbContextFactory;
- private readonly IMapper _mapper;
- private readonly IWalletRepository _walletRepository;
- private readonly IFMUTXORepository _ifmutxoRepository;
private readonly IChannelOperationRequestPSBTRepository _channelOperationRequestPsbtRepository;
private readonly IChannelRepository _channelRepository;
private readonly IRemoteSignerService _remoteSignerService;
@@ -129,9 +126,6 @@ public LightningService(ILogger logger,
IChannelOperationRequestRepository channelOperationRequestRepository,
INodeRepository nodeRepository,
IDbContextFactory dbContextFactory,
- IMapper mapper,
- IWalletRepository walletRepository,
- IFMUTXORepository ifmutxoRepository,
IChannelOperationRequestPSBTRepository channelOperationRequestPsbtRepository,
IChannelRepository channelRepository,
IRemoteSignerService remoteSignerService,
@@ -144,9 +138,6 @@ ICoinSelectionService coinSelectionService
_channelOperationRequestRepository = channelOperationRequestRepository;
_nodeRepository = nodeRepository;
_dbContextFactory = dbContextFactory;
- _mapper = mapper;
- _walletRepository = walletRepository;
- _ifmutxoRepository = ifmutxoRepository;
_channelOperationRequestPsbtRepository = channelOperationRequestPsbtRepository;
_channelRepository = channelRepository;
_remoteSignerService = remoteSignerService;
@@ -194,12 +185,19 @@ public async Task OpenChannel(ChannelOperationRequest channelOperationRequest)
var combinedPSBT = GetCombinedPsbt(channelOperationRequest, _logger);
+
//32 bytes of secure randomness for the pending channel id (lnd)
var pendingChannelId = RandomNumberGenerator.GetBytes(32);
var pendingChannelIdHex = Convert.ToHexString(pendingChannelId);
try
{
+ var virtualSize=combinedPSBT.GetGlobalTransaction().GetVirtualSize()+22; //22 bytes for the 1 segwit output
+ var feeRateResult = await LightningHelper.GetFeeRateResult(network, _nbXplorerService);
+
+ var totalFees = new Money(virtualSize * feeRateResult.FeeRate.SatoshiPerByte, MoneyUnit.Satoshi);
+
+ long fundingAmount = channelOperationRequest.Changeless ? channelOperationRequest.SatsAmount - totalFees : channelOperationRequest.SatsAmount;
//We prepare the request (shim) with the base PSBT we had presigned with the UTXOs to fund the channel
var openChannelRequest = new OpenChannelRequest
{
@@ -212,7 +210,7 @@ public async Task OpenChannel(ChannelOperationRequest channelOperationRequest)
PendingChanId = ByteString.CopyFrom(pendingChannelId)
}
},
- LocalFundingAmount = channelOperationRequest.SatsAmount,
+ LocalFundingAmount = fundingAmount,
CloseAddress = closeAddress.Address.ToString(),
Private = channelOperationRequest.IsChannelPrivate,
NodePubkey = ByteString.CopyFrom(Convert.FromHexString(destination.PubKey)),
@@ -397,29 +395,32 @@ public async Task OpenChannel(ChannelOperationRequest channelOperationRequest)
};
var channelfundingTx = fundedPSBT.GetGlobalTransaction();
+ var totalOut = new Money(channelOperationRequest.SatsAmount, MoneyUnit.Satoshi);
- //We manually fix the change (it was wrong from the Base template due to nbitcoin requiring a change on a PSBT)
-
- var totalIn = new Money(0L);
-
- foreach (var input in fundedPSBT.Inputs)
+ if (!channelOperationRequest.Changeless)
{
- totalIn += (input.GetTxOut()?.Value);
+ if (fundedPSBT.TryGetVirtualSize(out var vsize))
+ {
+ var totalIn = fundedPSBT.Inputs.Sum(i => i.GetTxOut()?.Value);
+ //We manually fix the change (it was wrong from the Base template due to nbitcoin requiring a change on a PSBT)
+ var totalChangefulFees = new Money(vsize * feeRateResult.FeeRate.SatoshiPerByte, MoneyUnit.Satoshi);
+ var changeOutput = channelfundingTx.Outputs.SingleOrDefault(o => o.Value != channelOperationRequest.SatsAmount) ?? channelfundingTx.Outputs.First();
+ changeOutput.Value = totalIn - totalOut - totalChangefulFees;
+
+ //We merge changeFixedPSBT with the other PSBT with the change fixed
+ fundedPSBT = channelfundingTx.CreatePSBT(network).UpdateFrom(fundedPSBT);
+ }
+ else
+ {
+ throw new ExternalException("VSized could not be calculated for the funded PSBT, channel operation request id: {RequestId}", channelOperationRequest.Id);
+ }
}
- var totalOut = new Money(channelOperationRequest.SatsAmount, MoneyUnit.Satoshi);
- var totalFees = combinedPSBT.GetFee();
- channelfundingTx.Outputs[0].Value = totalIn - totalOut - totalFees;
-
- //We merge changeFixedPSBT with the other PSBT with the change fixed
-
- var changeFixedPSBT = channelfundingTx.CreatePSBT(network).UpdateFrom(fundedPSBT);
-
PSBT? finalSignedPSBT = null;
//We check the way the nodeguard signs, with the nodeguard remote signer or with the embedded signer
if (Constants.ENABLE_REMOTE_SIGNER)
{
- finalSignedPSBT = await _remoteSignerService.Sign(changeFixedPSBT);
+ finalSignedPSBT = await _remoteSignerService.Sign(fundedPSBT);
if (finalSignedPSBT == null)
{
const string errorMessage = "The signed PSBT was null, something went wrong while signing with the remote signer";
@@ -435,7 +436,7 @@ public async Task OpenChannel(ChannelOperationRequest channelOperationRequest)
derivationStrategyBase,
channelfundingTx,
network,
- changeFixedPSBT,
+ fundedPSBT,
_logger);
if (finalSignedPSBT == null)
@@ -529,11 +530,13 @@ public async Task OpenChannel(ChannelOperationRequest channelOperationRequest)
}
else
{
+ _logger.LogError("TX Check failed for channel operation request id: {RequestId} reason: {Reason}", channelOperationRequest.Id, checkTx);
CancelPendingChannel(source, pendingChannelId, client);
}
}
else
{
+ _logger.LogError("Could not parse the PSBT for funding channel operation request id: {RequestId}", channelOperationRequest.Id);
CancelPendingChannel(source, pendingChannelId, client);
}
@@ -552,7 +555,8 @@ public async Task OpenChannel(ChannelOperationRequest channelOperationRequest)
CancelPendingChannel(source, pendingChannelId, client);
- //TODO Mark as failed (?)
+ //TODO: We have to separate the exceptions between the ones that are retriable and the ones that are not
+ //TODO: and mark the channel operation request as failed automatically when they are not retriable
throw;
}
}
@@ -872,7 +876,8 @@ public void CancelPendingChannel(Node source, byte[] pendingChannelId, IUnmockab
}
}
- var availableUTXOs = await _coinSelectionService.GetAvailableUTXOsAsync(derivationStrategy);
+ var previouslyLockedUTXOs = await _coinSelectionService.GetLockedUTXOsForRequest(channelOperationRequest, BitcoinRequestType.ChannelOperation);
+ var availableUTXOs = previouslyLockedUTXOs.Count > 0 ? previouslyLockedUTXOs : await _coinSelectionService.GetAvailableUTXOsAsync(derivationStrategy);
var (multisigCoins, selectedUtxOs) = await _coinSelectionService.GetTxInputCoins(availableUTXOs, channelOperationRequest, derivationStrategy);
if (multisigCoins == null || !multisigCoins.Any())
@@ -910,15 +915,26 @@ public void CancelPendingChannel(Node source, byte[] pendingChannelId, IUnmockab
.SetChange(changeAddress.Address)
.SendEstimatedFees(feeRateResult.FeeRate);
- result.Item1 = builder.BuildPSBT(false);
+ var originalPSBT = builder.BuildPSBT(false);
+
+ //Hack to remove outputs
+ var combinedPsbTtx = originalPSBT.GetGlobalTransaction();
+ if (channelOperationRequest.Changeless)
+ {
+ combinedPsbTtx.Outputs.Clear();
+ }
+
+ result.Item1 = combinedPsbTtx.CreatePSBT(network);
+ //Hack to make sure that witness and non-witness UTXOs are added to the PSBT
//Hack, see https://github.com/MetacoSA/NBitcoin/issues/1112 for details
foreach (var input in result.Item1.Inputs)
{
+ input.WitnessUtxo = originalPSBT.Inputs.FirstOrDefault(x=> x.PrevOut == input.PrevOut)?.WitnessUtxo;
+ input.NonWitnessUtxo = originalPSBT.Inputs.FirstOrDefault(x=> x.PrevOut == input.PrevOut)?.NonWitnessUtxo;
input.SighashType = SigHash.None;
}
- //Additional fields to support PSBT signing with a HW or the Remote Signer
var psbt = LightningHelper.AddDerivationData(channelOperationRequest.Wallet, result.Item1, selectedUtxOs, multisigCoins, _logger);
result = (psbt, result.Item2);
}
@@ -927,7 +943,10 @@ public void CancelPendingChannel(Node source, byte[] pendingChannelId, IUnmockab
_logger.LogError(e, "Error while generating base PSBT");
}
- await _coinSelectionService.LockUTXOs(selectedUtxOs, channelOperationRequest, _channelOperationRequestRepository);
+ if (previouslyLockedUTXOs.Count == 0)
+ {
+ await _coinSelectionService.LockUTXOs(selectedUtxOs, channelOperationRequest, BitcoinRequestType.ChannelOperation);
+ }
// The template PSBT is saved for later reuse
if (result.Item1 != null)
diff --git a/src/Services/NBXplorerService.cs b/src/Services/NBXplorerService.cs
index ce65fe03..11e40332 100644
--- a/src/Services/NBXplorerService.cs
+++ b/src/Services/NBXplorerService.cs
@@ -40,8 +40,17 @@ public Task GetScanUTXOSetInformationAsync(DerivationStrate
CancellationToken cancellation = default(CancellationToken));
}
+public enum MempoolRecommendedFeesTypes
+{
+ EconomyFee,
+ FastestFee,
+ HourFee,
+ HalfHourFee,
+ CustomFee
+}
+
///