wabiupdate

This commit is contained in:
Kukks
2023-09-28 16:11:03 +02:00
parent 61c2817435
commit c738a1b053
19 changed files with 372 additions and 175 deletions

View File

@@ -27,8 +27,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.FixedF
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.LiquidPlus", "Plugins\BTCPayServer.Plugins.LiquidPlus\BTCPayServer.Plugins.LiquidPlus.csproj", "{B4E2ED08-4AD3-4648-8BDB-3107200460B9}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.LiquidPlus", "Plugins\BTCPayServer.Plugins.LiquidPlus\BTCPayServer.Plugins.LiquidPlus.csproj", "{B4E2ED08-4AD3-4648-8BDB-3107200460B9}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.NFC", "Plugins\BTCPayServer.Plugins.NFC\BTCPayServer.Plugins.NFC.csproj", "{71885A5E-1B00-4676-9566-D81AAE37406C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.SideShift", "Plugins\BTCPayServer.Plugins.SideShift\BTCPayServer.Plugins.SideShift.csproj", "{5E1BAA06-7828-47BC-89D6-19C2A78EA427}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.SideShift", "Plugins\BTCPayServer.Plugins.SideShift\BTCPayServer.Plugins.SideShift.csproj", "{5E1BAA06-7828-47BC-89D6-19C2A78EA427}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.TicketTailor", "Plugins\BTCPayServer.Plugins.TicketTailor\BTCPayServer.Plugins.TicketTailor.csproj", "{7AFC20EB-1696-47D7-8E57-822B05DD18F2}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BTCPayServer.Plugins.TicketTailor", "Plugins\BTCPayServer.Plugins.TicketTailor\BTCPayServer.Plugins.TicketTailor.csproj", "{7AFC20EB-1696-47D7-8E57-822B05DD18F2}"
@@ -151,14 +149,6 @@ Global
{B4E2ED08-4AD3-4648-8BDB-3107200460B9}.Altcoins-Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU {B4E2ED08-4AD3-4648-8BDB-3107200460B9}.Altcoins-Debug|Any CPU.Build.0 = Altcoins-Debug|Any CPU
{B4E2ED08-4AD3-4648-8BDB-3107200460B9}.Altcoins-Release|Any CPU.ActiveCfg = Altcoins-Release|Any CPU {B4E2ED08-4AD3-4648-8BDB-3107200460B9}.Altcoins-Release|Any CPU.ActiveCfg = Altcoins-Release|Any CPU
{B4E2ED08-4AD3-4648-8BDB-3107200460B9}.Altcoins-Release|Any CPU.Build.0 = Altcoins-Release|Any CPU {B4E2ED08-4AD3-4648-8BDB-3107200460B9}.Altcoins-Release|Any CPU.Build.0 = Altcoins-Release|Any CPU
{71885A5E-1B00-4676-9566-D81AAE37406C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{71885A5E-1B00-4676-9566-D81AAE37406C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{71885A5E-1B00-4676-9566-D81AAE37406C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{71885A5E-1B00-4676-9566-D81AAE37406C}.Release|Any CPU.Build.0 = Release|Any CPU
{71885A5E-1B00-4676-9566-D81AAE37406C}.Altcoins-Debug|Any CPU.ActiveCfg = Debug|Any CPU
{71885A5E-1B00-4676-9566-D81AAE37406C}.Altcoins-Debug|Any CPU.Build.0 = Debug|Any CPU
{71885A5E-1B00-4676-9566-D81AAE37406C}.Altcoins-Release|Any CPU.ActiveCfg = Release|Any CPU
{71885A5E-1B00-4676-9566-D81AAE37406C}.Altcoins-Release|Any CPU.Build.0 = Release|Any CPU
{5E1BAA06-7828-47BC-89D6-19C2A78EA427}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5E1BAA06-7828-47BC-89D6-19C2A78EA427}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5E1BAA06-7828-47BC-89D6-19C2A78EA427}.Debug|Any CPU.Build.0 = Debug|Any CPU {5E1BAA06-7828-47BC-89D6-19C2A78EA427}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5E1BAA06-7828-47BC-89D6-19C2A78EA427}.Release|Any CPU.ActiveCfg = Release|Any CPU {5E1BAA06-7828-47BC-89D6-19C2A78EA427}.Release|Any CPU.ActiveCfg = Release|Any CPU

View File

@@ -2,7 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net6.0</TargetFramework>
<LangVersion>10</LangVersion> <LangVersion>11</LangVersion>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Platforms>AnyCPU</Platforms> <Platforms>AnyCPU</Platforms>
</PropertyGroup> </PropertyGroup>
@@ -13,7 +13,7 @@
<PropertyGroup> <PropertyGroup>
<Product>Wabisabi Coinjoin</Product> <Product>Wabisabi Coinjoin</Product>
<Description>Allows you to integrate your btcpayserver store with coinjoins.</Description> <Description>Allows you to integrate your btcpayserver store with coinjoins.</Description>
<Version>1.0.55</Version> <Version>1.0.56</Version>
</PropertyGroup> </PropertyGroup>
<!-- Plugin development properties --> <!-- Plugin development properties -->
@@ -43,7 +43,7 @@
</ProjectReference> </ProjectReference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="NNostr.Client" Version="0.0.34" /> <PackageReference Include="NNostr.Client" Version="0.0.37" />
<PackageReference Include="WabiSabi" Version="1.0.1.2" /> <PackageReference Include="WabiSabi" Version="1.0.1.2" />
</ItemGroup> </ItemGroup>
<Target Name="DeleteExampleFile" AfterTargets="Publish"> <Target Name="DeleteExampleFile" AfterTargets="Publish">

View File

@@ -15,6 +15,7 @@ using BTCPayServer.Payments.PayJoin;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using BTCPayServer.Services.Wallets; using BTCPayServer.Services.Wallets;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NBitcoin; using NBitcoin;
using NBXplorer; using NBXplorer;
@@ -53,7 +54,8 @@ public class BTCPayWallet : IWallet, IDestinationProvider
public readonly ILogger Logger; public readonly ILogger Logger;
public static readonly BlockchainAnalyzer BlockchainAnalyzer = new(); public static readonly BlockchainAnalyzer BlockchainAnalyzer = new();
public BTCPayWallet(WalletRepository walletRepository, public BTCPayWallet(
WalletRepository walletRepository,
BTCPayNetworkProvider btcPayNetworkProvider, BTCPayNetworkProvider btcPayNetworkProvider,
BitcoinLikePayoutHandler bitcoinLikePayoutHandler, BitcoinLikePayoutHandler bitcoinLikePayoutHandler,
BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings, BTCPayNetworkJsonSerializerSettings btcPayNetworkJsonSerializerSettings,
@@ -66,7 +68,8 @@ public class BTCPayWallet : IWallet, IDestinationProvider
WabisabiStoreSettings wabisabiStoreSettings, WabisabiStoreSettings wabisabiStoreSettings,
IUTXOLocker utxoLocker, IUTXOLocker utxoLocker,
ILoggerFactory loggerFactory, ILoggerFactory loggerFactory,
StoreRepository storeRepository) StoreRepository storeRepository,
IMemoryCache memoryCache)
{ {
KeyChain = keyChain; KeyChain = keyChain;
_walletRepository = walletRepository; _walletRepository = walletRepository;
@@ -81,6 +84,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
WabisabiStoreSettings = wabisabiStoreSettings; WabisabiStoreSettings = wabisabiStoreSettings;
UtxoLocker = utxoLocker; UtxoLocker = utxoLocker;
_storeRepository = storeRepository; _storeRepository = storeRepository;
_memoryCache = memoryCache;
Logger = loggerFactory.CreateLogger($"BTCPayWallet_{storeId}"); Logger = loggerFactory.CreateLogger($"BTCPayWallet_{storeId}");
} }
@@ -147,6 +151,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
private IRoundCoinSelector _coinSelector; private IRoundCoinSelector _coinSelector;
public Smartifier _smartifier => (KeyChain as BTCPayKeyChain)?.Smartifier; public Smartifier _smartifier => (KeyChain as BTCPayKeyChain)?.Smartifier;
private readonly StoreRepository _storeRepository; private readonly StoreRepository _storeRepository;
private readonly IMemoryCache _memoryCache;
public IRoundCoinSelector GetCoinSelector() public IRoundCoinSelector GetCoinSelector()
{ {
@@ -492,7 +497,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
}))}, "utxo"); }))}, "utxo");
} }
_smartifier.Transactions.AddOrReplace(txHash, Task.FromResult(smartTx)); _smartifier.SmartTransactions.AddOrReplace(txHash, Task.FromResult(smartTx));
// //
// var kp = await ExplorerClient.GetMetadataAsync<RootedKeyPath>(DerivationScheme, // var kp = await ExplorerClient.GetMetadataAsync<RootedKeyPath>(DerivationScheme,
// WellknownMetadataKeys.AccountKeyPath); // WellknownMetadataKeys.AccountKeyPath);
@@ -552,6 +557,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
stopwatch.Stop(); stopwatch.Stop();
Logger.LogInformation($"Registered coinjoin result for {StoreId} in {stopwatch.Elapsed}"); Logger.LogInformation($"Registered coinjoin result for {StoreId} in {stopwatch.Elapsed}");
_memoryCache.Remove(WabisabiService.GetCacheKey(StoreId) + "cjhistory");
} }
catch (Exception e) catch (Exception e)

