mirror of
https://github.com/aljazceru/BTCPayServerPlugins.git
synced 2025-12-17 07:34:24 +01:00
fix ws
This commit is contained in:
@@ -11,6 +11,7 @@ using WalletWasabi.Blockchain.TransactionOutputs;
|
|||||||
using WalletWasabi.Crypto.Randomness;
|
using WalletWasabi.Crypto.Randomness;
|
||||||
using WalletWasabi.Extensions;
|
using WalletWasabi.Extensions;
|
||||||
using WalletWasabi.Helpers;
|
using WalletWasabi.Helpers;
|
||||||
|
using WalletWasabi.WabiSabi;
|
||||||
using WalletWasabi.WabiSabi.Backend.Rounds;
|
using WalletWasabi.WabiSabi.Backend.Rounds;
|
||||||
using WalletWasabi.WabiSabi.Client;
|
using WalletWasabi.WabiSabi.Client;
|
||||||
using WalletWasabi.Wallets;
|
using WalletWasabi.Wallets;
|
||||||
@@ -20,12 +21,10 @@ namespace BTCPayServer.Plugins.Wabisabi;
|
|||||||
public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
|
public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
|
||||||
{
|
{
|
||||||
private readonly BTCPayWallet _wallet;
|
private readonly BTCPayWallet _wallet;
|
||||||
private readonly ILogger _logger;
|
|
||||||
|
|
||||||
public BTCPayCoinjoinCoinSelector(BTCPayWallet wallet, ILogger logger)
|
public BTCPayCoinjoinCoinSelector(BTCPayWallet wallet)
|
||||||
{
|
{
|
||||||
_wallet = wallet;
|
_wallet = wallet;
|
||||||
_logger = logger;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<ImmutableList<SmartCoin>> SelectCoinsAsync(IEnumerable<SmartCoin> coinCandidates,
|
public async Task<ImmutableList<SmartCoin>> SelectCoinsAsync(IEnumerable<SmartCoin> coinCandidates,
|
||||||
@@ -98,12 +97,13 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
|
|||||||
ConsolidationModeType.WhenLowFeeAndManyUTXO => isLowFee && coinCandidates.Count() > BTCPayWallet.HighAmountOfCoins,
|
ConsolidationModeType.WhenLowFeeAndManyUTXO => isLowFee && coinCandidates.Count() > BTCPayWallet.HighAmountOfCoins,
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
};
|
};
|
||||||
|
Dictionary<AnonsetType, int> idealMinimumPerType = new Dictionary<AnonsetType, int>()
|
||||||
|
{{AnonsetType.Red, 1}, {AnonsetType.Orange, 1}, {AnonsetType.Green, 1}};
|
||||||
|
|
||||||
var solution = await SelectCoinsInternal(utxoSelectionParameters, coinCandidates, payments,
|
var solution = await SelectCoinsInternal(utxoSelectionParameters, coinCandidates, payments,
|
||||||
Random.Shared.Next(10, 31),
|
Random.Shared.Next(10, 31),
|
||||||
maxPerType,
|
maxPerType,
|
||||||
new Dictionary<AnonsetType, int>() {{AnonsetType.Red, 1}, {AnonsetType.Orange, 1}, {AnonsetType.Green, 1}},
|
idealMinimumPerType,
|
||||||
consolidationMode, liquidityClue, secureRandom);
|
consolidationMode, liquidityClue, secureRandom);
|
||||||
|
|
||||||
if (attemptingTobeParanoid && !solution.HandledPayments.Any())
|
if (attemptingTobeParanoid && !solution.HandledPayments.Any())
|
||||||
@@ -119,7 +119,7 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
|
|||||||
attemptingToMixToOtherWallet = false;
|
attemptingToMixToOtherWallet = false;
|
||||||
goto selectCoins;
|
goto selectCoins;
|
||||||
}
|
}
|
||||||
_logger.LogTrace(solution.ToString());
|
_wallet.LogTrace(solution.ToString());
|
||||||
return solution.Coins.ToImmutableList();
|
return solution.Coins.ToImmutableList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,19 +140,26 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
|
|||||||
var solution = new SubsetSolution(remainingPendingPayments.Count, _wallet.AnonScoreTarget,
|
var solution = new SubsetSolution(remainingPendingPayments.Count, _wallet.AnonScoreTarget,
|
||||||
utxoSelectionParameters);
|
utxoSelectionParameters);
|
||||||
|
|
||||||
|
var cv = new CoinsView(remainingCoins);
|
||||||
|
var percentage = await _wallet.GetPrivacyPercentageAsync(cv);
|
||||||
var fullyPrivate = await _wallet.IsWalletPrivateAsync(new CoinsView(remainingCoins));
|
var fullyPrivate = await _wallet.IsWalletPrivateAsync(new CoinsView(remainingCoins));
|
||||||
var coinjoiningOnlyForPayments = fullyPrivate && remainingPendingPayments.Any();
|
var coinjoiningOnlyForPayments = fullyPrivate && remainingPendingPayments.Any();
|
||||||
|
|
||||||
|
if (!consolidationMode && percentage < 1 && _wallet.ConsolidationMode != ConsolidationModeType.Never)
|
||||||
|
{
|
||||||
|
consolidationMode = true;
|
||||||
|
}
|
||||||
|
solution.ConsolidationMode = consolidationMode;
|
||||||
if (fullyPrivate && !coinjoiningOnlyForPayments )
|
if (fullyPrivate && !coinjoiningOnlyForPayments )
|
||||||
{
|
{
|
||||||
var rand = Random.Shared.Next(1, 1001);
|
var rand = Random.Shared.Next(1, 1001);
|
||||||
if (rand > _wallet.WabisabiStoreSettings.ExtraJoinProbability)
|
if (rand > _wallet.WabisabiStoreSettings.ExtraJoinProbability)
|
||||||
{
|
{
|
||||||
_logger.LogTrace($"All coins are private and we have no pending payments. Skipping join.");
|
_wallet.LogTrace($"All coins are private and we have no pending payments. Skipping join.");
|
||||||
return solution;
|
return solution;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogTrace(
|
_wallet.LogTrace(
|
||||||
"All coins are private and we have no pending payments but will join just to reduce timing analysis");
|
"All coins are private and we have no pending payments but will join just to reduce timing analysis");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,6 +241,8 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//if we have less than the max suggested output registration, we should add more coins to reach that number to avoid breaking up into too many coins?
|
//if we have less than the max suggested output registration, we should add more coins to reach that number to avoid breaking up into too many coins?
|
||||||
var isLessThanMaxOutputRegistration = solution.Coins.Count < Math.Max(solution.HandledPayments.Count +1, 8);
|
var isLessThanMaxOutputRegistration = solution.Coins.Count < Math.Max(solution.HandledPayments.Count +1, 8);
|
||||||
var rand = Random.Shared.Next(1, 101);
|
var rand = Random.Shared.Next(1, 101);
|
||||||
@@ -253,10 +262,17 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
|
|||||||
chance -= maxCoinCapacityPercentage;
|
chance -= maxCoinCapacityPercentage;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.LogDebug($"coin selection: no payments left but at {solution.Coins.Count()} coins. random chance to add another coin if: {chance} > {rand} (random 0-100) continue: {chance > rand}");
|
|
||||||
|
|
||||||
if (chance <= rand)
|
if (chance <= rand)
|
||||||
{
|
{
|
||||||
|
if (_wallet.MinimumDenominationAmount is not null &&
|
||||||
|
Money.Coins(solution.LeftoverValue).Satoshi < _wallet.MinimumDenominationAmount)
|
||||||
|
{
|
||||||
|
_wallet.LogDebug(
|
||||||
|
$"coin selection: leftover value {solution.LeftoverValue} is less than minimum denomination amount {_wallet.MinimumDenominationAmount} so we will try to add more coins");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_wallet.LogDebug($"coin selection: no payments left but at {solution.Coins.Count()} coins. random chance to add another coin if: {chance} > {rand} (random 0-100) continue: {chance > rand}");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +281,7 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
|
|||||||
|
|
||||||
if (coinjoiningOnlyForPayments && solution.HandledPayments?.Any() is not true)
|
if (coinjoiningOnlyForPayments && solution.HandledPayments?.Any() is not true)
|
||||||
{
|
{
|
||||||
_logger.LogInformation(
|
_wallet.LogInfo(
|
||||||
"Attempted to coinjoin only to fulfill payments but the coin selection results yielded no handled payment.");
|
"Attempted to coinjoin only to fulfill payments but the coin selection results yielded no handled payment.");
|
||||||
return new SubsetSolution(remainingPendingPayments.Count, _wallet.AnonScoreTarget,
|
return new SubsetSolution(remainingPendingPayments.Count, _wallet.AnonScoreTarget,
|
||||||
utxoSelectionParameters);
|
utxoSelectionParameters);
|
||||||
@@ -326,8 +342,6 @@ public static class SmartCoinExtensions
|
|||||||
public static AnonsetType CoinColor(this SmartCoin coin, int anonsetTarget)
|
public static AnonsetType CoinColor(this SmartCoin coin, int anonsetTarget)
|
||||||
{
|
{
|
||||||
return coin.IsPrivate(anonsetTarget)? AnonsetType.Green: coin.IsSemiPrivate(anonsetTarget)? AnonsetType.Orange: AnonsetType.Red;
|
return coin.IsPrivate(anonsetTarget)? AnonsetType.Green: coin.IsSemiPrivate(anonsetTarget)? AnonsetType.Orange: AnonsetType.Red;
|
||||||
return coin.AnonymitySet <= 1 ? AnonsetType.Red :
|
|
||||||
coin.AnonymitySet >= anonsetTarget ? AnonsetType.Green : AnonsetType.Orange;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,10 +379,10 @@ public class SubsetSolution
|
|||||||
payment.ToTxOut().EffectiveCost(_utxoSelectionParameters.MiningFeeRate).ToDecimal(MoneyUnit.BTC));
|
payment.ToTxOut().EffectiveCost(_utxoSelectionParameters.MiningFeeRate).ToDecimal(MoneyUnit.BTC));
|
||||||
|
|
||||||
public decimal LeftoverValue => TotalValue - TotalPaymentCost;
|
public decimal LeftoverValue => TotalValue - TotalPaymentCost;
|
||||||
|
public bool ConsolidationMode { get; set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
|
||||||
if (!Coins.Any())
|
if (!Coins.Any())
|
||||||
{
|
{
|
||||||
return "Solution yielded no selection of coins";
|
return "Solution yielded no selection of coins";
|
||||||
@@ -378,20 +392,8 @@ public class SubsetSolution
|
|||||||
sc.TryGetValue(AnonsetType.Green, out var gcoins);
|
sc.TryGetValue(AnonsetType.Green, out var gcoins);
|
||||||
sc.TryGetValue(AnonsetType.Orange, out var ocoins);
|
sc.TryGetValue(AnonsetType.Orange, out var ocoins);
|
||||||
sc.TryGetValue(AnonsetType.Red, out var rcoins);
|
sc.TryGetValue(AnonsetType.Red, out var rcoins);
|
||||||
sb.AppendLine(
|
|
||||||
$"Solution total coins:{Coins.Count} R:{rcoins?.Length ?? 0} O:{ocoins?.Length ?? 0} G:{gcoins?.Length ?? 0} AL:{GetAnonLoss(Coins)} total value: {TotalValue} total payments:{TotalPaymentCost}/{TotalPaymentsGross} leftover: {LeftoverValue}");
|
|
||||||
if (HandledPayments.Any())
|
|
||||||
sb.AppendLine($"handled payments: {string.Join(", ", HandledPayments.Select(p => p.Value))} ");
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static decimal GetAnonLoss<TCoin>(IEnumerable<TCoin> coins)
|
return $"Selected {Coins.Count} ({TotalValue} BTC) ({ocoins?.Length + rcoins?.Length} not private, {gcoins?.Length ?? 0} private) coins to pay {TotalPaymentsGross} payments ({TotalPaymentCost} BTC) with {LeftoverValue} BTC leftover\n Consolidation mode:{ConsolidationMode}";
|
||||||
where TCoin : SmartCoin
|
|
||||||
{
|
|
||||||
double minimumAnonScore = coins.Min(x => x.AnonymitySet);
|
|
||||||
var rawSum = coins.Sum(x => x.Amount);
|
|
||||||
return coins.Sum(x =>
|
|
||||||
((decimal)x.AnonymitySet - (decimal)minimumAnonScore) * x.Amount.ToDecimal(MoneyUnit.BTC)) / rawSum;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Product>Coinjoin</Product>
|
<Product>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.67</Version>
|
<Version>1.0.68</Version>
|
||||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
|
|||||||
// public readonly IBTCPayServerClientFactory BtcPayServerClientFactory;
|
// public readonly IBTCPayServerClientFactory BtcPayServerClientFactory;
|
||||||
public WabisabiStoreSettings WabisabiStoreSettings;
|
public WabisabiStoreSettings WabisabiStoreSettings;
|
||||||
public readonly IUTXOLocker UtxoLocker;
|
public readonly IUTXOLocker UtxoLocker;
|
||||||
public readonly ILogger Logger;
|
// public readonly ILogger Logger;
|
||||||
public static readonly BlockchainAnalyzer BlockchainAnalyzer = new();
|
public static readonly BlockchainAnalyzer BlockchainAnalyzer = new();
|
||||||
|
|
||||||
public BTCPayWallet(
|
public BTCPayWallet(
|
||||||
@@ -85,12 +85,12 @@ public class BTCPayWallet : IWallet, IDestinationProvider
|
|||||||
UtxoLocker = utxoLocker;
|
UtxoLocker = utxoLocker;
|
||||||
_storeRepository = storeRepository;
|
_storeRepository = storeRepository;
|
||||||
_memoryCache = memoryCache;
|
_memoryCache = memoryCache;
|
||||||
Logger = loggerFactory.CreateLogger($"BTCPayWallet_{storeId}");
|
_logger = loggerFactory.CreateLogger($"BTCPayWallet_{storeId}");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string StoreId { get; set; }
|
public string StoreId { get; set; }
|
||||||
public List<(Microsoft.Extensions.Logging.LogLevel, string)> LastLogs { get; private set; } = new();
|
public List<(DateTimeOffset time, Microsoft.Extensions.Logging.LogLevel level , string message)> LastLogs { get; private set; } = new();
|
||||||
public void Log(LogLevel logLevel, string logMessage, string callerFilePath = "", string callerMemberName = "",
|
public void Log(LogLevel logLevel, string logMessage, string callerFilePath = "", string callerMemberName = "",
|
||||||
int callerLineNumber = -1)
|
int callerLineNumber = -1)
|
||||||
{
|
{
|
||||||
@@ -104,12 +104,12 @@ public class BTCPayWallet : IWallet, IDestinationProvider
|
|||||||
LogLevel.Critical => Microsoft.Extensions.Logging.LogLevel.Critical,
|
LogLevel.Critical => Microsoft.Extensions.Logging.LogLevel.Critical,
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(logLevel))
|
_ => throw new ArgumentOutOfRangeException(nameof(logLevel))
|
||||||
};
|
};
|
||||||
if(LastLogs.FirstOrDefault().Item2 != logMessage)
|
if(LastLogs.FirstOrDefault().message != logMessage)
|
||||||
LastLogs.Insert(0, (ll, logMessage) );
|
LastLogs.Insert(0, (DateTimeOffset.Now, ll, logMessage) );
|
||||||
if (LastLogs.Count >= 100)
|
if (LastLogs.Count >= 100)
|
||||||
LastLogs.RemoveLast();
|
LastLogs.RemoveLast();
|
||||||
|
|
||||||
Logger.Log(ll, logMessage, callerFilePath, callerMemberName, callerLineNumber);
|
_logger.Log(ll, logMessage, callerFilePath, callerMemberName, callerLineNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string WalletName => StoreId;
|
public string WalletName => StoreId;
|
||||||
@@ -147,16 +147,16 @@ public class BTCPayWallet : IWallet, IDestinationProvider
|
|||||||
|
|
||||||
public async Task<bool> IsWalletPrivateAsync(CoinsView coins)
|
public async Task<bool> IsWalletPrivateAsync(CoinsView coins)
|
||||||
{
|
{
|
||||||
var privacy= GetPrivacyPercentage(coins, AnonScoreTarget);
|
var privacy= await GetPrivacyPercentageAsync(coins);
|
||||||
var mixToOtherWallet = !WabisabiStoreSettings.PlebMode && !string.IsNullOrEmpty(WabisabiStoreSettings
|
var mixToOtherWallet = !WabisabiStoreSettings.PlebMode && !string.IsNullOrEmpty(WabisabiStoreSettings
|
||||||
.MixToOtherWallet);
|
.MixToOtherWallet);
|
||||||
var forceConsolidate = ConsolidationMode == ConsolidationModeType.WhenLowFeeAndManyUTXO && coins.Available().Confirmed().Count() > HighAmountOfCoins;
|
var forceConsolidate = ConsolidationMode == ConsolidationModeType.WhenLowFeeAndManyUTXO && coins.Available().Confirmed().Count() > HighAmountOfCoins;
|
||||||
return !BatchPayments && privacy >= 1 && !mixToOtherWallet && !forceConsolidate;
|
return !BatchPayments && privacy >= 1 && !mixToOtherWallet && !forceConsolidate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<double> GetPrivacyPercentageAsync()
|
public async Task<double> GetPrivacyPercentageAsync(CoinsView coins)
|
||||||
{
|
{
|
||||||
return GetPrivacyPercentage(await GetAllCoins(), AnonScoreTarget);
|
return GetPrivacyPercentage(coins, AnonScoreTarget);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CoinsView> GetAllCoins()
|
public async Task<CoinsView> GetAllCoins()
|
||||||
@@ -199,7 +199,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
|
|||||||
|
|
||||||
public IRoundCoinSelector GetCoinSelector()
|
public IRoundCoinSelector GetCoinSelector()
|
||||||
{
|
{
|
||||||
_coinSelector??= new BTCPayCoinjoinCoinSelector(this, Logger );
|
_coinSelector??= new BTCPayCoinjoinCoinSelector(this );
|
||||||
return _coinSelector;
|
return _coinSelector;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,9 +213,8 @@ public class BTCPayWallet : IWallet, IDestinationProvider
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if(await finishedCoinJoin.CoinJoinTask is not SuccessfulCoinJoinResult successfulCoinJoinResult)
|
||||||
var successfulCoinJoinResult = (await finishedCoinJoin.CoinJoinTask) as SuccessfulCoinJoinResult;
|
return;
|
||||||
|
|
||||||
await RegisterCoinjoinTransaction(successfulCoinJoinResult,
|
await RegisterCoinjoinTransaction(successfulCoinJoinResult,
|
||||||
finishedCoinJoin.CoinJoinClient.CoordinatorName);
|
finishedCoinJoin.CoinJoinClient.CoordinatorName);
|
||||||
}
|
}
|
||||||
@@ -410,6 +409,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Task _savingProgress = Task.CompletedTask;
|
private Task _savingProgress = Task.CompletedTask;
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public async Task RegisterCoinjoinTransaction(SuccessfulCoinJoinResult result, string coordinatorName)
|
public async Task RegisterCoinjoinTransaction(SuccessfulCoinJoinResult result, string coordinatorName)
|
||||||
{
|
{
|
||||||
@@ -419,209 +419,216 @@ public class BTCPayWallet : IWallet, IDestinationProvider
|
|||||||
}
|
}
|
||||||
private async Task RegisterCoinjoinTransactionInternal(SuccessfulCoinJoinResult result, string coordinatorName)
|
private async Task RegisterCoinjoinTransactionInternal(SuccessfulCoinJoinResult result, string coordinatorName)
|
||||||
{
|
{
|
||||||
try
|
var attempts = 0;
|
||||||
|
while (attempts < 5)
|
||||||
{
|
{
|
||||||
var stopwatch = new Stopwatch();
|
//wait longer between attempts
|
||||||
stopwatch.Start();
|
await Task.Delay(TimeSpan.FromSeconds(attempts * 3));
|
||||||
var txHash = result.UnsignedCoinJoin.GetHash();
|
|
||||||
var kp = await ExplorerClient.GetMetadataAsync<RootedKeyPath>(DerivationScheme,
|
|
||||||
WellknownMetadataKeys.AccountKeyPath);
|
|
||||||
|
|
||||||
var storeIdForutxo = WabisabiStoreSettings.PlebMode ||
|
|
||||||
string.IsNullOrEmpty(WabisabiStoreSettings.MixToOtherWallet)? StoreId: WabisabiStoreSettings.MixToOtherWallet;
|
|
||||||
var utxoDerivationScheme = DerivationScheme;
|
|
||||||
if (storeIdForutxo != StoreId)
|
|
||||||
{
|
|
||||||
var s = await _storeRepository.FindStore(storeIdForutxo);
|
|
||||||
var scheme = s.GetDerivationSchemeSettings(_btcPayNetworkProvider, "BTC");
|
|
||||||
utxoDerivationScheme = scheme.AccountDerivation;
|
|
||||||
}
|
|
||||||
List<(IndexedTxOut txout, Task<KeyPathInformation>)> scriptInfos = new();
|
|
||||||
|
|
||||||
|
|
||||||
Dictionary<IndexedTxOut, PendingPayment> indexToPayment = new();
|
|
||||||
foreach (var script in result.Outputs)
|
|
||||||
{
|
|
||||||
var txout = result.UnsignedCoinJoin.Outputs.AsIndexedOutputs()
|
|
||||||
.Single(@out => @out.TxOut.ScriptPubKey == script.ScriptPubKey && @out.TxOut.Value == script.Value);
|
|
||||||
|
|
||||||
|
|
||||||
//this was not a mix to self, but rather a payment
|
|
||||||
var isPayment = result.HandledPayments.Where(pair =>
|
|
||||||
pair.Key.ScriptPubKey == txout.TxOut.ScriptPubKey && pair.Key.Value == txout.TxOut.Value);
|
|
||||||
if (isPayment.Any())
|
|
||||||
{
|
|
||||||
indexToPayment.Add(txout, isPayment.First().Value);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var privateEnough = result.Coins.All(c => c.AnonymitySet >= WabisabiStoreSettings.AnonymitySetTarget );
|
|
||||||
scriptInfos.Add((txout, ExplorerClient.GetKeyInformationAsync(BlockchainAnalyzer.StdDenoms.Contains(txout.TxOut.Value)&& privateEnough?utxoDerivationScheme:DerivationScheme, script.ScriptPubKey)));
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.WhenAll(scriptInfos.Select(t => t.Item2));
|
|
||||||
var scriptInfos2 = scriptInfos.Where(tuple => tuple.Item2.Result is not null).ToDictionary(tuple => tuple.txout.TxOut.ScriptPubKey);
|
|
||||||
var smartTx = new SmartTransaction(result.UnsignedCoinJoin, new Height(HeightType.Unknown));
|
|
||||||
result.Coins.ForEach(coin =>
|
|
||||||
{
|
|
||||||
coin.HdPubKey.SetKeyState(KeyState.Used);
|
|
||||||
coin.SpenderTransaction = smartTx;
|
|
||||||
smartTx.TryAddWalletInput(SmartCoin.Clone(coin));
|
|
||||||
});
|
|
||||||
result.Outputs.ForEach(s =>
|
|
||||||
{
|
|
||||||
if (scriptInfos2.TryGetValue(s.ScriptPubKey, out var si))
|
|
||||||
{
|
|
||||||
var derivation = DerivationScheme.GetChild(si.Item2.Result.KeyPath).GetExtPubKeys().First().PubKey;
|
|
||||||
|
|
||||||
var hdPubKey = new HdPubKey(derivation, kp.Derive(si.Item2.Result.KeyPath).KeyPath,
|
|
||||||
LabelsArray.Empty,
|
|
||||||
KeyState.Used);
|
|
||||||
|
|
||||||
var coin = new SmartCoin(smartTx, si.txout.N, hdPubKey);
|
|
||||||
smartTx.TryAddWalletOutput(coin);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
attempts++;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
BlockchainAnalyzer.Analyze(smartTx);
|
var stopwatch = new Stopwatch();
|
||||||
|
stopwatch.Start();
|
||||||
|
var txHash = result.UnsignedCoinJoin.GetHash();
|
||||||
|
var kp = await ExplorerClient.GetMetadataAsync<RootedKeyPath>(DerivationScheme,
|
||||||
|
WellknownMetadataKeys.AccountKeyPath);
|
||||||
|
|
||||||
|
var storeIdForutxo = WabisabiStoreSettings.PlebMode ||
|
||||||
|
string.IsNullOrEmpty(WabisabiStoreSettings.MixToOtherWallet)
|
||||||
|
? StoreId
|
||||||
|
: WabisabiStoreSettings.MixToOtherWallet;
|
||||||
|
var utxoDerivationScheme = DerivationScheme;
|
||||||
|
if (storeIdForutxo != StoreId)
|
||||||
|
{
|
||||||
|
var s = await _storeRepository.FindStore(storeIdForutxo);
|
||||||
|
var scheme = s.GetDerivationSchemeSettings(_btcPayNetworkProvider, "BTC");
|
||||||
|
utxoDerivationScheme = scheme.AccountDerivation;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<(IndexedTxOut txout, Task<KeyPathInformation>)> scriptInfos = new();
|
||||||
|
|
||||||
|
|
||||||
|
Dictionary<IndexedTxOut, PendingPayment> indexToPayment = new();
|
||||||
|
foreach (var script in result.Outputs)
|
||||||
|
{
|
||||||
|
var txout = result.UnsignedCoinJoin.Outputs.AsIndexedOutputs()
|
||||||
|
.Single(@out =>
|
||||||
|
@out.TxOut.ScriptPubKey == script.ScriptPubKey && @out.TxOut.Value == script.Value);
|
||||||
|
|
||||||
|
|
||||||
|
//this was not a mix to self, but rather a payment
|
||||||
|
var isPayment = result.HandledPayments.Where(pair =>
|
||||||
|
pair.Key.ScriptPubKey == txout.TxOut.ScriptPubKey && pair.Key.Value == txout.TxOut.Value);
|
||||||
|
if (isPayment.Any())
|
||||||
|
{
|
||||||
|
indexToPayment.Add(txout, isPayment.First().Value);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var privateEnough =
|
||||||
|
result.Coins.All(c => c.AnonymitySet >= WabisabiStoreSettings.AnonymitySetTarget);
|
||||||
|
scriptInfos.Add((txout,
|
||||||
|
ExplorerClient.GetKeyInformationAsync(
|
||||||
|
BlockchainAnalyzer.StdDenoms.Contains(txout.TxOut.Value) && privateEnough
|
||||||
|
? utxoDerivationScheme
|
||||||
|
: DerivationScheme, script.ScriptPubKey)));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.WhenAll(scriptInfos.Select(t => t.Item2));
|
||||||
|
var scriptInfos2 = scriptInfos.Where(tuple => tuple.Item2.Result is not null)
|
||||||
|
.ToDictionary(tuple => tuple.txout.TxOut.ScriptPubKey);
|
||||||
|
var smartTx = new SmartTransaction(result.UnsignedCoinJoin, new Height(HeightType.Unknown));
|
||||||
|
result.Coins.ForEach(coin =>
|
||||||
|
{
|
||||||
|
coin.HdPubKey.SetKeyState(KeyState.Used);
|
||||||
|
coin.SpenderTransaction = smartTx;
|
||||||
|
smartTx.TryAddWalletInput(SmartCoin.Clone(coin));
|
||||||
|
});
|
||||||
|
result.Outputs.ForEach(s =>
|
||||||
|
{
|
||||||
|
if (scriptInfos2.TryGetValue(s.ScriptPubKey, out var si))
|
||||||
|
{
|
||||||
|
var derivation = DerivationScheme.GetChild(si.Item2.Result.KeyPath).GetExtPubKeys().First()
|
||||||
|
.PubKey;
|
||||||
|
|
||||||
|
var hdPubKey = new HdPubKey(derivation, kp.Derive(si.Item2.Result.KeyPath).KeyPath,
|
||||||
|
LabelsArray.Empty,
|
||||||
|
KeyState.Used);
|
||||||
|
|
||||||
|
var coin = new SmartCoin(smartTx, si.txout.N, hdPubKey);
|
||||||
|
smartTx.TryAddWalletOutput(coin);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
BlockchainAnalyzer.Analyze(smartTx);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
this.LogError($"Failed to analyze anonsets of tx {smartTx.GetHash()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
var cjData = new CoinjoinData()
|
||||||
|
{
|
||||||
|
Round = result.RoundId.ToString(),
|
||||||
|
CoordinatorName = coordinatorName,
|
||||||
|
Transaction = txHash.ToString(),
|
||||||
|
CoinsIn = result.Coins.Select(coin => new CoinjoinData.CoinjoinDataCoin()
|
||||||
|
{
|
||||||
|
AnonymitySet = coin.AnonymitySet,
|
||||||
|
PayoutId = null,
|
||||||
|
Amount = coin.Amount.ToDecimal(MoneyUnit.BTC),
|
||||||
|
Outpoint = coin.Outpoint.ToString()
|
||||||
|
}).ToArray(),
|
||||||
|
CoinsOut = smartTx.WalletOutputs.Select(coin => new CoinjoinData.CoinjoinDataCoin()
|
||||||
|
{
|
||||||
|
AnonymitySet = coin.AnonymitySet,
|
||||||
|
PayoutId = null,
|
||||||
|
Amount = coin.Amount.ToDecimal(MoneyUnit.BTC),
|
||||||
|
Outpoint = coin.Outpoint.ToString()
|
||||||
|
}).Concat(indexToPayment.Select(pair => new CoinjoinData.CoinjoinDataCoin()
|
||||||
|
{
|
||||||
|
Amount = pair.Key.TxOut.Value.ToDecimal(MoneyUnit.BTC),
|
||||||
|
PayoutId = pair.Value.Identifier,
|
||||||
|
Outpoint = new OutPoint(result.UnsignedCoinJoin, pair.Key.N).ToString()
|
||||||
|
})).ToArray()
|
||||||
|
};
|
||||||
|
foreach (var smartTxWalletOutput in smartTx.WalletOutputs)
|
||||||
|
{
|
||||||
|
Smartifier.SetIsSufficientlyDistancedFromExternalKeys(smartTxWalletOutput, cjData);
|
||||||
|
}
|
||||||
|
|
||||||
|
var attachments = new List<Attachment>()
|
||||||
|
{
|
||||||
|
new("coinjoin", result.RoundId.ToString(), JObject.FromObject(cjData)),
|
||||||
|
new(coordinatorName, null, null)
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
if (result.HandledPayments.Any())
|
||||||
|
{
|
||||||
|
attachments.AddRange(result.HandledPayments.Select(payment =>
|
||||||
|
new Attachment("payout", payment.Value.Identifier)));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<(WalletId wallet, string id, IEnumerable<Attachment> attachments, string type )> objects = new();
|
||||||
|
|
||||||
|
objects.Add((new WalletId(StoreId, "BTC"),
|
||||||
|
result.UnsignedCoinJoin.GetHash().ToString(),
|
||||||
|
attachments, "tx"));
|
||||||
|
|
||||||
|
var mixedCoins = smartTx.WalletOutputs.Where(coin =>
|
||||||
|
coin.AnonymitySet > 1 && BlockchainAnalyzer.StdDenoms.Contains(coin.TxOut.Value.Satoshi));
|
||||||
|
if (storeIdForutxo != StoreId)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
objects.Add((new WalletId(storeIdForutxo, "BTC"),
|
||||||
|
txHash.ToString(), new List<Attachment>()
|
||||||
|
{
|
||||||
|
new Attachment("coinjoin", result.RoundId.ToString(), JObject.FromObject(new CoinjoinData()
|
||||||
|
{
|
||||||
|
Transaction = txHash.ToString(),
|
||||||
|
Round = result.RoundId.ToString(),
|
||||||
|
CoinsOut = mixedCoins.Select(coin => new CoinjoinData.CoinjoinDataCoin()
|
||||||
|
{
|
||||||
|
AnonymitySet = coin.AnonymitySet,
|
||||||
|
Amount = coin.Amount.ToDecimal(MoneyUnit.BTC),
|
||||||
|
Outpoint = coin.Outpoint.ToString()
|
||||||
|
}).ToArray(),
|
||||||
|
CoordinatorName = coordinatorName
|
||||||
|
})),
|
||||||
|
new Attachment(coordinatorName, null, null)
|
||||||
|
}, "tx"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var mixedCoin in mixedCoins)
|
||||||
|
{
|
||||||
|
objects.Add((new WalletId(storeIdForutxo, "BTC"),
|
||||||
|
mixedCoin.Outpoint.ToString(),
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new Attachment("anonset", mixedCoin.AnonymitySet.ToString(), JObject.FromObject(new
|
||||||
|
{
|
||||||
|
Tooltip =
|
||||||
|
$"This coin has an anonset score of {mixedCoin.AnonymitySet.ToString()} (anonset-{mixedCoin.AnonymitySet.ToString()})"
|
||||||
|
}))
|
||||||
|
}, "utxo"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
_smartifier.SmartTransactions.AddOrReplace(txHash, Task.FromResult(smartTx));
|
||||||
|
smartTx.WalletOutputs.ForEach(coin =>
|
||||||
|
{
|
||||||
|
_smartifier.Coins.AddOrReplace(coin.Outpoint, Task.FromResult(coin));
|
||||||
|
});
|
||||||
|
|
||||||
|
await _walletRepository.AddWalletTransactionAttachments(objects.ToArray());
|
||||||
|
|
||||||
|
stopwatch.Stop();
|
||||||
|
|
||||||
|
this.LogInfo($"Registered coinjoin result for {StoreId} in {stopwatch.Elapsed}");
|
||||||
|
_memoryCache.Remove(WabisabiService.GetCacheKey(StoreId) + "cjhistory");
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.LogError($"Failed to analyze anonsets of tx {smartTx.GetHash()}");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
this.LogError( "Could not save coinjoin progress! " + e.Message);
|
||||||
|
// ignored
|
||||||
var cjData = new CoinjoinData()
|
|
||||||
{
|
|
||||||
Round = result.RoundId.ToString(),
|
|
||||||
CoordinatorName = coordinatorName,
|
|
||||||
Transaction = txHash.ToString(),
|
|
||||||
CoinsIn = result.Coins.Select(coin => new CoinjoinData.CoinjoinDataCoin()
|
|
||||||
{
|
|
||||||
AnonymitySet = coin.AnonymitySet,
|
|
||||||
PayoutId = null,
|
|
||||||
Amount = coin.Amount.ToDecimal(MoneyUnit.BTC),
|
|
||||||
Outpoint = coin.Outpoint.ToString()
|
|
||||||
}).ToArray(),
|
|
||||||
CoinsOut = smartTx.WalletOutputs.Select(coin => new CoinjoinData.CoinjoinDataCoin()
|
|
||||||
{
|
|
||||||
AnonymitySet = coin.AnonymitySet,
|
|
||||||
PayoutId = null,
|
|
||||||
Amount = coin.Amount.ToDecimal(MoneyUnit.BTC),
|
|
||||||
Outpoint = coin.Outpoint.ToString()
|
|
||||||
}).Concat(indexToPayment.Select(pair => new CoinjoinData.CoinjoinDataCoin()
|
|
||||||
{
|
|
||||||
Amount = pair.Key.TxOut.Value.ToDecimal(MoneyUnit.BTC),
|
|
||||||
PayoutId = pair.Value.Identifier,
|
|
||||||
Outpoint = new OutPoint(result.UnsignedCoinJoin, pair.Key.N).ToString()
|
|
||||||
})).ToArray()
|
|
||||||
};
|
|
||||||
foreach (var smartTxWalletOutput in smartTx.WalletOutputs)
|
|
||||||
{
|
|
||||||
Smartifier.SetIsSufficientlyDistancedFromExternalKeys(smartTxWalletOutput, cjData);
|
|
||||||
}
|
|
||||||
|
|
||||||
var attachments = new List<Attachment>()
|
|
||||||
{
|
|
||||||
new("coinjoin", result.RoundId.ToString(), JObject.FromObject(cjData)),
|
|
||||||
new(coordinatorName, null, null)
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
if (result.HandledPayments.Any())
|
|
||||||
{
|
|
||||||
attachments.AddRange(result.HandledPayments.Select(payment => new Attachment("payout", payment.Value.Identifier)));
|
|
||||||
}
|
|
||||||
List<(WalletId wallet, string id, IEnumerable<Attachment> attachments, string type )> objects = new();
|
|
||||||
|
|
||||||
objects.Add((new WalletId(StoreId, "BTC"),
|
|
||||||
result.UnsignedCoinJoin.GetHash().ToString(),
|
|
||||||
attachments, "tx"));
|
|
||||||
|
|
||||||
var mixedCoins = smartTx.WalletOutputs.Where(coin =>
|
|
||||||
coin.AnonymitySet > 1 && BlockchainAnalyzer.StdDenoms.Contains(coin.TxOut.Value.Satoshi));
|
|
||||||
if (storeIdForutxo != StoreId)
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
objects.Add((new WalletId(storeIdForutxo, "BTC"),
|
|
||||||
txHash.ToString(), new List<Attachment>()
|
|
||||||
{
|
|
||||||
new Attachment("coinjoin", result.RoundId.ToString(), JObject.FromObject(new CoinjoinData()
|
|
||||||
{
|
|
||||||
Transaction = txHash.ToString(),
|
|
||||||
Round = result.RoundId.ToString(),
|
|
||||||
CoinsOut = mixedCoins.Select(coin => new CoinjoinData.CoinjoinDataCoin()
|
|
||||||
{
|
|
||||||
AnonymitySet = coin.AnonymitySet,
|
|
||||||
Amount = coin.Amount.ToDecimal(MoneyUnit.BTC),
|
|
||||||
Outpoint = coin.Outpoint.ToString()
|
|
||||||
}).ToArray(),
|
|
||||||
CoordinatorName = coordinatorName
|
|
||||||
})),
|
|
||||||
new Attachment(coordinatorName, null, null)
|
|
||||||
}, "tx"));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var mixedCoin in mixedCoins)
|
|
||||||
{
|
|
||||||
objects.Add((new WalletId(storeIdForutxo, "BTC"),
|
|
||||||
mixedCoin.Outpoint.ToString(),
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
new Attachment("anonset", mixedCoin.AnonymitySet.ToString(), JObject.FromObject(new
|
|
||||||
{
|
|
||||||
Tooltip =
|
|
||||||
$"This coin has an anonset score of {mixedCoin.AnonymitySet.ToString()} (anonset-{mixedCoin.AnonymitySet.ToString()})"
|
|
||||||
}))
|
|
||||||
}, "utxo"));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
_smartifier.SmartTransactions.AddOrReplace(txHash, Task.FromResult(smartTx));
|
|
||||||
smartTx.WalletOutputs.ForEach(coin =>
|
|
||||||
{
|
|
||||||
_smartifier.Coins.AddOrReplace(coin.Outpoint, Task.FromResult(coin));
|
|
||||||
});
|
|
||||||
|
|
||||||
await _walletRepository.AddWalletTransactionAttachments(objects.ToArray());
|
|
||||||
|
|
||||||
stopwatch.Stop();
|
|
||||||
|
|
||||||
Logger.LogInformation($"Registered coinjoin result for {StoreId} in {stopwatch.Elapsed}");
|
|
||||||
_memoryCache.Remove(WabisabiService.GetCacheKey(StoreId) + "cjhistory");
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
Logger.LogError(e, "Could not save coinjoin progress!");
|
|
||||||
// ignored
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// public async Task UnlockUTXOs()
|
|
||||||
// {
|
|
||||||
// var client = await BtcPayServerClientFactory.Create(null, StoreId);
|
|
||||||
// var utxos = await client.GetOnChainWalletUTXOs(StoreId, "BTC");
|
|
||||||
// var unlocked = new List<string>();
|
|
||||||
// foreach (OnChainWalletUTXOData utxo in utxos)
|
|
||||||
// {
|
|
||||||
//
|
|
||||||
// if (await UtxoLocker.TryUnlock(utxo.Outpoint))
|
|
||||||
// {
|
|
||||||
// unlocked.Add(utxo.Outpoint.ToString());
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Logger.LogTrace($"unlocked utxos: {string.Join(',', unlocked)}");
|
|
||||||
// }
|
|
||||||
|
|
||||||
public async Task<IEnumerable<IDestination>> GetNextDestinationsAsync(int count, bool mixedOutputs, bool privateEnough)
|
public async Task<IEnumerable<IDestination>> GetNextDestinationsAsync(int count, bool mixedOutputs, bool privateEnough)
|
||||||
{
|
{
|
||||||
if (!WabisabiStoreSettings.PlebMode && !string.IsNullOrEmpty(WabisabiStoreSettings.MixToOtherWallet) && mixedOutputs && privateEnough)
|
if (!WabisabiStoreSettings.PlebMode && !string.IsNullOrEmpty(WabisabiStoreSettings.MixToOtherWallet) && mixedOutputs && privateEnough)
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
@inject WabisabiCoordinatorClientInstanceManager WabisabiCoordinatorClientInstanceManager
|
@inject WabisabiCoordinatorClientInstanceManager WabisabiCoordinatorClientInstanceManager
|
||||||
|
|
||||||
<script src="~/Resources/chart.js" type="text/javascript"> </script>
|
<script src="~/Resources/chart.js" type="text/javascript"> </script>
|
||||||
@inject IExplorerClientProvider ExplorerClientProvider
|
|
||||||
|
|
||||||
@{
|
@{
|
||||||
var available = true;
|
var available = true;
|
||||||
@@ -21,6 +20,7 @@
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var storeId = ScopeProvider.GetCurrentStoreId();
|
var storeId = ScopeProvider.GetCurrentStoreId();
|
||||||
Context.Items["cjlite"] = true;
|
Context.Items["cjlite"] = true;
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
@if (!enabledSettings.Any())
|
@if (!enabledSettings.Any())
|
||||||
{
|
{
|
||||||
<div class="widget">
|
<div class="widget">
|
||||||
<partial name="../WabisabiStore/UpdateWabisabiStoreSettings" model="@settings"/>
|
<partial name="../WabisabiStore/UpdateWabisabiStoreSettings" model="@settings"/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
var privacy = wallet.GetPrivacyPercentage(coins, wallet.AnonScoreTarget);
|
var privacy = wallet.GetPrivacyPercentage(coins, wallet.AnonScoreTarget);
|
||||||
|
|
||||||
var privacyPercentage = Math.Round(privacy * 100);
|
var privacyPercentage = Math.Round(privacy * 100);
|
||||||
var data = new
|
var data = new
|
||||||
{
|
{
|
||||||
privacyProgress = privacyPercentage,
|
privacyProgress = privacyPercentage,
|
||||||
targetScore = wallet.AnonScoreTarget,
|
targetScore = wallet.AnonScoreTarget,
|
||||||
@@ -78,11 +78,9 @@
|
|||||||
id = coin.Outpoint.ToString(),
|
id = coin.Outpoint.ToString(),
|
||||||
coinjoinInProgress = coin.CoinJoinInProgress
|
coinjoinInProgress = coin.CoinJoinInProgress
|
||||||
}).OrderBy(coin => coin.isPrivate).ThenBy(coin => coin.score),
|
}).OrderBy(coin => coin.isPrivate).ThenBy(coin => coin.score),
|
||||||
|
|
||||||
};
|
};
|
||||||
@if(coins.Any())
|
@if (coins.Any())
|
||||||
{
|
{
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
@@ -261,6 +259,7 @@ updateInProgressAnimation(myChart);
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div class="widget store-numbers">
|
<div class="widget store-numbers">
|
||||||
|
|
||||||
@if (wallet is { })
|
@if (wallet is { })
|
||||||
@@ -289,10 +288,11 @@ updateInProgressAnimation(myChart);
|
|||||||
|
|
||||||
@if (coins.Any())
|
@if (coins.Any())
|
||||||
{
|
{
|
||||||
<div class="d-flex justify-content-center mb-4" style="max-height: 400px; "> <canvas id="cjchart"></canvas></div>
|
<div class="d-flex justify-content-center mb-4" style="max-height: 400px; ">
|
||||||
|
<canvas id="cjchart"></canvas>
|
||||||
}
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div class="modal modal-lg fade" id="coins" data-bs-keyboard="false" tabindex="-1">
|
<div class="modal modal-lg fade" id="coins" data-bs-keyboard="false" tabindex="-1">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
@@ -372,6 +372,7 @@ updateInProgressAnimation(myChart);
|
|||||||
tracker?.CoinJoinClient?.RoundStatusUpdater?.RoundStates?.TryGetValue(tracker?.CoinJoinClient?.CurrentRoundId, out currentRound) is true)
|
tracker?.CoinJoinClient?.RoundStatusUpdater?.RoundStates?.TryGetValue(tracker?.CoinJoinClient?.CurrentRoundId, out currentRound) is true)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
var statusMsg = coordinator.WasabiCoordinatorStatusFetcher.Connected ? $"Connected to {(coordinator.Coordinator?.ToString() ?? "local")}" : $"Not connected to {(coordinator.Coordinator?.ToString() ?? "local")}";
|
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="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">
|
||||||
@@ -422,7 +423,9 @@ updateInProgressAnimation(myChart);
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="">Round id</th>
|
<th scope="">Round id</th>
|
||||||
<td class="text-truncate"><vc:truncate-center text="@currentRound.Id.ToString()" classes="truncate-center-id"></vc:truncate-center></td>
|
<td class="text-truncate">
|
||||||
|
<vc:truncate-center text="@currentRound.Id.ToString()" classes="truncate-center-id"></vc:truncate-center>
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="">Mining feerate</th>
|
<th scope="">Mining feerate</th>
|
||||||
@@ -439,7 +442,9 @@ updateInProgressAnimation(myChart);
|
|||||||
{
|
{
|
||||||
statement += $" / {tracker.CoinJoinClient.CoinsToRegister.Count()} inputs ({tracker.CoinJoinClient.CoinsToRegister.Sum(coin => coin.Amount.ToDecimal(MoneyUnit.BTC))} BTC)";
|
statement += $" / {tracker.CoinJoinClient.CoinsToRegister.Count()} inputs ({tracker.CoinJoinClient.CoinsToRegister.Sum(coin => coin.Amount.ToDecimal(MoneyUnit.BTC))} BTC)";
|
||||||
}
|
}
|
||||||
<tr>
|
|
||||||
|
var inputToolTip = tracker.CoinJoinClient.CoinsToRegister.Aggregate("", (current, coin) => current + $"{coin.Amount.ToDecimal(MoneyUnit.BTC)} BTC\n");
|
||||||
|
<tr data-bs-toggle="tooltip" title="@inputToolTip">
|
||||||
<th scope="">Your inputs</th>
|
<th scope="">Your inputs</th>
|
||||||
<td class="">
|
<td class="">
|
||||||
<span class="w-100">@statement</span>
|
<span class="w-100">@statement</span>
|
||||||
@@ -459,9 +464,11 @@ updateInProgressAnimation(myChart);
|
|||||||
</tr>
|
</tr>
|
||||||
if (tracker.CoinJoinClient.OutputTxOuts is { } outputs)
|
if (tracker.CoinJoinClient.OutputTxOuts is { } outputs)
|
||||||
{
|
{
|
||||||
var statement = $"{outputs.outputTxOuts.Count()} outputs ({outputs.outputTxOuts.Sum(coin => coin.Value.ToDecimal(MoneyUnit.BTC))} BTC {(outputs.batchedPayments.Any()? $"{outputs.batchedPayments.Count()} batched payments": "")}";
|
var statement = $"{outputs.outputTxOuts.Count()} outputs ({outputs.outputTxOuts.Sum(coin => coin.Value.ToDecimal(MoneyUnit.BTC))} BTC {(outputs.batchedPayments.Any() ? $"{outputs.batchedPayments.Count()} batched payments" : "")}";
|
||||||
|
var outputToolTip = outputs.outputTxOuts.Aggregate("", (current, output) => current + $"{output.Value.ToDecimal(MoneyUnit.BTC)} BTC\n");
|
||||||
<tr>
|
|
||||||
|
|
||||||
|
<tr data-bs-toggle="tooltip" title="@outputToolTip">
|
||||||
<th scope="">Your outputs</th>
|
<th scope="">Your outputs</th>
|
||||||
<td >@statement</td>
|
<td >@statement</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -470,8 +477,6 @@ updateInProgressAnimation(myChart);
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
@@ -481,16 +486,14 @@ updateInProgressAnimation(myChart);
|
|||||||
|
|
||||||
@if (coins.Any())
|
@if (coins.Any())
|
||||||
{
|
{
|
||||||
|
|
||||||
<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>
|
||||||
}
|
}
|
||||||
@if (wallet.LastLogs.Any())
|
@if (wallet.LastLogs.Any())
|
||||||
{
|
{
|
||||||
|
|
||||||
<button type="button" class="btn btn-text p-1" data-bs-toggle="modal" data-bs-target="#logs">
|
<button type="button" class="btn btn-text p-1" data-bs-toggle="modal" data-bs-target="#logs">
|
||||||
Recent logs
|
Recent logs
|
||||||
</button>
|
</button>
|
||||||
<div class="modal modal-lg fade" id="logs" data-bs-keyboard="false" tabindex="-1">
|
<div class="modal modal-lg fade" id="logs" data-bs-keyboard="false" tabindex="-1">
|
||||||
<div class="modal-dialog">
|
<div class="modal-dialog">
|
||||||
@@ -499,32 +502,41 @@ updateInProgressAnimation(myChart);
|
|||||||
<h3 class="mb-0">Coinjoin logs</h3>
|
<h3 class="mb-0">Coinjoin logs</h3>
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body table-responsive mt-0">
|
<div class="modal-body mt-0">
|
||||||
<table class="table">
|
<div class="table-responsive">
|
||||||
<thead>
|
|
||||||
<tr>
|
<table class="table">
|
||||||
<th>Message</th>
|
<thead>
|
||||||
</tr>
|
<tr>
|
||||||
</thead>
|
<th>Time</th>
|
||||||
<tbody>
|
<th>Message</th>
|
||||||
@foreach (var evt in wallet.LastLogs)
|
|
||||||
{
|
|
||||||
string cssClass = evt.Item1 <= (LogLevel) 2 ? "info" : evt.Item1 == (LogLevel) 4 ? "warning" : "danger";
|
|
||||||
<tr class="text-@cssClass">
|
|
||||||
<td>@evt.Item2</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
@foreach (var evt in wallet.LastLogs)
|
||||||
</div>
|
{
|
||||||
<div class="modal-footer">
|
string cssClass = evt.level <= (LogLevel) 2 ? "info" : evt.level == (LogLevel) 4 ? "warning" : "danger";
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
<tr class="text-@cssClass">
|
||||||
|
<td>
|
||||||
|
<small class="text-muted" data-timeago-unixms="@evt.time.ToUnixTimeMilliseconds()">@evt.time.ToTimeAgo()</small>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<pre>@evt.message</pre>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|||||||
Submodule submodules/walletwasabi updated: 847a659227...cebb57875c
Reference in New Issue
Block a user