|
66 | 66 | </Tooltip> |
67 | 67 | </FieldLabel> |
68 | 68 | <div class="d-flex"> |
69 | | - <NumericPicker TValue="int" |
| 69 | + <NumericPicker TValue="decimal" |
70 | 70 | @bind-Value="_maxRoutingFeesPercent" |
71 | 71 | CurrencySymbolPlacement="CurrencySymbolPlacement.Suffix" |
72 | 72 | CurrencySymbol="%" |
| 73 | + Decimals="2" |
| 74 | + Step="0.1m" |
73 | 75 | Min="0" |
74 | 76 | Max="100"> |
75 | 77 | </NumericPicker> |
|
122 | 124 | @bind-Value="_maxMinerFeesPercent" |
123 | 125 | CurrencySymbolPlacement="CurrencySymbolPlacement.Suffix" |
124 | 126 | CurrencySymbol="%" |
| 127 | + Decimals="2" |
| 128 | + Step="0.1m" |
125 | 129 | Min="0" |
126 | 130 | Max="100"> |
127 | 131 | </NumericPicker> |
|
132 | 136 | } |
133 | 137 | @Math.Round(amount, 8) BTC (~@Math.Round(PriceConversionService.BtcToUsdConversion(amount, _btcPrice), 2) USD) |
134 | 138 | </FieldHelp> |
| 139 | + <FieldHelp Style="color: red !important;">Max miner fee is calculated automatically, only change the value if you want to override the default</FieldHelp> |
135 | 140 | </Field> |
136 | 141 | <Field> |
137 | 142 | <FieldLabel>Max Service Fees</FieldLabel> |
138 | 143 | <div class="d-flex"> |
139 | 144 | <NumericPicker TValue="decimal" |
140 | | - @bind-Value="_maxServiceFeesBtc" |
| 145 | + @bind-Value="_maxServiceFeesPercent" |
141 | 146 | CurrencySymbolPlacement="CurrencySymbolPlacement.Suffix" |
142 | | - CurrencySymbol=" BTC" |
143 | | - Decimals="8" |
144 | | - Step="0.00001000m" |
145 | | - Min="0.00001000m"> |
| 147 | + CurrencySymbol="%" |
| 148 | + Decimals="2" |
| 149 | + Step="0.1m" |
| 150 | + Min="0" |
| 151 | + Max="100"> |
146 | 152 | </NumericPicker> |
147 | 153 | </div> |
148 | 154 | <FieldHelp> |
149 | | - $@Math.Round(PriceConversionService.BtcToUsdConversion(_maxServiceFeesBtc, _btcPrice), 2) |
| 155 | + @{ |
| 156 | + var amount = _maxServiceFeesPercent * _amountBtc / 100; |
| 157 | + } |
| 158 | + @Math.Round(amount, 8) BTC (~@Math.Round(PriceConversionService.BtcToUsdConversion(amount, _btcPrice), 2) USD) |
150 | 159 | </FieldHelp> |
151 | 160 | </Field> |
152 | 161 | <Field> |
|
205 | 214 | private SwapDirection _selectedDirection = SwapDirection.Out; |
206 | 215 | private SwapProvider _selectedProvider = SwapProvider.Loop; |
207 | 216 | private decimal _amountBtc = 0.0025m; |
208 | | - private decimal _maxServiceFeesBtc = 0.00001m; |
| 217 | + private decimal _maxServiceFeesPercent = 0.1m; |
209 | 218 | private List<Wallet> _availableWallets = []; |
210 | 219 | private Wallet? _selectedWallet; |
211 | 220 | private string? _amountInWalletBtc; |
|
217 | 226 | private bool _showAdvancedOptions = false; |
218 | 227 | private int _swapPublicationDeadlineOffsetMinutes = Constants.IS_DEV_ENVIRONMENT ? 1 : 30; |
219 | 228 | private int _maxMinerFeesPercent = 0; |
220 | | - private int _maxRoutingFeesPercent = 1; |
| 229 | + private decimal _maxRoutingFeesPercent = 0.5m; |
221 | 230 | private int _sweepConfirmationTarget = Constants.IS_DEV_ENVIRONMENT ? 250 : 400; |
222 | 231 |
|
223 | | - private CancellationTokenSource? _cancellationTokenSource; |
224 | | - protected CancellationToken ComponentCancellationToken => (_cancellationTokenSource ??= new()).Token; |
225 | | - |
226 | 232 | private string FormatBtcWithUsd(decimal btcAmount) |
227 | 233 | { |
228 | 234 | var usdAmount = Math.Round(PriceConversionService.BtcToUsdConversion(btcAmount, _btcPrice), 2); |
|
264 | 270 | var swapRequest = new SwapOutRequest |
265 | 271 | { |
266 | 272 | Amount = amount, |
267 | | - MaxServiceFees = NBitcoin.Money.FromUnit(_maxServiceFeesBtc, NBitcoin.MoneyUnit.BTC), |
| 273 | + MaxServiceFeesPercent = _maxServiceFeesPercent, |
268 | 274 | PrepayAmtSat = _confirmationModal.GetData<long>("PrepayAmtSat"), |
269 | 275 | Address = btcAddress.Address.ToString(), |
270 | 276 | SwapPublicationDeadlineMinutes = _swapPublicationDeadlineOffsetMinutes, |
|
316 | 322 | } |
317 | 323 | catch (RpcException ex) |
318 | 324 | { |
| 325 | + Logger.LogError(ex, "Error creating swap"); |
319 | 326 | ToastService.ShowError(ex.Status.Detail); |
320 | 327 | } |
321 | 328 | catch (Exception ex) |
|
334 | 341 |
|
335 | 342 | private async Task ShowConfirmationModal() |
336 | 343 | { |
| 344 | + var HTLC_SWEEP_FEE_SAT_ON_QUOTE_ERROR = _amountBtc * EnvironmentHelpers.GetOrDefault("HTLC_SWEEP_FEE_SAT_ON_QUOTE_ERROR", 0.015m); |
| 345 | + var PREPAY_AMT_SAT_ON_QUOTE_ERROR = _amountBtc * EnvironmentHelpers.GetOrDefault("PREPAY_AMT_SAT_ON_QUOTE_ERROR", 0.005m); |
| 346 | + |
| 347 | + var middleChunkMessage = $"<strong>- Provider:</strong> {_selectedProvider}<br>" + |
| 348 | + $"<strong>- Direction:</strong> {_selectedDirection}<br>" + |
| 349 | + $"<strong>- Amount:</strong> {FormatBtcWithUsd(_amountBtc)}<br>" + |
| 350 | + $"<strong>- Origin Node:</strong> {_selectedNode?.Name}<br>" + |
| 351 | + $"<strong>- Destination Wallet:</strong> {_selectedWallet?.Name}<br><br>"; |
| 352 | + |
337 | 353 | ArgumentNullException.ThrowIfNull(_selectedWallet, nameof(_selectedWallet)); |
338 | 354 | ArgumentNullException.ThrowIfNull(_selectedNode, nameof(_selectedNode)); |
| 355 | + |
| 356 | + var showErrorModal = async (string errorMessage) => { |
| 357 | + _confirmationModalBody = "Quote was not successful because of the following error: <br><br>" + |
| 358 | + $"<span style='color: red;'>{errorMessage}</span>" + "<br><br>" + |
| 359 | + "The swap will be created with the following details:<br><br>" + |
| 360 | + middleChunkMessage + |
| 361 | + "<span style='color: red'>The fees could not be estimated, the swap might not go through.</span><br><br>" + |
| 362 | + "<br>Please confirm that you want to proceed."; |
| 363 | + |
| 364 | + var data = new Dictionary<string, object> |
| 365 | + { |
| 366 | + { "HtlcSweepFeeSat", HTLC_SWEEP_FEE_SAT_ON_QUOTE_ERROR }, |
| 367 | + { "PrepayAmtSat", PREPAY_AMT_SAT_ON_QUOTE_ERROR } |
| 368 | + }; |
| 369 | + |
| 370 | + await _confirmationModal.ShowModal(data); |
| 371 | + }; |
| 372 | + |
339 | 373 | try |
340 | 374 | { |
341 | 375 | var request = new SwapOutQuoteRequest{ |
|
348 | 382 | var offChainFee = new Money(quote.OffChainFees); |
349 | 383 | var onChainFee = new Money(quote.OnChainFees); |
350 | 384 | var totalFees = new Money(quote.ServiceFees + quote.OffChainFees + quote.OnChainFees); |
351 | | - |
| 385 | + |
| 386 | + var selectedServiceFee = _maxServiceFeesPercent * _amountBtc / 100; |
| 387 | + var serviceFeeChunkMessage = $"<li><strong>Service Fee:</strong> {FormatBtcWithUsd(serviceFee.ToUnit(MoneyUnit.BTC))}</li>"; |
| 388 | + if (serviceFee.ToUnit(MoneyUnit.BTC) > selectedServiceFee) { |
| 389 | + serviceFeeChunkMessage += $"<li style='list-style-type: none;'><span style='color: orange;'> (You selected: {FormatBtcWithUsd(selectedServiceFee)}, it might not go through)</span></li>"; |
| 390 | + } |
| 391 | + |
| 392 | + var selectedRoutingFee = _maxRoutingFeesPercent * _amountBtc / 100; |
| 393 | + var routingFeeChunkMessage = $"<li><strong>Off-Chain Fee:</strong> {FormatBtcWithUsd(offChainFee.ToUnit(MoneyUnit.BTC))}</li>"; |
| 394 | + if (offChainFee.ToUnit(MoneyUnit.BTC) > selectedRoutingFee) { |
| 395 | + routingFeeChunkMessage += $"<li style='list-style-type: none;'><span style='color: orange;'> (You selected: {FormatBtcWithUsd(selectedRoutingFee)}, it might not go through)</span></li>"; |
| 396 | + } |
| 397 | + |
352 | 398 | _confirmationModalBody = $"You are about to create a new swap with the following details:<br><br>" + |
353 | | - $"<strong>- Provider:</strong> {_selectedProvider}<br>" + |
354 | | - $"<strong>- Direction:</strong> {_selectedDirection}<br>" + |
355 | | - $"<strong>- Amount:</strong> {FormatBtcWithUsd(_amountBtc)}<br>" + |
| 399 | + middleChunkMessage + |
356 | 400 | $"<strong>- Estimated Fees:</strong><br>" + |
357 | | - $" <strong>• Service Fee:</strong> {FormatBtcWithUsd(serviceFee.ToUnit(MoneyUnit.BTC))}<br>" + |
358 | | - $" <strong>• Off-Chain Fee:</strong> {FormatBtcWithUsd(offChainFee.ToUnit(MoneyUnit.BTC))}<br>" + |
359 | | - $" <strong>• On-Chain Fee:</strong> {FormatBtcWithUsd(onChainFee.ToUnit(MoneyUnit.BTC))}<br>" + |
360 | | - $" <strong>• Total:</strong> {FormatBtcWithUsd(totalFees.ToUnit(MoneyUnit.BTC))}<br>" + |
361 | | - $"<strong>- Origin Node:</strong> {_selectedNode?.Name}<br>" + |
362 | | - $"<strong>- Destination Wallet:</strong> {_selectedWallet?.Name}<br><br>" + |
| 401 | + "<ul>"+ |
| 402 | + serviceFeeChunkMessage + |
| 403 | + routingFeeChunkMessage + |
| 404 | + $"<li><strong>On-Chain Fee:</strong> {FormatBtcWithUsd(onChainFee.ToUnit(MoneyUnit.BTC))}</li>" + |
| 405 | + $"<li><strong>Total:</strong> {FormatBtcWithUsd(totalFees.ToUnit(MoneyUnit.BTC))}</li>" + |
| 406 | + "<ul>"+ |
363 | 407 | "<br>" + |
364 | | - $"<strong>- Estimated total cost:</strong> {FormatBtcWithUsd((Money.FromUnit(_amountBtc, MoneyUnit.BTC) + totalFees).ToUnit(MoneyUnit.BTC))}<br>"; |
| 408 | + $"<strong>- Estimated total cost:</strong> {FormatBtcWithUsd(totalFees.ToUnit(MoneyUnit.BTC))} (Percentage of total swap: {Math.Round(totalFees.ToUnit(MoneyUnit.BTC) / _amountBtc * 100, 2)}%)<br>"; |
365 | 409 |
|
366 | 410 | if (!quote.CouldEstimateRoutingFees) |
367 | 411 | { |
368 | | - _confirmationModalBody += "<br>" + $"<span style='color: orange;'><strong>Warning:</strong> Routing fees could not be estimated. The actual fees might be higher than the estimated ones.</span><br><br>"; |
| 412 | + _confirmationModalBody += "<br>" + $"<span style='color: orange;'><strong>Warning:</strong> Routing fees could not be estimated. The actual fees will be higher than 0.</span><br><br>"; |
369 | 413 | } |
370 | 414 |
|
371 | 415 | _confirmationModalBody += $"<br>Please confirm that you want to proceed."; |
|
379 | 423 | catch (RpcException ex) |
380 | 424 | { |
381 | 425 | Logger.LogError(ex, "Error getting swap quote"); |
382 | | - ToastService.ShowError(ex.Status.Detail); |
| 426 | + await showErrorModal(ex.Status.Detail); |
383 | 427 | } |
384 | 428 | catch (Exception ex) |
385 | 429 | { |
386 | 430 | Logger.LogError(ex, "Error getting swap quote"); |
387 | | - ToastService.ShowError("Failed to get swap quote, please try again later"); |
| 431 | + await showErrorModal(ex.Message); |
388 | 432 | } |
389 | 433 | } |
390 | 434 |
|
|
0 commit comments