View File

@@ -97,7 +97,7 @@ Reputation risks: as the coordinator, the user may be associated with illegal ac
else else
{ {
vm.UriToAdvertise = Request.GetAbsoluteRootUri(); vm.UriToAdvertise = Request.GetAbsoluteRootUri();
TempData["SuccessMessage"] = $"Will create nostr events that point to ${ vm.UriToAdvertise }"; TempData["SuccessMessage"] = $"Will create nostr events that point to { vm.UriToAdvertise }";
await _wabisabiCoordinatorService.UpdateSettings( vm); await _wabisabiCoordinatorService.UpdateSettings( vm);
return RedirectToAction(nameof(UpdateWabisabiSettings)); return RedirectToAction(nameof(UpdateWabisabiSettings));
} }

View File

@@ -195,9 +195,7 @@ public class WabisabiCoordinatorService : PeriodicRunner
var coordinatorParameters = var coordinatorParameters =
new CoordinatorParameters(Path.Combine(_dataDirectories.Value.DataDir, "Plugins", "Coinjoin")); new CoordinatorParameters(Path.Combine(_dataDirectories.Value.DataDir, "Plugins", "Coinjoin"));
var coinJoinIdStore = var coinJoinIdStore =
CoinJoinIdStore.Create( CoinJoinIdStore.Create( coordinatorParameters.CoinJoinIdStoreFilePath);
Path.Combine(coordinatorParameters.ApplicationDataDir, "CcjCoordinator",
$"CoinJoins{explorerClient.Network}.txt"), coordinatorParameters.CoinJoinIdStoreFilePath);
var coinJoinScriptStore = CoinJoinScriptStore.LoadFromFile(coordinatorParameters.CoinJoinScriptStoreFilePath); var coinJoinScriptStore = CoinJoinScriptStore.LoadFromFile(coordinatorParameters.CoinJoinScriptStoreFilePath);
var rpc = new BtcPayRpcClient(explorerClient.RPCClient, _memoryCache, explorerClient); var rpc = new BtcPayRpcClient(explorerClient.RPCClient, _memoryCache, explorerClient);

View File

