mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 01:43:50 +01:00
new feature: Wallet Receive Page (#1065)
* new feature: Wallet Receive Page closes #965 * Conserve addresses by waiting till address is spent before generating each run * fix tests * Filter by cryptocode before matching outpoint * fix build * fix edge case issue * use address in keypathinfo directly * rebase fixes * rebase fixes * remove duplicate code * fix messy condition * fixes * fix e2e * fix
This commit is contained in:
parent
4ac79a7ea3
commit
025da0261d
@ -415,7 +415,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.Quit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanManageWallet()
|
||||
{
|
||||
@ -427,16 +427,55 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// In this test, we try to spend from a manual seed. We import the xpub 49'/0'/0', then try to use the seed
|
||||
// to sign the transaction
|
||||
var mnemonic = s.GenerateWallet("BTC", "", true, false);
|
||||
s.GenerateWallet("BTC", "", true, false);
|
||||
|
||||
//let's test quickly the receive wallet page
|
||||
s.Driver.FindElement(By.Id("Wallets")).Click();
|
||||
s.Driver.FindElement(By.LinkText("Manage")).Click();
|
||||
s.Driver.FindElement(By.Id("WalletReceive")).Click();
|
||||
//generate a receiving address
|
||||
s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
|
||||
Assert.True(s.Driver.FindElement(By.ClassName("qr-container")).Displayed);
|
||||
var receiveAddr = s.Driver.FindElement(By.Id("vue-address")).GetAttribute("value");
|
||||
//unreserve
|
||||
s.Driver.FindElement(By.CssSelector("button[value=unreserve-current-address]")).Click();
|
||||
//generate it again, should be the same one as before as nothign got used in the meantime
|
||||
s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
|
||||
Assert.True(s.Driver.FindElement(By.ClassName("qr-container")).Displayed);
|
||||
Assert.Equal( receiveAddr, s.Driver.FindElement(By.Id("vue-address")).GetAttribute("value"));
|
||||
|
||||
//send money to addr and ensure it changed
|
||||
|
||||
var sess = await s.Server.ExplorerClient.CreateWebsocketNotificationSessionAsync();
|
||||
sess.ListenAllTrackedSource();
|
||||
var nextEvent = sess.NextEventAsync();
|
||||
s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create(receiveAddr, Network.RegTest),
|
||||
Money.Parse("0.1"));
|
||||
await nextEvent;
|
||||
await Task.Delay(200);
|
||||
s.Driver.Navigate().Refresh();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
|
||||
Assert.NotEqual( receiveAddr, s.Driver.FindElement(By.Id("vue-address")).GetAttribute("value"));
|
||||
receiveAddr = s.Driver.FindElement(By.Id("vue-address")).GetAttribute("value");
|
||||
//change the wallet and ensure old address is not there and generating a new one does not result in the prev one
|
||||
s.GoToStore(storeId.storeId);
|
||||
s.GenerateWallet("BTC", "", true, false);
|
||||
s.Driver.FindElement(By.Id("Wallets")).Click();
|
||||
s.Driver.FindElement(By.LinkText("Manage")).Click();
|
||||
s.Driver.FindElement(By.Id("WalletReceive")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
|
||||
Assert.NotEqual( receiveAddr, s.Driver.FindElement(By.Id("vue-address")).GetAttribute("value"));
|
||||
|
||||
|
||||
var invoiceId = s.CreateInvoice(storeId.storeId);
|
||||
var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
|
||||
var address = invoice.EntityToDTO().Addresses["BTC"];
|
||||
|
||||
|
||||
var result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
|
||||
Assert.True(result.IsWatchOnly);
|
||||
s.GoToStore(storeId.storeId);
|
||||
mnemonic = s.GenerateWallet("BTC", "", true, true);
|
||||
var mnemonic = s.GenerateWallet("BTC", "", true, true);
|
||||
|
||||
var root = new Mnemonic(mnemonic).DeriveExtKey();
|
||||
invoiceId = s.CreateInvoice(storeId.storeId);
|
||||
|
@ -9,6 +9,7 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
@ -267,6 +268,11 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
await _Repo.UpdateStore(store);
|
||||
_EventAggregator.Publish(new WalletChangedEvent()
|
||||
{
|
||||
WalletId = new WalletId(storeId, cryptoCode)
|
||||
});
|
||||
|
||||
if (willBeExcluded != wasExcluded)
|
||||
{
|
||||
var label = willBeExcluded ? "disabled" : "enabled";
|
||||
|
@ -58,6 +58,7 @@ namespace BTCPayServer.Controllers
|
||||
PaymentMethodHandlerDictionary paymentMethodHandlerDictionary,
|
||||
SettingsRepository settingsRepository,
|
||||
IAuthorizationService authorizationService,
|
||||
EventAggregator eventAggregator,
|
||||
CssThemeManager cssThemeManager)
|
||||
{
|
||||
_RateFactory = rateFactory;
|
||||
@ -74,6 +75,7 @@ namespace BTCPayServer.Controllers
|
||||
_settingsRepository = settingsRepository;
|
||||
_authorizationService = authorizationService;
|
||||
_CssThemeManager = cssThemeManager;
|
||||
_EventAggregator = eventAggregator;
|
||||
_NetworkProvider = networkProvider;
|
||||
_ExplorerProvider = explorerProvider;
|
||||
_FeeRateProvider = feeRateProvider;
|
||||
@ -100,6 +102,7 @@ namespace BTCPayServer.Controllers
|
||||
private readonly SettingsRepository _settingsRepository;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly CssThemeManager _CssThemeManager;
|
||||
private readonly EventAggregator _EventAggregator;
|
||||
|
||||
[TempData]
|
||||
public bool StoreNotConfigured
|
||||
|
@ -8,10 +8,12 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.ModelBinders;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.WalletViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Rates;
|
||||
@ -23,6 +25,7 @@ using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
@ -49,6 +52,8 @@ namespace BTCPayServer.Controllers
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly IFeeProviderFactory _feeRateProvider;
|
||||
private readonly BTCPayWalletProvider _walletProvider;
|
||||
private readonly WalletReceiveStateService _WalletReceiveStateService;
|
||||
private readonly EventAggregator _EventAggregator;
|
||||
public RateFetcher RateFetcher { get; }
|
||||
|
||||
CurrencyNameTable _currencyTable;
|
||||
@ -63,7 +68,9 @@ namespace BTCPayServer.Controllers
|
||||
IAuthorizationService authorizationService,
|
||||
ExplorerClientProvider explorerProvider,
|
||||
IFeeProviderFactory feeRateProvider,
|
||||
BTCPayWalletProvider walletProvider)
|
||||
BTCPayWalletProvider walletProvider,
|
||||
WalletReceiveStateService walletReceiveStateService,
|
||||
EventAggregator eventAggregator)
|
||||
{
|
||||
_currencyTable = currencyTable;
|
||||
Repository = repo;
|
||||
@ -77,6 +84,8 @@ namespace BTCPayServer.Controllers
|
||||
ExplorerClientProvider = explorerProvider;
|
||||
_feeRateProvider = feeRateProvider;
|
||||
_walletProvider = walletProvider;
|
||||
_WalletReceiveStateService = walletReceiveStateService;
|
||||
_EventAggregator = eventAggregator;
|
||||
}
|
||||
|
||||
// Borrowed from https://github.com/ManageIQ/guides/blob/master/labels.md
|
||||
@ -231,6 +240,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpGet]
|
||||
[Route("{walletId}")]
|
||||
[Route("{walletId}/transactions")]
|
||||
public async Task<IActionResult> WalletTransactions(
|
||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, string labelFilter = null)
|
||||
@ -294,6 +304,76 @@ namespace BTCPayServer.Controllers
|
||||
return $"{walletId}:{txId}";
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{walletId}/receive")]
|
||||
public IActionResult WalletReceive([ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, string statusMessage = null)
|
||||
{
|
||||
if (walletId?.StoreId == null)
|
||||
return NotFound();
|
||||
DerivationSchemeSettings paymentMethod = GetDerivationSchemeSettings(walletId);
|
||||
if (paymentMethod == null)
|
||||
return NotFound();
|
||||
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId?.CryptoCode);
|
||||
if (network == null)
|
||||
return NotFound();
|
||||
|
||||
var address = _WalletReceiveStateService.Get(walletId)?.Address;
|
||||
if (!string.IsNullOrEmpty(statusMessage))
|
||||
{
|
||||
TempData[WellKnownTempData.SuccessMessage] = statusMessage;
|
||||
}
|
||||
|
||||
return View(new WalletReceiveViewModel()
|
||||
{
|
||||
CryptoCode = walletId.CryptoCode,
|
||||
Address = address?.ToString(),
|
||||
CryptoImage = GetImage(paymentMethod.PaymentId, network)
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{walletId}/receive")]
|
||||
public async Task<IActionResult> WalletReceive([ModelBinder(typeof(WalletIdModelBinder))]
|
||||
WalletId walletId, WalletReceiveViewModel viewModel, string command)
|
||||
{
|
||||
if (walletId?.StoreId == null)
|
||||
return NotFound();
|
||||
DerivationSchemeSettings paymentMethod = GetDerivationSchemeSettings(walletId);
|
||||
if (paymentMethod == null)
|
||||
return NotFound();
|
||||
var network = this.NetworkProvider.GetNetwork<BTCPayNetwork>(walletId?.CryptoCode);
|
||||
if (network == null)
|
||||
return NotFound();
|
||||
var statusMessage = string.Empty;
|
||||
var wallet = _walletProvider.GetWallet(network);
|
||||
switch (command)
|
||||
{
|
||||
case "unreserve-current-address":
|
||||
KeyPathInformation cachedAddress = _WalletReceiveStateService.Get(walletId);
|
||||
if (cachedAddress == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
var address = cachedAddress.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork);
|
||||
ExplorerClientProvider.GetExplorerClient(network)
|
||||
.CancelReservation(cachedAddress.DerivationStrategy, new[] {cachedAddress.KeyPath});
|
||||
statusMessage = new StatusMessageModel()
|
||||
{
|
||||
AllowDismiss =true,
|
||||
Message = $"Address {address} was unreserved.",
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
}.ToString();
|
||||
_WalletReceiveStateService.Remove(walletId);
|
||||
break;
|
||||
case "generate-new-address":
|
||||
var reserve = (await wallet.ReserveAddressAsync(paymentMethod.AccountDerivation));
|
||||
_WalletReceiveStateService.Set(walletId, reserve);
|
||||
break;
|
||||
}
|
||||
return RedirectToAction(nameof(WalletReceive), new {walletId, statusMessage});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{walletId}/send")]
|
||||
public async Task<IActionResult> WalletSend(
|
||||
@ -957,6 +1037,23 @@ namespace BTCPayServer.Controllers
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private string GetImage(PaymentMethodId paymentMethodId, BTCPayNetwork network)
|
||||
{
|
||||
var res = paymentMethodId.PaymentType == PaymentTypes.BTCLike
|
||||
? Url.Content(network.CryptoImagePath)
|
||||
: Url.Content(network.LightningImagePath);
|
||||
return "/" + res;
|
||||
}
|
||||
}
|
||||
|
||||
public class WalletReceiveViewModel
|
||||
{
|
||||
public string CryptoImage { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string Address { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
10
BTCPayServer/Events/NewOnChainTransactionEvent.cs
Normal file
10
BTCPayServer/Events/NewOnChainTransactionEvent.cs
Normal file
@ -0,0 +1,10 @@
|
||||
using NBXplorer.Models;
|
||||
|
||||
namespace BTCPayServer.Events
|
||||
{
|
||||
public class NewOnChainTransactionEvent
|
||||
{
|
||||
public NewTransactionEvent NewTransactionEvent { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
}
|
||||
}
|
7
BTCPayServer/Events/WalletChangedEvent.cs
Normal file
7
BTCPayServer/Events/WalletChangedEvent.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace BTCPayServer.Events
|
||||
{
|
||||
public class WalletChangedEvent
|
||||
{
|
||||
public WalletId WalletId { get; set; }
|
||||
}
|
||||
}
|
51
BTCPayServer/HostedServices/WalletReceiveCacheUpdater.cs
Normal file
51
BTCPayServer/HostedServices/WalletReceiveCacheUpdater.cs
Normal file
@ -0,0 +1,51 @@
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using NBXplorer;
|
||||
|
||||
namespace BTCPayServer.HostedServices
|
||||
{
|
||||
public class WalletReceiveCacheUpdater : IHostedService
|
||||
{
|
||||
private readonly EventAggregator _EventAggregator;
|
||||
private readonly WalletReceiveStateService _WalletReceiveStateService;
|
||||
|
||||
private readonly CompositeDisposable _Leases = new CompositeDisposable();
|
||||
|
||||
public WalletReceiveCacheUpdater(EventAggregator eventAggregator,
|
||||
WalletReceiveStateService walletReceiveStateService)
|
||||
{
|
||||
_EventAggregator = eventAggregator;
|
||||
_WalletReceiveStateService = walletReceiveStateService;
|
||||
}
|
||||
|
||||
public Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_Leases.Add(_EventAggregator.Subscribe<WalletChangedEvent>(evt =>
|
||||
_WalletReceiveStateService.Remove(evt.WalletId)));
|
||||
|
||||
_Leases.Add(_EventAggregator.Subscribe<NewOnChainTransactionEvent>(evt =>
|
||||
{
|
||||
var matching = _WalletReceiveStateService
|
||||
.GetByDerivation(evt.CryptoCode, evt.NewTransactionEvent.DerivationStrategy).Where(pair =>
|
||||
evt.NewTransactionEvent.Outputs.Any(output => output.ScriptPubKey == pair.Value.ScriptPubKey));
|
||||
|
||||
foreach (var keyValuePair in matching)
|
||||
{
|
||||
_WalletReceiveStateService.Remove(keyValuePair.Key);
|
||||
}
|
||||
}));
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
_Leases.Dispose();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
@ -177,6 +177,7 @@ namespace BTCPayServer.Hosting
|
||||
services.TryAddSingleton<StoreRepository>();
|
||||
services.TryAddSingleton<PaymentRequestRepository>();
|
||||
services.TryAddSingleton<BTCPayWalletProvider>();
|
||||
services.TryAddSingleton<WalletReceiveStateService>();
|
||||
services.TryAddSingleton<CurrencyNameTable>();
|
||||
services.TryAddSingleton<IFeeProviderFactory>(o => new NBXplorerFeeProviderFactory(o.GetRequiredService<ExplorerClientProvider>())
|
||||
{
|
||||
@ -216,6 +217,7 @@ namespace BTCPayServer.Hosting
|
||||
services.AddSingleton<IHostedService, DynamicDnsHostedService>();
|
||||
services.AddSingleton<IHostedService, TorServicesHostedService>();
|
||||
services.AddSingleton<IHostedService, PaymentRequestStreamer>();
|
||||
services.AddSingleton<IHostedService, WalletReceiveCacheUpdater>();
|
||||
services.AddSingleton<IBackgroundJobClient, BackgroundJobClient>();
|
||||
services.AddScoped<IAuthorizationHandler, CookieAuthorizationHandler>();
|
||||
services.AddScoped<IAuthorizationHandler, OpenIdAuthorizationHandler>();
|
||||
|
@ -10,6 +10,7 @@ using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using NBitcoin;
|
||||
using NBXplorer.Models;
|
||||
|
||||
namespace BTCPayServer.Payments.Bitcoin
|
||||
{
|
||||
@ -35,7 +36,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
{
|
||||
public Task<FeeRate> GetFeeRate;
|
||||
public Task<FeeRate> GetNetworkFeeRate;
|
||||
public Task<BitcoinAddress> ReserveAddress;
|
||||
public Task<KeyPathInformation> ReserveAddress;
|
||||
}
|
||||
|
||||
public override void PreparePaymentModel(PaymentModel model, InvoiceResponse invoiceResponse,
|
||||
@ -141,7 +142,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
onchainMethod.NextNetworkFee = Money.Zero;
|
||||
break;
|
||||
}
|
||||
onchainMethod.DepositAddress = (await prepare.ReserveAddress).ToString();
|
||||
onchainMethod.DepositAddress = (await prepare.ReserveAddress).Address.ToString();
|
||||
return onchainMethod;
|
||||
}
|
||||
}
|
||||
|
@ -145,6 +145,11 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
break;
|
||||
case NBXplorer.Models.NewTransactionEvent evt:
|
||||
wallet.InvalidateCache(evt.DerivationStrategy);
|
||||
_Aggregator.Publish(new NewOnChainTransactionEvent()
|
||||
{
|
||||
CryptoCode = wallet.Network.CryptoCode,
|
||||
NewTransactionEvent = evt
|
||||
});
|
||||
foreach (var output in network.GetValidOutputs(evt))
|
||||
{
|
||||
var key = output.Item1.ScriptPubKey.Hash + "#" + network.CryptoCode.ToUpperInvariant();
|
||||
@ -373,7 +378,7 @@ namespace BTCPayServer.Payments.Bitcoin
|
||||
paymentMethod.Calculate().Due > Money.Zero)
|
||||
{
|
||||
var address = await wallet.ReserveAddressAsync(strategy);
|
||||
btc.DepositAddress = address.ToString();
|
||||
btc.DepositAddress = address.Address.ToString();
|
||||
await _InvoiceRepository.NewAddress(invoice.Id, btc, wallet.Network);
|
||||
_Aggregator.Publish(new InvoiceNewAddressEvent(invoice.Id, address.ToString(), wallet.Network));
|
||||
paymentMethod.SetPaymentMethodDetails(btc);
|
||||
|
@ -63,7 +63,7 @@ namespace BTCPayServer.Services.Wallets
|
||||
|
||||
public TimeSpan CacheSpan { get; private set; } = TimeSpan.FromMinutes(5);
|
||||
|
||||
public async Task<BitcoinAddress> ReserveAddressAsync(DerivationStrategyBase derivationStrategy)
|
||||
public async Task<KeyPathInformation> ReserveAddressAsync(DerivationStrategyBase derivationStrategy)
|
||||
{
|
||||
if (derivationStrategy == null)
|
||||
throw new ArgumentNullException(nameof(derivationStrategy));
|
||||
@ -74,8 +74,7 @@ namespace BTCPayServer.Services.Wallets
|
||||
await _Client.TrackAsync(derivationStrategy).ConfigureAwait(false);
|
||||
pathInfo = await _Client.GetUnusedAsync(derivationStrategy, DerivationFeature.Deposit, 0, true).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
return pathInfo.Address;
|
||||
return pathInfo;
|
||||
}
|
||||
|
||||
public async Task<(BitcoinAddress, KeyPath)> GetChangeAddressAsync(DerivationStrategyBase derivationStrategy)
|
||||
|
44
BTCPayServer/Services/Wallets/WalletReceiveStateService.cs
Normal file
44
BTCPayServer/Services/Wallets/WalletReceiveStateService.cs
Normal file
@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NBitcoin;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
|
||||
namespace BTCPayServer.Services.Wallets
|
||||
{
|
||||
public class WalletReceiveStateService
|
||||
{
|
||||
private readonly ConcurrentDictionary<WalletId, KeyPathInformation> _walletReceiveState =
|
||||
new ConcurrentDictionary<WalletId, KeyPathInformation>();
|
||||
|
||||
public void Remove(WalletId walletId)
|
||||
{
|
||||
_walletReceiveState.TryRemove(walletId, out _);
|
||||
}
|
||||
|
||||
public KeyPathInformation Get(WalletId walletId)
|
||||
{
|
||||
if (_walletReceiveState.ContainsKey(walletId))
|
||||
{
|
||||
return _walletReceiveState[walletId];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Set(WalletId walletId, KeyPathInformation information)
|
||||
{
|
||||
_walletReceiveState.AddOrReplace(walletId, information);
|
||||
}
|
||||
|
||||
public IEnumerable<KeyValuePair<WalletId, KeyPathInformation>> GetByDerivation(string cryptoCode,
|
||||
DerivationStrategyBase derivationStrategyBase)
|
||||
{
|
||||
return _walletReceiveState.Where(pair =>
|
||||
pair.Key.CryptoCode.Equals(cryptoCode, StringComparison.InvariantCulture) &&
|
||||
pair.Value.DerivationStrategy == derivationStrategyBase);
|
||||
}
|
||||
}
|
||||
}
|
121
BTCPayServer/Views/Wallets/WalletReceive.cshtml
Normal file
121
BTCPayServer/Views/Wallets/WalletReceive.cshtml
Normal file
@ -0,0 +1,121 @@
|
||||
@using Microsoft.AspNetCore.Mvc.ModelBinding
|
||||
@model BTCPayServer.Controllers.WalletReceiveViewModel
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData["Title"] = "Manage wallet";
|
||||
ViewData.SetActivePageAndTitle(WalletsNavPages.Receive);
|
||||
}
|
||||
|
||||
<div class="row no-gutters">
|
||||
<div class="col-lg-6 mx-auto my-auto ">
|
||||
<form method="post" asp-action="WalletReceive" class="card text-center">
|
||||
@if (string.IsNullOrEmpty(Model.Address))
|
||||
{
|
||||
|
||||
<h2 class="card-title">Receive @Model.CryptoCode</h2>
|
||||
<button class="btn btn-lg btn-primary m-2" type="submit" name="command" value="generate-new-address">Generate @Model.CryptoCode address</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<h2 class="card-title">Next available @Model.CryptoCode address</h2>
|
||||
<noscript>
|
||||
<div class="card-body m-sm-0 p-sm-0">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control " readonly="readonly" asp-for="Address" id="address"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text fa fa-copy"> </span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" name="command" value="unreserve-current-address" class="btn btn-link">Unreserve this address</button>
|
||||
|
||||
</div>
|
||||
</noscript>
|
||||
<div class="only-for-js card-body m-sm-0 p-sm-0" id="app">
|
||||
<div class="qr-container mb-2">
|
||||
<img v-bind:src="srvModel.cryptoImage" class="qr-icon" />
|
||||
<qrcode v-bind:value="srvModel.address" :options="{ width: 256, margin: 0, color: {dark:'#000', light:'#fff'} }" tag="svg">
|
||||
</qrcode>
|
||||
</div>
|
||||
<div class="input-group copy" data-clipboard-target="#vue-address">
|
||||
<input type="text" class=" form-control " readonly="readonly" :value="srvModel.address" id="vue-address"/>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text fa fa-copy"> </span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" name="command" value="unreserve-current-address" class="btn btn-link">Unreserve this address</button>
|
||||
|
||||
</div>
|
||||
}
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section HeadScripts
|
||||
|
||||
{
|
||||
<script src="~/bundles/lightning-node-info-bundle.min.js" type="text/javascript"></script>
|
||||
<script type="text/javascript">
|
||||
var srvModel = @Safe.Json(Model);
|
||||
window.onload = function() {
|
||||
if($("#app").length <1){
|
||||
return;
|
||||
}
|
||||
Vue.use(Toasted);
|
||||
var app = new Vue({
|
||||
el: '#app',
|
||||
components: {
|
||||
qrcode: VueQrcode
|
||||
},
|
||||
data: {
|
||||
srvModel: srvModel
|
||||
},
|
||||
mounted: function() {
|
||||
|
||||
this.$nextTick(function() {
|
||||
var copyInput = new Clipboard('.copy');
|
||||
|
||||
copyInput.on("success",
|
||||
function(e) {
|
||||
Vue.toasted.show('Copied',
|
||||
{
|
||||
iconPack: "fontawesome",
|
||||
icon: "copy",
|
||||
duration: 5000
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.qr-icon {
|
||||
height: 64px;
|
||||
width: 64px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.qr-container {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.qr-container svg {
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
}
|
||||
|
||||
.copy {
|
||||
cursor: copy;
|
||||
}
|
||||
</style>
|
||||
}
|
@ -11,6 +11,7 @@ namespace BTCPayServer.Views.Wallets
|
||||
Transactions,
|
||||
Rescan,
|
||||
PSBT,
|
||||
Settings
|
||||
Settings,
|
||||
Receive
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,7 @@
|
||||
{
|
||||
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Send)" asp-action="WalletSend" asp-route-walletId="@this.Context.GetRouteValue("walletId")" id="WalletSend">Send</a>
|
||||
}
|
||||
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Receive)" asp-action="WalletReceive" asp-route-walletId="@this.Context.GetRouteValue("walletId")" id="WalletReceive">Receive</a>
|
||||
<a class="nav-link @ViewData.IsActivePage(WalletsNavPages.Rescan)" asp-action="WalletRescan" asp-route-walletId="@this.Context.GetRouteValue("walletId")" id="WalletRescan">Rescan</a>
|
||||
@if (!network.ReadonlyWallet)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user