@@ -1,12 +1,18 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace BTCPayServer.Plugins.Wabisabi; namespace BTCPayServer.Plugins.Wabisabi;
public static class Extensions public static class Extensions
{ {
public static string ToSentenceCase(this string str)
{
return Regex.Replace(str, "[a-z][A-Z]", m => m.Value[0] + " " + char.ToLower(m.Value[1]));
}
/// <summary> /// <summary>
/// Returns an existing task from the concurrent dictionary, or adds a new task /// Returns an existing task from the concurrent dictionary, or adds a new task
/// using the specified asynchronous factory method. Concurrent invocations for /// using the specified asynchronous factory method. Concurrent invocations for

View File

@@ -8,6 +8,7 @@ using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Payments.PayJoin; using BTCPayServer.Payments.PayJoin;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Wallets; using BTCPayServer.Services.Wallets;
using Microsoft.Extensions.Logging;
using NBitcoin; using NBitcoin;
using NBXplorer; using NBXplorer;
using NBXplorer.DerivationStrategy; using NBXplorer.DerivationStrategy;
@@ -22,6 +23,7 @@ namespace BTCPayServer.Plugins.Wabisabi;
public class Smartifier public class Smartifier
{ {
private readonly ILogger _logger;
private readonly WalletRepository _walletRepository; private readonly WalletRepository _walletRepository;
private readonly ExplorerClient _explorerClient; private readonly ExplorerClient _explorerClient;
public DerivationStrategyBase DerivationScheme { get; } public DerivationStrategyBase DerivationScheme { get; }
@@ -29,10 +31,12 @@ public class Smartifier
private readonly IUTXOLocker _utxoLocker; private readonly IUTXOLocker _utxoLocker;
public Smartifier( public Smartifier(
ILogger logger,
WalletRepository walletRepository, WalletRepository walletRepository,
ExplorerClient explorerClient, DerivationStrategyBase derivationStrategyBase, string storeId, ExplorerClient explorerClient, DerivationStrategyBase derivationStrategyBase, string storeId,
IUTXOLocker utxoLocker, RootedKeyPath accountKeyPath) IUTXOLocker utxoLocker, RootedKeyPath accountKeyPath)
{ {
_logger = logger;
_walletRepository = walletRepository; _walletRepository = walletRepository;
_explorerClient = explorerClient; _explorerClient = explorerClient;
DerivationScheme = derivationStrategyBase; DerivationScheme = derivationStrategyBase;
@@ -40,10 +44,49 @@ public class Smartifier
_utxoLocker = utxoLocker; _utxoLocker = utxoLocker;
_accountKeyPath = accountKeyPath; _accountKeyPath = accountKeyPath;
} }
public readonly ConcurrentDictionary<uint256, Lazy<Task<TransactionInformation>>> TransactionInformations = new();
public readonly ConcurrentDictionary<uint256, Task<TransactionInformation>> CachedTransactions = new(); public readonly ConcurrentDictionary<uint256, Task<SmartTransaction>> SmartTransactions = new();
public readonly ConcurrentDictionary<uint256, Task<SmartTransaction>> Transactions = new();
public readonly ConcurrentDictionary<OutPoint, Task<SmartCoin>> Coins = new(); public readonly ConcurrentDictionary<OutPoint, Task<SmartCoin>> Coins = new();
public static async Task<T?> GetOrCreate<T, Y>(ConcurrentDictionary<Y, Lazy<Task<T?>>> collection, Y key, Func<Task<T?>> create, ILogger logger = null)
{
var lazyTask = new Lazy<Task<T?>>(() => FetchFromServer(create, logger, key));
// Even if multiple threads provide their own new Lazy instances, only one will be stored.
var task = collection.GetOrAdd(key, lazyTask).Value;
try
{
return await task;
}
catch (Exception)
{
// If there's an error, remove the lazy task from the dictionary.
collection.TryRemove(key, out _);
// The error has already been logged inside FetchFromServer.
return default;
}
}
private static async Task<T?> FetchFromServer<T, Y>(Func<Task<T?>> create, ILogger logger, Y key)
{
try
{
return await create();
}
catch (Exception e)
{
logger?.LogError(e, "Error while loading(and caching) {key}", key);
throw; // Re-throw the exception so the outer catch can handle it.
}
}
public async Task<TransactionInformation?> GetTransactionInfo(uint256 hash)
{
return await GetOrCreate(TransactionInformations , hash, () => _explorerClient.GetTransactionAsync(DerivationScheme, hash), _logger);
}
private readonly RootedKeyPath _accountKeyPath; private readonly RootedKeyPath _accountKeyPath;
public async Task LoadCoins(List<ReceivedCoin> coins, int current , public async Task LoadCoins(List<ReceivedCoin> coins, int current ,
@@ -57,15 +100,14 @@ public class Smartifier
var txs = coins.Select(data => data.OutPoint.Hash).Distinct(); var txs = coins.Select(data => data.OutPoint.Hash).Distinct();
foreach (uint256 tx in txs) foreach (uint256 tx in txs)
{ {
if(!CachedTransactions.ContainsKey(tx)) _ =GetTransactionInfo(tx);
CachedTransactions.TryAdd(tx, _explorerClient.GetTransactionAsync(DerivationScheme, tx));
} }
foreach (var coin in coins) foreach (var coin in coins)
{ {
var tx = await Transactions.GetOrAdd(coin.OutPoint.Hash, async uint256 => var tx = await SmartTransactions.GetOrAdd(coin.OutPoint.Hash, async uint256 =>
{ {
var unsmartTx = await CachedTransactions[coin.OutPoint.Hash]; var unsmartTx = await GetTransactionInfo(coin.OutPoint.Hash);
if (unsmartTx?.Transaction is null) if (unsmartTx?.Transaction is null)
{ {
return null; return null;
@@ -85,17 +127,7 @@ public class Smartifier
potentialMatches.TryAdd(matchedInput, potentialMatchesForInput.ToArray()); potentialMatches.TryAdd(matchedInput, potentialMatchesForInput.ToArray());
foreach (IndexedTxIn potentialMatchForInput in potentialMatchesForInput) foreach (IndexedTxIn potentialMatchForInput in potentialMatchesForInput)
{ {
TransactionInformation ti = null; var ti = await GetTransactionInfo(potentialMatchForInput.PrevOut.Hash);
try
{
ti = await CachedTransactions.GetOrAdd(potentialMatchForInput.PrevOut.Hash,
_explorerClient.GetTransactionAsync(DerivationScheme,
potentialMatchForInput.PrevOut.Hash));
}
catch (Exception e)
{
CachedTransactions.Remove(potentialMatchForInput.PrevOut.Hash, out _);
}
if (ti is not null) if (ti is not null)
{ {
MatchedOutput found = ti.Outputs.Find(output => MatchedOutput found = ti.Outputs.Find(output =>
@@ -157,7 +189,6 @@ public class Smartifier
var smartCoin = await Coins.GetOrAdd(coin.OutPoint, async point => var smartCoin = await Coins.GetOrAdd(coin.OutPoint, async point =>
{ {
utxoLabels.TryGetValue(coin.OutPoint, out var labels); utxoLabels.TryGetValue(coin.OutPoint, out var labels);
var unsmartTx = await CachedTransactions[coin.OutPoint.Hash];
var pubKey = DerivationScheme.GetChild(coin.KeyPath).GetExtPubKeys().First().PubKey; var pubKey = DerivationScheme.GetChild(coin.KeyPath).GetExtPubKeys().First().PubKey;
//if there is no account key path, it most likely means this is a watch only wallet. Fake the key path //if there is no account key path, it most likely means this is a watch only wallet. Fake the key path
var kp = _accountKeyPath?.Derive(coin.KeyPath).KeyPath ?? new KeyPath(0,0,0,0,0); var kp = _accountKeyPath?.Derive(coin.KeyPath).KeyPath ?? new KeyPath(0,0,0,0,0);

View File

@@ -3,7 +3,7 @@
@using BTCPayServer.Abstractions.Contracts @using BTCPayServer.Abstractions.Contracts
@model WalletWasabi.Backend.Controllers.DiscoveredCoordinator @model WalletWasabi.Backend.Controllers.DiscoveredCoordinator
@inject IScopeProvider ScopeProvider @inject IScopeProvider ScopeProvider
<form asp-action="AddCoordinator" method="post" class="card mt-3" asp-route-storeId="@ScopeProvider.GetCurrentStoreId()" permission="@Policies.CanModifyServerSettings"> <form asp-action="AddCoordinator" asp-controller="WabisabiStore" method="post" class="card mt-3" asp-route-storeId="@ScopeProvider.GetCurrentStoreId()" permission="@Policies.CanModifyServerSettings">
<input type="hidden" asp-for="Description"/> <input type="hidden" asp-for="Description"/>
<input type="hidden" asp-for="Name"/> <input type="hidden" asp-for="Name"/>
<input type="hidden" asp-for="Uri"/> <input type="hidden" asp-for="Uri"/>

View File

@@ -10,10 +10,10 @@
<button type="button" class="btn btn-secondary mt-2" permission="@Policies.CanModifyServerSettings" @* <button type="button" class="btn btn-secondary mt-2" permission="@Policies.CanModifyServerSettings" *@
data-bs-toggle="modal" data-bs-target="#discover-prompt"> @* data-bs-toggle="modal" data-bs-target="#discover-prompt"> *@
Add Coordinator @* Add Coordinator *@
</button> @* </button> *@
<div class="modal fade" id="discover-prompt" permission="@Policies.CanModifyServerSettings"> <div class="modal fade" id="discover-prompt" permission="@Policies.CanModifyServerSettings">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
@@ -30,7 +30,7 @@
</li> </li>
</ul> </ul>
<div class="tab-content "> <div class="tab-content ">
<form asp-action="AddCoordinator" asp-route-storeId="@ScopeProvider.GetCurrentStoreId()" <form asp-action="AddCoordinator" asp-controller="WabisabiStore" asp-route-storeId="@ScopeProvider.GetCurrentStoreId()"
class="tab-pane fade show active " id="nostr-tab-pane" role="tabpanel" aria-labelledby="home-tab" tabindex="0"> class="tab-pane fade show active " id="nostr-tab-pane" role="tabpanel" aria-labelledby="home-tab" tabindex="0">
<div class="modal-body"> <div class="modal-body">
@@ -45,7 +45,7 @@
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
</div> </div>
</form> </form>
<form asp-action="AddCoordinator" asp-route-storeId="@ScopeProvider.GetCurrentStoreId()" <form asp-action="AddCoordinator" asp-controller="WabisabiStore" asp-route-storeId="@ScopeProvider.GetCurrentStoreId()"
class="tab-pane fade" id="manual-tab-pane" role="tabpanel" tabindex="0"> class="tab-pane fade" id="manual-tab-pane" role="tabpanel" tabindex="0">
<div class="modal-body"> <div class="modal-body">

View File

@@ -1,15 +1,13 @@
@using BTCPayServer.Abstractions.Contracts @using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Client
@using BTCPayServer.Client.Models
@using BTCPayServer.Common @using BTCPayServer.Common
@using BTCPayServer.Plugins.Wabisabi @using BTCPayServer.Plugins.Wabisabi
@using BTCPayServer.Security
@using Microsoft.AspNetCore.Mvc.TagHelpers
@using NBitcoin @using NBitcoin
@using WalletWasabi.Blockchain.Analysis @using WalletWasabi.Extensions
@using WalletWasabi.WabiSabi.Backend.Rounds
@using WalletWasabi.WabiSabi.Client
@using WalletWasabi.WabiSabi.Models
@model object @model object
@inject IScopeProvider ScopeProvider @inject IScopeProvider ScopeProvider
@inject BTCPayServerClient Client
@inject WabisabiService WabisabiService; @inject WabisabiService WabisabiService;
@inject WalletProvider WalletProvider; @inject WalletProvider WalletProvider;
@inject WabisabiCoordinatorClientInstanceManager WabisabiCoordinatorClientInstanceManager @inject WabisabiCoordinatorClientInstanceManager WabisabiCoordinatorClientInstanceManager
@@ -22,12 +20,6 @@
{ {
return; return;
} }
@if (!(await ExplorerClientProvider.GetExplorerClient("BTC").GetStatusAsync()).IsFullySynched)
{
available = false;
return;
}
var storeId = ScopeProvider.GetCurrentStoreId(); var storeId = ScopeProvider.GetCurrentStoreId();
} }
@@ -57,7 +49,7 @@
} }
else else
{ {
<div class="widget store-wallet-balance" > <div class="widget store-wallet-balance">
<header> <header>
<h3>Recent Coinjoins</h3> <h3>Recent Coinjoins</h3>
@if (cjHistory.Any()) @if (cjHistory.Any())
@@ -89,7 +81,7 @@
var privacyPercentage = Math.Round(privacy * 100); var privacyPercentage = Math.Round(privacy * 100);
var colorCoins = coins.GroupBy(coin => coin.CoinColor(wallet.AnonScoreTarget)).ToDictionary(grouping => grouping.Key, grouping => grouping); var colorCoins = coins.GroupBy(coin => coin.CoinColor(wallet.AnonScoreTarget)).ToDictionary(grouping => grouping.Key, grouping => grouping);
<div class="widget store-numbers" > <div class="widget store-numbers">
@if (wallet is { }) @if (wallet is { })
{ {
@@ -151,35 +143,35 @@
} }
</div> </div>
</div> </div>
@{ @* @{ *@
var coinjoined = @coins.CoinJoinInProcess(); @* var coinjoined = @coins.CoinJoinInProcess(); *@
} @* } *@
@if (coinjoined.Any()) @* @if (coinjoined.Any()) *@
{ @* { *@
var count = @coins.CoinJoinInProcess().Count(); @* var count = @coins.CoinJoinInProcess().Count(); *@
var totalCount = @coins.Count(); @* var totalCount = @coins.Count(); *@
var sum = @coinjoined.TotalAmount().ToDecimal(MoneyUnit.BTC); @* var sum = @coinjoined.TotalAmount().ToDecimal(MoneyUnit.BTC); *@
var totalSum = @coins.TotalAmount().ToDecimal(MoneyUnit.BTC); @* var totalSum = @coins.TotalAmount().ToDecimal(MoneyUnit.BTC); *@
var sumPercentage = decimal.Divide(sum, totalSum) * 100; @* var sumPercentage = decimal.Divide(sum, totalSum) * 100; *@
var countPercentage = decimal.Divide(count, totalCount) * 100; @* var countPercentage = decimal.Divide(count, totalCount) * 100; *@
@* *@
<div> @* <div> *@
<h6 class="mb-2">Coins currently joining</h6> @* <h6 class="mb-2">Coins currently joining</h6> *@
<div class="progress mb-2 position-relative" style="height: 2rem;"> @* <div class="progress mb-2 position-relative" style="height: 2rem;"> *@
<div class="w-100 text-center position-absolute bg-transparent progress-bar h-100">@count </div> @* <div class="w-100 text-center position-absolute bg-transparent progress-bar h-100">@count </div> *@
<div class="progress-bar bg-info progress-bar-striped progress-bar-animated w-100" role="progressbar"></div> @* <div class="progress-bar bg-info progress-bar-striped progress-bar-animated w-100" role="progressbar"></div> *@
</div> @* </div> *@
</div> @* </div> *@
<div> @* <div> *@
<h6 class="mb-2">Value currently joining</h6> @* <h6 class="mb-2">Value currently joining</h6> *@
<div class="progress mb-2 position-relative" style="height: 2rem;"> @* <div class="progress mb-2 position-relative" style="height: 2rem;"> *@
<div class="w-100 text-center position-absolute bg-transparent progress-bar h-100">@sum BTC</div> @* <div class="w-100 text-center position-absolute bg-transparent progress-bar h-100">@sum BTC</div> *@
<div class="progress-bar bg-info progress-bar-striped progress-bar-animated w-100" role="progressbar"></div> @* <div class="progress-bar bg-info progress-bar-striped progress-bar-animated w-100" role="progressbar"></div> *@
@* *@
@* *@
</div> @* </div> *@
</div> @* </div> *@
} @* } *@
<!-- Modal --> <!-- Modal -->
@@ -272,7 +264,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="list-group list-group-flush mb-2"> <div class="list-group list-group-flush mt-4 mb-3">
<h5 class="list-group-item-heading text-muted">Enabled coordinators</h5> <h5 class="list-group-item-heading text-muted">Enabled coordinators</h5>
@{ @{
@@ -282,42 +274,172 @@
{ {
continue; continue;
} }
<div class="list-group-item">
<h6>@coordinator.CoordinatorDisplayName</h6>
<div class="row ">
<span class="text-muted col-sm-12 col-xxl-9 p-0 text-break">
@coordinator.Coordinator
</span>
@if (!coordinator.WasabiCoordinatorStatusFetcher.Connected) RoundState currentRound = null;
CoinJoinTracker tracker = null;
if (coordinator.CoinJoinManager.TrackedCoinJoins?.TryGetValue(wallet.WalletName, out tracker) is true &&
tracker?.CoinJoinClient?.CurrentRoundId is { } &&
tracker?.CoinJoinClient?.RoundStatusUpdater?.RoundStates?.TryGetValue(tracker?.CoinJoinClient?.CurrentRoundId, out currentRound) is true)
{ {
<p class="text-danger mb-0 col-sm-12 col-xxl-3 p-0 text-break">Not connected</p>
} }
else var statusMsg = coordinator.WasabiCoordinatorStatusFetcher.Connected ? $"Connected to {(coordinator.Coordinator?.ToString() ?? "local")}" : $"Not connected to {(coordinator.Coordinator?.ToString() ?? "local")}";
<div class="list-group-item">
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3">
<div class="d-flex flex-wrap align-items-center justify-content-between gap-3" data-bs-toggle="tooltip" title="@statusMsg">
<span class="btcpay-status btcpay-status--@(coordinator.WasabiCoordinatorStatusFetcher.Connected ? "enabled" : "disabled")"></span>
<h6>@coordinator.CoordinatorDisplayName</h6>
</div>
@if(currentRound is not null)
{ {
<p class="text-success mb-0 col-sm-12 col-xxl-3 p-0 text-break">Connected</p> <div class="timer cursor-pointer" data-bs-toggle="collapse" data-bs-target="#cj-@currentRound.Id">
<span class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden"></span>
</span>
<span class="h6">Mixing</span>
<vc:icon symbol="caret-down" />
</div>
}else if( coordinator.WasabiCoordinatorStatusFetcher.Connected)
{
<span class="h6">Idle</span>
} }
</div> </div>
@{ @{
if (coordinator.CoinPrison is not null) if (coordinator.CoinPrison is not null)
{ {
var bannedCoins = coins.Where(coin => coordinator.CoinPrison.TryGetOrRemoveBannedCoin(coin.Outpoint, out _)); var bannedCoins = coins.Where(coin => coordinator.CoinPrison.TryGetOrRemoveBannedCoin(coin.Outpoint, out _));
@if (bannedCoins.Any()) @if (bannedCoins.Any())
{ {
<div> <div class="text-muted">@bannedCoins.Count() banned coins(for disrupting rounds)</div>
<h6 class="mb-2">Coins currently banned (for disrupting rounds)</h6>
<div class="progress mb-2 position-relative" style="height: 2rem;">
<div class="w-100 text-center position-absolute bg-transparent progress-bar h-100">@bannedCoins.Count() </div>
<div class="progress-bar bg-danger w-100" role="progressbar"></div>
</div>
</div>
}
} }
} }
if (currentRound is not null)
{
<div class="collapse table-responsive @(enabledSettings.Count() ==1? "show": "")" id="cj-@currentRound.Id">
<table class="table ">
<tr>
<th scope="row">Status</th>
<td class="text-truncate">@currentRound.Phase.ToString().ToSentenceCase()</td>
</tr><tr>
<th scope="row">Round id</th>
<td class="text-truncate" style="max-width: 200px" title="@currentRound.Id.ToString()">@currentRound.Id.ToString()</td>
</tr>
<tr>
<th scope="row">Mining feerate</th>
<td >@currentRound.CoinjoinState.Parameters.MiningFeeRate.ToString()</td>
</tr>
<tr>
<th scope="row">Coinjoin total inputs</th>
<td >@currentRound.CoinjoinState.Inputs.Count() inputs (@currentRound.CoinjoinState.Inputs.Sum(coin => coin.Amount.ToDecimal(MoneyUnit.BTC)) BTC)</td>
</tr>
@if (!tracker.CoinJoinClient.CoinsToRegister.IsEmpty)
{
<tr>
<th scope="row">Your inputs</th>
<td class="row" >
<span class="w-100">Registered @tracker.CoinJoinClient.CoinsInCriticalPhase.Count() inputs (@tracker.CoinJoinClient.CoinsInCriticalPhase.Sum(coin => coin.Amount.ToDecimal(MoneyUnit.BTC)) BTC) / @tracker.CoinJoinClient.CoinsToRegister.Count() inputs (@tracker.CoinJoinClient.CoinsToRegister.Sum(coin => coin.Amount.ToDecimal(MoneyUnit.BTC)) BTC) </span>
@if (tracker.BannedCoins.Any())
{
<span class="w-100 text-danger">but got @tracker.BannedCoins.Count() inputs (@tracker.BannedCoins.Sum(coin => coin.Coin.Amount.ToDecimal(MoneyUnit.BTC)) BTC) banned</span>
}
</td>
</tr>
}
@if (currentRound.Phase >= Phase.OutputRegistration)
{
<tr>
<th scope="row">Coinjoin total outputs</th>
<td >@currentRound.CoinjoinState.Outputs.Count() outputs (@currentRound.CoinjoinState.Outputs.Sum(coin => coin.Value.ToDecimal(MoneyUnit.BTC)) BTC)</td>
</tr>
if (tracker.CoinJoinClient.OutputTxOuts is { } outputs)
{
<tr>
<th scope="row">Your outputs</th>
<td >@outputs.outputTxOuts.Count() outputs (@outputs.outputTxOuts.Sum(coin => coin.Value.ToDecimal(MoneyUnit.BTC)) BTC, @outputs.batchedPayments.Count() batched payments)</td>
</tr>
}
}
</table>
</div>
@* <div class="collapse table-responsive" id="cj-@currentRound.Id"> *@
@* *@
@* <dl> *@
@* <div class="d-flex flex-wrap align-items-center gap-2"> *@
@* <dt class="w-100px"> *@
@* Status *@
@* </dt> *@
@* <dd> *@
@* @currentRound.Phase.ToString().ToSentenceCase() *@
@* </dd> *@
@* </div> *@
@* <div class="d-flex flex-wrap align-items-center gap-2"> *@
@* <dt class="w-100px"> *@
@* Round ID *@
@* </dt> *@
@* <dd> *@
@* @currentRound.Id.ToString() *@
@* </dd> *@
@* </div> *@
@* <div class="d-flex flex-wrap align-items-center gap-2"> *@
@* <dt class="w-100px"> *@
@* Mining feerate *@
@* </dt> *@
@* <dd> *@
@* @currentRound.CoinjoinState.Parameters.MiningFeeRate.ToString() *@
@* </dd> *@
@* </div> *@
@* <div class="d-flex flex-wrap align-items-center gap-2"> *@
@* <dt class="w-100px"> *@
@* Coinjoin total inputs *@
@* </dt> *@
@* <dd> *@
@* @currentRound.CoinjoinState.Inputs.Count() inputs (@currentRound.CoinjoinState.Inputs.Sum(coin => coin.Amount.ToDecimal(MoneyUnit.BTC)) BTC) *@
@* </dd> *@
@* </div> *@
@* *@
@* *@
@* @if (!tracker.CoinJoinClient.CoinsToRegister.IsEmpty) *@
@* { *@
@* <div class="d-flex flex-wrap align-items-center gap-2"> *@
@* <dt class="w-100px">Your inputs</dt> *@
@* <dd> *@
@* <span class="w-100">Registered @tracker.CoinJoinClient.CoinsInCriticalPhase.Count() inputs (@tracker.CoinJoinClient.CoinsInCriticalPhase.Sum(coin => coin.Amount.ToDecimal(MoneyUnit.BTC)) BTC) / @tracker.CoinJoinClient.CoinsToRegister.Count() inputs (@tracker.CoinJoinClient.CoinsToRegister.Sum(coin => coin.Amount.ToDecimal(MoneyUnit.BTC)) BTC) </span> *@
@* @if (tracker.BannedCoins.Any()) *@
@* { *@
@* <span class="w-100 text-danger">but got @tracker.BannedCoins.Count() inputs (@tracker.BannedCoins.Sum(coin => coin.Coin.Amount.ToDecimal(MoneyUnit.BTC)) BTC) banned</span> *@
@* } *@
@* </dd> *@
@* </div> *@
@* } *@
@* *@
@* @if (currentRound.Phase >= Phase.OutputRegistration) *@
@* { *@
@* <div class="d-flex flex-wrap align-items-center gap-2"> *@
@* <dt class="w-100px">Coinjoin total outputs</dt> *@
@* <dd> *@
@* @currentRound.CoinjoinState.Outputs.Count() outputs (@currentRound.CoinjoinState.Outputs.Sum(coin => coin.Value.ToDecimal(MoneyUnit.BTC)) BTC) *@
@* </dd> *@
@* </div> *@
@* if (tracker.CoinJoinClient.OutputTxOuts is { } outputs) *@
@* { *@
@* <div class="d-flex flex-wrap align-items-center gap-2"> *@
@* <dt class="w-100px">>Your outputs</dt> *@
@* <dd> *@
@* @outputs.outputTxOuts.Count() outputs (@outputs.outputTxOuts.Sum(coin => coin.Value.ToDecimal(MoneyUnit.BTC)) BTC, @outputs.batchedPayments.Count() batched payments) *@
@* </dd> *@
@* </div> *@
@* } *@
@* } *@
@* </dl> *@
@* *@
@* </div> *@
}
}
</div> </div>
} }
} }
@@ -326,8 +448,6 @@
<button type="button" class="btn btn-text p-1" data-bs-toggle="modal" data-bs-target="#coins"> <button type="button" class="btn btn-text p-1" data-bs-toggle="modal" data-bs-target="#coins">
View coins View coins
</button> </button>
</div> </div>
</div> </div>
} }

View File

@@ -1,5 +1,4 @@
 
@using WalletWasabi.Backend.Controllers
@using BTCPayServer.Plugins.Wabisabi @using BTCPayServer.Plugins.Wabisabi
@model WalletWasabi.Backend.Controllers.WabisabiCoordinatorSettings @model WalletWasabi.Backend.Controllers.WabisabiCoordinatorSettings
@@ -8,9 +7,19 @@
ViewData["NavPartialName"] = "../UIServer/_Nav"; ViewData["NavPartialName"] = "../UIServer/_Nav";
} }
<h2 class="mb-4">Coinjoin coordinator configuration</h2>
<form method="post"> <form method="post">
<div class="d-flex align-items-center justify-content-between mb-3">
<h3 class="mb-0">
<span>Coinjoin coordinator</span>
<a href="https://docs.btcpayserver.org/Wabisabi" target="_blank" rel="noreferrer noopener" title="More information...">
<vc:icon symbol="info"/>
</a>
</h3>
<button name="command" type="submit" value="save" class="btn btn-primary mt-3 mt-sm-0">Save</button>
</div>
<div class="row"> <div class="row">
<div class="col-xxl-constrain col-xl-8"> <div class="col-xxl-constrain col-xl-8">
<div class="form-group form-check"> <div class="form-group form-check">
@@ -36,9 +45,8 @@
</textarea> </textarea>
</div> </div>
</div> </div>
</div>
<div class="row "> <div class="col-xxl-constrain col-xl-4">
<div class="col-xxl-constrain col-xl-8">
<h3 class="mb-3">Publish to Nostr </h3> <h3 class="mb-3">Publish to Nostr </h3>
<div class="form-group "> <div class="form-group ">
<label asp-for="NostrRelay" class="form-label">Nostr Relay</label> <label asp-for="NostrRelay" class="form-label">Nostr Relay</label>
@@ -68,7 +76,6 @@
<p class=" alert alert-warning" style="white-space: pre-line"> <p class=" alert alert-warning" style="white-space: pre-line">
@WabisabiCoordinatorConfigController.OurDisclaimer @WabisabiCoordinatorConfigController.OurDisclaimer
</p> </p>
<button name="command" type="submit" value="save" class="btn btn-primary mt-2">Save</button>
</form> </form>

View File

@@ -1,15 +1,25 @@
 @using BTCPayServer.Components
@using BTCPayServer.Components @using BTCPayServer
@using BTCPayServer.Abstractions.Extensions
@using BTCPayServer.Views.Stores
@model BTCPayServer.Plugins.Wabisabi.CoinjoinsViewModel @model BTCPayServer.Plugins.Wabisabi.CoinjoinsViewModel
@{ @{
ViewData.SetActivePage(StoreNavPages.Plugins); var storeId = Context.GetCurrentStoreId();
ViewData.SetActivePage("CoinjoinHistory", "Coinjoin", "Coinjoin History", storeId);
} }
<div class="d-flex align-items-center justify-content-between mb-3"> <div class="d-flex align-items-center justify-content-between mb-3">
<h3 class="mb-0">Coinjoin History</h3> <h3 class="mb-0">
<span>@ViewData["Title"]</span>
<a href="https://docs.btcpayserver.org/Wabisabi" target="_blank" rel="noreferrer noopener" title="More information...">
<vc:icon symbol="info"/>
</a>
</h3>
<a asp-action="UpdateWabisabiStoreSettings" asp-route-storeId="@storeId" class="btn btn-primary mt-3 mt-sm-0" role="button">
<span class="fa fa-settings"></span>
Settings
</a>
</div> </div>
<partial name="Wabisabi/CoinjoinHistoryTable" model="Model.Coinjoins.ToList()" />
<partial name="Wabisabi/CoinjoinHistoryTable" model="Model.Coinjoins.ToList()"/>
<vc:pager view-model="Model"></vc:pager> <vc:pager view-model="Model"></vc:pager>

View File

@@ -17,9 +17,7 @@
@inject BTCPayServerOptions BtcPayServerOptions @inject BTCPayServerOptions BtcPayServerOptions
@{ @{
var storeId = _scopeProvider.GetCurrentStoreId(); var storeId = _scopeProvider.GetCurrentStoreId();
Layout = "../Shared/_NavLayout.cshtml"; ViewData.SetActivePage("CoinjoinSettings", "Coinjoin", "Coinjoin settings", storeId);
ViewData["NavPartialName"] = "../UIStores/_Nav";
ViewData.SetActivePage("Plugins", "BTCPayServer.Views.Stores.StoreNavPages", "Wabisabi coinjoin support", storeId);
var userid = Context.User.Claims.Single(claim => claim.Type == ClaimTypes.NameIdentifier).Value; var userid = Context.User.Claims.Single(claim => claim.Type == ClaimTypes.NameIdentifier).Value;
var anyEnabled = Model.Settings.Any(settings => settings.Enabled); var anyEnabled = Model.Settings.Any(settings => settings.Enabled);
ScriptPubKeyType? scriptType; ScriptPubKeyType? scriptType;
@@ -34,15 +32,33 @@
.Select(pair => new SelectListItem(pair.Value.s.StoreName, pair.Key, Model.MixToOtherWallet == pair.Key)).Prepend(new SelectListItem("None", "")); .Select(pair => new SelectListItem(pair.Value.s.StoreName, pair.Key, Model.MixToOtherWallet == pair.Key)).Prepend(new SelectListItem("None", ""));
} }
<div class="row">
<div class="d-flex"> <form method="post">
<h2 class="">Coinjoin configuration</h2>
<a href="https://github.com/Kukks/BTCPayServerPlugins/blob/master/Plugins/BTCPayServer.Plugins.Wabisabi/readme.md" class="ms-1" target="_blank" rel="noreferrer noopener">
<span class="fa fa-question-circle-o text-secondary" title="More information..."></span> <partial name="_StatusMessage"/>
<div class="d-flex align-items-center justify-content-between mb-3">
<h3 class="mb-0">
<span>@ViewData["Title"]</span>
<a href="https://docs.btcpayserver.org/Wabisabi" target="_blank" rel="noreferrer noopener" title="More information...">
<vc:icon symbol="info"/>
</a> </a>
</h3>
<div>
<button name="command" type="submit" value="save" class="btn btn-primary mt-3 mt-sm-0">Save</button>
<a asp-action="ListCoinjoins" asp-route-storeId="@storeId" class="btn btn-secondary mt-3 mt-sm-0" role="button">
Coinjoin History
</a>
<button type="button" class="btn btn-secondary mt-3 mt-sm-0" permission="@Policies.CanModifyServerSettings"
data-bs-toggle="modal" data-bs-target="#discover-prompt">
Add Coordinator
</button>
<a asp-controller="WabisabiCoordinatorConfig" asp-action="UpdateWabisabiSettings" class="btn btn-secondary mt-3 mt-sm-0" permission="@Policies.CanModifyServerSettings">Coordinator</a>
@* <a class="btn btn-secondary mt-3 mt-sm-0" href="https://gist.github.com/nopara73/bb17e89d7dc9af536ca41f50f705d329" rel="noreferrer noopener" target="_blank">Enable Discreet payments - Coming soon</a> *@
</div> </div>
</div> </div>
<form method="post">
@{ @{
if (BtcPayServerOptions.SocksEndpoint is null) if (BtcPayServerOptions.SocksEndpoint is null)
{ {
@@ -55,7 +71,6 @@
var wallet = await WalletProvider.GetWalletAsync(storeId); var wallet = await WalletProvider.GetWalletAsync(storeId);
if (wallet is BTCPayWallet) if (wallet is BTCPayWallet)
{ {
@if (!((BTCPayKeyChain) wallet.KeyChain).KeysAvailable) @if (!((BTCPayKeyChain) wallet.KeyChain).KeysAvailable)
{ {
<div class="alert alert-danger d-flex align-items-center" role="alert"> <div class="alert alert-danger d-flex align-items-center" role="alert">
@@ -217,7 +232,7 @@
<span class="text-muted">@coordinator.Coordinator</span> <span class="text-muted">@coordinator.Coordinator</span>
<div> <div>
<div>@(!coordinator.WasabiCoordinatorStatusFetcher.Connected? "Coordinator Status: Not connected": "Coordinator Status: Connected")</div> <div>@(!coordinator.WasabiCoordinatorStatusFetcher.Connected ? "Coordinator Status: Not connected" : "Coordinator Status: Connected")</div>
@if (!string.IsNullOrEmpty(coordinator.Description)) @if (!string.IsNullOrEmpty(coordinator.Description))
{ {
@@ -346,15 +361,10 @@
} }
<button name="command" type="submit" value="save" class="btn btn-primary mt-2">Save</button>
<a asp-controller="WabisabiStore" asp-action="ListCoinjoins" class="btn btn-secondary mt-2" asp-route-storeId="@storeId">Coinjoins</a>
</form> </form>
<a asp-controller="WabisabiCoordinatorConfig" asp-action="UpdateWabisabiSettings" class="btn btn-secondary mt-2" permission="@Policies.CanModifyServerSettings">Coordinator runner</a>
<partial name="Wabisabi/AddCoordinatorPrompt" model="@(new DiscoveredCoordinator())"/> <partial name="Wabisabi/AddCoordinatorPrompt" model="@(new DiscoveredCoordinator())"/>
<a class="btn btn-secondary mt-2" href="https://gist.github.com/nopara73/bb17e89d7dc9af536ca41f50f705d329" rel="noreferrer noopener" target="_blank">Enable Discreet payments - Coming soon</a>
@section PageFootContent { @section PageFootContent {

View File

@@ -21,7 +21,6 @@ using WalletWasabi.WabiSabi.Client.RoundStateAwaiters;
using WalletWasabi.WabiSabi.Client.StatusChangedEvents; using WalletWasabi.WabiSabi.Client.StatusChangedEvents;
using WalletWasabi.Wallets; using WalletWasabi.Wallets;
using WalletWasabi.WebClients.Wasabi; using WalletWasabi.WebClients.Wasabi;
using HttpClientFactory = WalletWasabi.WebClients.Wasabi.HttpClientFactory;
namespace BTCPayServer.Plugins.Wabisabi; namespace BTCPayServer.Plugins.Wabisabi;
@@ -137,7 +136,7 @@ public class WabisabiCoordinatorClientInstance
public Uri Coordinator { get; set; } public Uri Coordinator { get; set; }
public WalletProvider WalletProvider { get; } public WalletProvider WalletProvider { get; }
public string TermsConditions { get; set; } public string TermsConditions { get; set; }
public HttpClientFactory WasabiHttpClientFactory { get; set; } public WasabiHttpClientFactory WasabiHttpClientFactory { get; set; }
public RoundStateUpdater RoundStateUpdater { get; set; } public RoundStateUpdater RoundStateUpdater { get; set; }
public CoinPrison CoinPrison { get; private set; } public CoinPrison CoinPrison { get; private set; }
public WasabiCoordinatorStatusFetcher WasabiCoordinatorStatusFetcher { get; set; } public WasabiCoordinatorStatusFetcher WasabiCoordinatorStatusFetcher { get; set; }
@@ -176,7 +175,7 @@ public class WabisabiCoordinatorClientInstance
} }
else else
{ {
WasabiHttpClientFactory = new HttpClientFactory(torEndpoint, () => Coordinator); WasabiHttpClientFactory = new WasabiHttpClientFactory(torEndpoint, () => Coordinator);
var roundStateUpdaterCircuit = new PersonCircuit(); var roundStateUpdaterCircuit = new PersonCircuit();
var roundStateUpdaterHttpClient = var roundStateUpdaterHttpClient =
WasabiHttpClientFactory.NewHttpClient(Mode.SingleCircuitPerLifetime, roundStateUpdaterCircuit); WasabiHttpClientFactory.NewHttpClient(Mode.SingleCircuitPerLifetime, roundStateUpdaterCircuit);

View File

@@ -1,4 +1,5 @@
using System; using System;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Models; using BTCPayServer.Abstractions.Models;
@@ -20,7 +21,7 @@ public class WabisabiPlugin : BaseBTCPayServerPlugin
{ {
public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = public override IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } =
{ {
new() { Identifier = nameof(BTCPayServer), Condition = ">=1.11.0" } new() { Identifier = nameof(BTCPayServer), Condition = ">=1.11.5" }
}; };
public override void Execute(IServiceCollection applicationBuilder) public override void Execute(IServiceCollection applicationBuilder)
{ {
@@ -140,3 +141,4 @@ public class WabisabiPlugin : BaseBTCPayServerPlugin
base.Execute(applicationBuilder, applicationBuilderApplicationServices); base.Execute(applicationBuilder, applicationBuilderApplicationServices);
} }
} }

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -33,7 +34,7 @@ namespace BTCPayServer.Plugins.Wabisabi
_memoryCache = memoryCache; _memoryCache = memoryCache;
} }
private string GetCacheKey(string storeId) public static string GetCacheKey(string storeId)
{ {
return $"{nameof(WabisabiStoreSettings)}-{storeId}"; return $"{nameof(WabisabiStoreSettings)}-{storeId}";
} }
@@ -76,7 +77,7 @@ namespace BTCPayServer.Plugins.Wabisabi
_walletProvider.LoadedWallets.TryGetValue(storeId, out var walletTask); _walletProvider.LoadedWallets.TryGetValue(storeId, out var walletTask);
if (walletTask != null) if (walletTask != null)
{ {
var wallet = await walletTask; var wallet = await walletTask.Value;
await _coordinatorClientInstanceManager.StopWallet(wallet, setting.Coordinator); await _coordinatorClientInstanceManager.StopWallet(wallet, setting.Coordinator);
} }
} }
@@ -136,15 +137,29 @@ namespace BTCPayServer.Plugins.Wabisabi
} }
public async Task<List<BTCPayWallet.CoinjoinData>> GetCoinjoinHistory(string storeId) public async Task<List<BTCPayWallet.CoinjoinData>> GetCoinjoinHistory(string storeId, bool force = false)
{ {
return (await _walletRepository.GetWalletObjects( var k = GetCacheKey(storeId) + "cjhistory";
if (force)
{
_memoryCache.Remove(k);
}
return await _memoryCache.GetOrCreateAsync(k, async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1);
var result = (await _walletRepository.GetWalletObjects(
new GetWalletObjectsQuery(new WalletId(storeId, "BTC")) new GetWalletObjectsQuery(new WalletId(storeId, "BTC"))
{ {
Type = "coinjoin" Type = "coinjoin"
})).Values.Where(data => !string.IsNullOrEmpty(data.Data)) })).Values.Where(data => !string.IsNullOrEmpty(data.Data))
.Select(data => JObject.Parse(data.Data).ToObject<BTCPayWallet.CoinjoinData>()) .Select(data => JObject.Parse(data.Data).ToObject<BTCPayWallet.CoinjoinData>())
.OrderByDescending(tuple => tuple.Timestamp).ToList(); .OrderByDescending(tuple => tuple.Timestamp).ToList();
entry.Value = result;
return result;
});
} }
} }

View File

@@ -14,6 +14,7 @@ using BTCPayServer.HostedServices;
using BTCPayServer.Payments.PayJoin; using BTCPayServer.Payments.PayJoin;
using BTCPayServer.Services; using BTCPayServer.Services;
using BTCPayServer.Services.Stores; using BTCPayServer.Services.Stores;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NBitcoin; using NBitcoin;
@@ -57,7 +58,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
_networkProvider = networkProvider; _networkProvider = networkProvider;
} }
public readonly ConcurrentDictionary<string, Task<IWallet?>> LoadedWallets = new(); public readonly ConcurrentDictionary<string, Lazy<Task<IWallet>>> LoadedWallets = new();
public class WalletUnloadEventArgs : EventArgs public class WalletUnloadEventArgs : EventArgs
{ {
@@ -73,7 +74,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
public async Task<IWallet?> GetWalletAsync(string name) public async Task<IWallet?> GetWalletAsync(string name)
{ {
await initialLoad.Task; await initialLoad.Task;
return await LoadedWallets.GetOrAddAsync(name, async s => return await Smartifier.GetOrCreate(LoadedWallets, name, async () =>
{ {
if (!_cachedSettings.TryGetValue(name, out var wabisabiStoreSettings)) if (!_cachedSettings.TryGetValue(name, out var wabisabiStoreSettings))
{ {
@@ -101,7 +102,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
var accountKeyPath2 = await explorerClient.GetMetadataAsync<RootedKeyPath>(derivationStrategy, var accountKeyPath2 = await explorerClient.GetMetadataAsync<RootedKeyPath>(derivationStrategy,
WellknownMetadataKeys.AccountKeyPath); WellknownMetadataKeys.AccountKeyPath);
accountKeyPath = accountKeyPath2 ?? accountKeyPath; accountKeyPath = accountKeyPath2 ?? accountKeyPath;
var smartifier = new Smartifier(_serviceProvider.GetRequiredService<WalletRepository>(), var smartifier = new Smartifier(_logger,_serviceProvider.GetRequiredService<WalletRepository>(),
explorerClient, derivationStrategy, name, UtxoLocker, accountKeyPath); explorerClient, derivationStrategy, name, UtxoLocker, accountKeyPath);
if (masterKey is null || accountKey is null || accountKeyPath is null) if (masterKey is null || accountKey is null || accountKeyPath is null)
{ {
@@ -112,7 +113,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
} }
else else
{ {
var smartifier = new Smartifier(_serviceProvider.GetRequiredService<WalletRepository>(), explorerClient, var smartifier = new Smartifier(_logger,_serviceProvider.GetRequiredService<WalletRepository>(), explorerClient,
derivationStrategy, name, UtxoLocker, accountKeyPath); derivationStrategy, name, UtxoLocker, accountKeyPath);
keychain = new BTCPayKeyChain(explorerClient, derivationStrategy, null, null, smartifier); keychain = new BTCPayKeyChain(explorerClient, derivationStrategy, null, null, smartifier);
} }
@@ -127,9 +128,11 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
_serviceProvider.GetRequiredService<PullPaymentHostedService>(),derivationStrategy, explorerClient, keychain, _serviceProvider.GetRequiredService<PullPaymentHostedService>(),derivationStrategy, explorerClient, keychain,
name, wabisabiStoreSettings, UtxoLocker, name, wabisabiStoreSettings, UtxoLocker,
_loggerFactory, _loggerFactory,
_serviceProvider.GetRequiredService<StoreRepository>()); _serviceProvider.GetRequiredService<StoreRepository>(),
_serviceProvider.GetRequiredService<IMemoryCache>()
);
}); }, _logger);
} }
@@ -231,7 +234,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
LoadedWallets.TryRemove(name, out var walletTask); LoadedWallets.TryRemove(name, out var walletTask);
if (walletTask != null) if (walletTask != null)
{ {
var wallet = await walletTask; var wallet = await walletTask.Value;
WalletUnloaded?.Invoke(this, new WalletUnloadEventArgs(wallet)); WalletUnloaded?.Invoke(this, new WalletUnloadEventArgs(wallet));
} }
} }
@@ -247,7 +250,7 @@ public class WalletProvider : PeriodicRunner,IWalletProvider
{ {
_cachedSettings.AddOrReplace(storeId, wabisabiSettings); _cachedSettings.AddOrReplace(storeId, wabisabiSettings);
var btcpayWalet = (BTCPayWallet) await existingWallet; var btcpayWalet = (BTCPayWallet) await existingWallet.Value;
if (btcpayWalet is null) if (btcpayWalet is null)
{ {