mirror of
https://github.com/aljazceru/BTCPayServerPlugins.git
synced 2025-12-17 07:34:24 +01:00
558 lines
28 KiB
Plaintext
558 lines
28 KiB
Plaintext
@using BTCPayServer.Plugins.Wabisabi
|
|
@using BTCPayServer.Abstractions.Contracts
|
|
@using NBitcoin
|
|
@using System.Security.Claims
|
|
@using BTCPayServer
|
|
@using BTCPayServer.Client
|
|
@using BTCPayServer.Configuration
|
|
@using BTCPayServer.Services.Stores
|
|
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
|
@using WalletWasabi.Backend.Controllers
|
|
@using WalletWasabi.Blockchain.Analysis
|
|
@using WalletWasabi.Wallets
|
|
@model BTCPayServer.Plugins.Wabisabi.WabisabiStoreSettings
|
|
@inject WabisabiCoordinatorClientInstanceManager WabisabiCoordinatorClientInstanceManager
|
|
@inject IScopeProvider _scopeProvider
|
|
@inject StoreRepository StoreRepository
|
|
@inject WalletProvider WalletProvider
|
|
@inject BTCPayNetworkProvider BtcPayNetworkProvider
|
|
@inject BTCPayServerOptions BtcPayServerOptions
|
|
@{
|
|
var liteMode = Context.Items.TryGetValue("cjlite", out _);
|
|
|
|
var storeId = _scopeProvider.GetCurrentStoreId();
|
|
ViewData.SetActivePage("CoinjoinSettings", "Coinjoin", "Coinjoin settings", storeId);
|
|
var userid = Context.User.Claims.Single(claim => claim.Type == ClaimTypes.NameIdentifier).Value;
|
|
var anyEnabled = Model.Settings.Any(settings => settings.Enabled);
|
|
ScriptPubKeyType? scriptType;
|
|
|
|
var stores = (await StoreRepository.GetStoresByUserId(userid))
|
|
.ToDictionary(s => s.Id, s => (s, s.GetDerivationSchemeSettings(BtcPayNetworkProvider, "BTC")));
|
|
|
|
stores.TryGetValue(storeId, out var thisStore);
|
|
scriptType = thisStore.Item2?.AccountDerivation.ScriptPubKeyType();
|
|
var selectStores =
|
|
stores.Where(pair => pair.Key != storeId && pair.Value.Item2 is not null && pair.Value.Item2?.AccountDerivation.ScriptPubKeyType() == scriptType)
|
|
.Select(pair => new SelectListItem(pair.Value.s.StoreName, pair.Key, Model.MixToOtherWallet == pair.Key)).Prepend(new SelectListItem("None", ""));
|
|
}
|
|
|
|
|
|
<form method="post" asp-action="UpdateWabisabiStoreSettings" asp-controller="WabisabiStore" asp-route-storeId="@storeId">
|
|
|
|
|
|
<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>
|
|
</h3>
|
|
<div class="d-flex align-items-center gap-1 ">
|
|
<div class="d-flex align-items-center @(liteMode ? "d-none" : "")">
|
|
<input asp-for="Active" type="checkbox" class="btcpay-toggle me-2"/>
|
|
<label asp-for="Active" class="form-label mb-0 me-1"></label>
|
|
</div>
|
|
<button name="command" type="submit" value="save" class="btn btn-primary mt-3 mt-sm-0 @(liteMode ? "btn-lin" : "btn-secondary")">Save</button>
|
|
<a asp-action="ListCoinjoins" asp-route-storeId="@storeId" class="btn btn-secondary mt-3 mt-sm-0 @(liteMode ? "d-none" : "")" role="button">
|
|
Coinjoin History
|
|
</a>
|
|
<button type="button" class="btn btn-secondary mt-3 mt-sm-0 @(liteMode ? "d-none" : "")" permission="@Policies.CanModifyServerSettings"
|
|
data-bs-toggle="modal" data-bs-target="#discover-prompt">
|
|
Add Coordinator
|
|
</button>
|
|
<a asp-controller="WabisabiCoordinatorConfig" asp-action="UpdateWabisabiSettings" class="btn @(liteMode ? "btn-lin" : "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>
|
|
@{
|
|
if (BtcPayServerOptions.SocksEndpoint is null)
|
|
{
|
|
<div class="alert alert-danger d-flex align-items-center" role="alert">
|
|
<vc:icon symbol="warning"/>
|
|
<span class="ms-3">TOR is not configured on this BTCPay Server instance. All communication will be over clearnet and therefore not private!</span>
|
|
</div>
|
|
}
|
|
|
|
var wallet = await WalletProvider.GetWalletAsync(storeId);
|
|
if (wallet is BTCPayWallet && !((BTCPayKeyChain) wallet.KeyChain).KeysAvailable)
|
|
{
|
|
<div class="alert alert-danger d-flex align-items-center" role="alert">
|
|
<vc:icon symbol="warning"/>
|
|
<span class="ms-3">This wallet is either not a hot wallet, or enabled in your store settings and will not be able to participate in coinjoins.</span>
|
|
</div>
|
|
}
|
|
}
|
|
<style>
|
|
#blocker:hover{
|
|
background-color: rgba(128,128,128, 0.5);
|
|
}
|
|
#blocker:hover h4{
|
|
display: block !important;
|
|
left: 0;
|
|
position: fixed;
|
|
width: 100%
|
|
}
|
|
</style>
|
|
|
|
<div class="@(anyEnabled ? "" : "d-none") card card-body coordinator-settings">
|
|
@if (Model.Active && anyEnabled)
|
|
{
|
|
<div class="position-absolute w-100 h-100 text-center rounded" id="blocker" style=" left: 0; top: 0; z-index: 1">
|
|
<h4 class="d-none pt-4">Settings cannot be changed while active</h4>
|
|
|
|
</div>
|
|
}
|
|
<div class="row">
|
|
<div class="col-sm-12 col-md-6">
|
|
<div class="form-check">
|
|
<input class="form-check-input plebModeRadio"
|
|
type="radio" asp-for="PlebMode" value="true">
|
|
<label class="form-check-label" asp-for="PlebMode">
|
|
Pleb mode
|
|
</label>
|
|
<p class="text-muted">I just want to coinjoin.</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-12 col-md-6">
|
|
<div class="form-check">
|
|
<input class="form-check-input plebModeRadio" asp-for="PlebMode" type="radio"value="false">
|
|
<label class="form-check-label" asp-for="PlebMode">
|
|
Scientist mode
|
|
</label>
|
|
<p class="text-muted">The world is broken and I need to be vigilant about my bitcoin practices.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div id="advanced" class="@(Model.PlebMode ? "d-none" : "")">
|
|
<button type="submit" name="command" value="reset" class="btn btn-link">Reset to defaults </button>
|
|
|
|
<div class="row">
|
|
|
|
<div class="col-sm-12 col-md-6">
|
|
<div class="form-group">
|
|
|
|
<label asp-for="AnonymitySetTarget" class="form-label">Anon score target</label>
|
|
<input type="number" class="form-control" asp-for="AnonymitySetTarget" placeholder="target anon score" min="0">
|
|
|
|
<p class="text-muted">Scores your coinjoined utxos based on how many other utxos in the coinjoin (and other previous coinjoin rounds) had the same value.<br/> Anonset score computation is not an exact science, and when using coordinators with massive liquidity, is not that important as all rounds (past, present, future) contribute to your privacy.</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-12 col-md-6">
|
|
<div class="form-group form-check">
|
|
<label asp-for="ConsiderEntryProximity" class="form-check-label">Consider entry proximity</label>
|
|
<input asp-for="ConsiderEntryProximity" type="checkbox" class="form-check-input"/>
|
|
<p class="text-muted">Coins which have been mixed but are only one hop away from the original coinjoin transaction won't be considered private. This helps break linking assumptions when coins are spent.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="row">
|
|
|
|
<div class="col-sm-12 col-md-6">
|
|
<div class="form-group ">
|
|
<label asp-for="FeeRateMedianTimeFrameHours" class="form-label">Mining fee limits in hours</label>
|
|
<div class="input-group">
|
|
<input type="number" class="form-control" asp-for="FeeRateMedianTimeFrameHours" placeholder="hours" min="0">
|
|
<span class="input-group-text">hours</span>
|
|
</div>
|
|
<p class="text-muted">Only coinjoin if the mining fee is below the median of the specified past number of hours. Set to 0 to ignore</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-sm-12 col-md-6">
|
|
<div class="form-group ">
|
|
<label asp-for="ExplicitHighestFeeTarget" class="form-label">Highest feerate allowed</label>
|
|
<div class="input-group">
|
|
<input type="number" class="form-control" asp-for="ExplicitHighestFeeTarget" placeholder="sat/b" min="1">
|
|
|
|
<span class="input-group-text">sat/b</span>
|
|
</div>
|
|
<p class="text-muted">Only coinjoin if the mining fee is below this feerate</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="row">
|
|
<div class="col-sm-12 col-md-6">
|
|
<div class="form-group ">
|
|
<label asp-for="ConsolidationMode" class="form-check-label">Coinsolidation mode</label>
|
|
<select asp-for="ConsolidationMode" class="form-select">
|
|
<option value="@ConsolidationModeType.Never">Never</option>
|
|
<option value="@ConsolidationModeType.Always">Always</option>
|
|
<option value="@ConsolidationModeType.WhenLowFee">When the mining fee is low</option>
|
|
<option value="@ConsolidationModeType.WhenLowFeeAndManyUTXO">When the mining fee is low and you have too many UTXOs</option>
|
|
</select>
|
|
<p class="text-muted">Feed as many coins to the coinjoin as possible.</p>
|
|
<p class="text-muted">NOTE: When choosing WhenLowFeeAndManyUTXO, if you have over @BTCPayWallet.HighAmountOfCoins coins, coinjoins will happen regardless if your wallet is full private, to consolidate your wallet to a smaller utxo set.</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-12 col-md-6">
|
|
<div class="form-group ">
|
|
<label asp-for="LowFeeTarget" class="form-label">Mining low fee threshold</label>
|
|
<div class="input-group">
|
|
<input type="number" class="form-control" asp-for="LowFeeTarget" placeholder="sat/b" min="1">
|
|
|
|
<span class="input-group-text">sat/b</span>
|
|
</div>
|
|
<p class="text-muted">Consider it low fees when it is below this threshold</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
<div class="row">
|
|
<div class="col-sm-12 col-md-6">
|
|
<div class="form-group">
|
|
|
|
<label asp-for="MinimumDenominationAmount" class="form-label">Minimum denomination (in sats)</label>
|
|
<input type="number" class="form-control" asp-for="MinimumDenominationAmount" placeholder="sats" min="0">
|
|
<p class="text-muted">Do no use any of the standard denominations below this amount (creates change (which will get remixed) but prevent tiny utxos)</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-12 col-md-6">
|
|
<div class="form-group">
|
|
|
|
<label asp-for="AllowedDenominations" class="form-label">Allowed denominations (in sats)</label>
|
|
<select asp-for="AllowedDenominations" class="form-select" multiple="multiple">
|
|
@foreach (var denomination in BlockchainAnalyzer.StdDenoms)
|
|
{
|
|
<option value="@denomination">@denomination sats</option>
|
|
}
|
|
</select>
|
|
<p>DO NOT USE UNLESS YOU KNOW WHAT YOU ARE DOING</p>
|
|
<p class="text-muted">Only generate outputs of these denoms. Leave blank to ignore. Generates change. You must match other user's settings to gain any anonymity.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="form-group form-check">
|
|
<label asp-for="RedCoinIsolation" class="form-check-label">Cautious coinjoin entry mode </label>
|
|
<input asp-for="RedCoinIsolation" type="checkbox" class="form-check-input"/>
|
|
<p class="text-muted">Only allow a single non-private coin into a coinjoin.</p>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<div class="col-sm-12 col-md-6">
|
|
<div class="form-group form-check">
|
|
<label asp-for="BatchPayments" class="form-check-label">Batch payments</label>
|
|
<input asp-for="BatchPayments" type="checkbox" class="form-check-input"/>
|
|
<p class="text-muted">Batch your pending payments (on-chain payouts awaiting payment) inside coinjoins.</p>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-12 col-md-6">
|
|
<div class="form-group form-check">
|
|
<label asp-for="ParanoidPayments" class="form-check-label">Paranoid payments</label>
|
|
<input asp-for="ParanoidPayments" type="checkbox" class="form-check-input"/>
|
|
<p class="text-muted">Only batch payments with fully private coins.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label asp-for="CrossMixBetweenCoordinatorsMode" class="form-label">Mix funds between different coordinators</label>
|
|
<select asp-for="CrossMixBetweenCoordinatorsMode" class="form-select">
|
|
<option value="@WabisabiStoreSettings.CrossMixMode.WhenFree">Cross mix when free</option>
|
|
<option value="@WabisabiStoreSettings.CrossMixMode.Always">Always cross mix</option>
|
|
<option value="@WabisabiStoreSettings.CrossMixMode.Never">Never cross mix</option>
|
|
</select>
|
|
<p class="text-muted">Whether to allow mixed coins to be mixed within different coordinators for greater privacy (Warning: This will make your coins to lose the free remix within the same coordinator)</p>
|
|
</div>
|
|
<div class="form-group">
|
|
<label asp-for="ExtraJoinProbability" class="form-label">Continuous Coinjoin</label>
|
|
<div class="input-group">
|
|
<input asp-for="ExtraJoinProbability" type="number" min="0" max="100" step="any" class="form-control"/>
|
|
|
|
<span class="input-group-text">% * 0.01 </span>
|
|
</div>
|
|
<p class="text-muted">Percentage (100 = 1% reality) probability of joining a round even if you have no payments to batch and all coins are private, prevents timing analysis. (Warning: a high probability will quickly eat up your balance in mining fees) </p>
|
|
</div>
|
|
<div class="form-group ">
|
|
<label asp-for="MixToOtherWallet" class="form-check-label">Send to other wallet</label>
|
|
<select asp-for="MixToOtherWallet" asp-items="selectStores" class="form-select"></select>
|
|
<p class="text-muted">Send coins that have been created in a coinjoin in a standard denomination to another wallet</p>
|
|
</div>
|
|
<div class="row">
|
|
|
|
<div class="col-sm-12 col-md-6">
|
|
<div class="list-group form-group">
|
|
<div class="list-group-item font-weight-bold">Only mix coins with these labels</div>
|
|
@if (Model.InputLabelsAllowed?.Any() is not true)
|
|
{
|
|
<div class="list-group-item">No label filter applied</div>
|
|
}
|
|
else
|
|
{
|
|
@for (var xIndex = 0; xIndex < Model.InputLabelsAllowed.Count; xIndex++)
|
|
{
|
|
<div class="list-group-item">
|
|
<div class="input-group input-group-sm">
|
|
<input asp-for="InputLabelsAllowed[xIndex]" type="text" class="form-control"/>
|
|
<button name="command" value="include-label-remove:@Model.InputLabelsAllowed[xIndex]" type="submit" class="btn btn-secondary btn-sm">Remove</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
<div class="list-group-item">
|
|
<button name="command" value="include-label-add" type="submit" class="btn btn-secondary btn-sm">Add</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-sm-12 col-md-6">
|
|
<div class="list-group form-group">
|
|
<div class="list-group-item font-weight-bold">Only mix coins without these labels</div>
|
|
@if (Model.InputLabelsExcluded?.Any() is not true)
|
|
{
|
|
<div class="list-group-item">No label filter applied</div>
|
|
}
|
|
else
|
|
{
|
|
@for (var xIndex = 0; xIndex < Model.InputLabelsExcluded.Count; xIndex++)
|
|
{
|
|
<div class="list-group-item">
|
|
|
|
<div class="input-group input-group-sm">
|
|
<input asp-for="InputLabelsExcluded[xIndex]" type="text" class="form-control"/>
|
|
<button name="command" value="exclude-label-remove:@Model.InputLabelsExcluded[xIndex]" type="submit" class="btn btn-secondary btn-sm">Remove</button>
|
|
</div>
|
|
</div>
|
|
}
|
|
}
|
|
<div class="list-group-item">
|
|
<button name="command" value="exclude-label-add" type="submit" class="btn btn-secondary btn-sm">Add</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
</div>
|
|
</div>
|
|
|
|
@for (var index = 0; index < Model.Settings.Count; index++)
|
|
{
|
|
<input asp-for="Settings[index].Coordinator" type="hidden"/>
|
|
var s = Model.Settings[index];
|
|
|
|
if (!WabisabiCoordinatorClientInstanceManager.HostedServices.TryGetValue(s.Coordinator, out var coordinator))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
<div class="card mt-3">
|
|
<div class="card-header d-flex justify-content-between">
|
|
<div>
|
|
<div class="d-flex">
|
|
|
|
<h3>@coordinator.CoordinatorDisplayName</h3>
|
|
@if (coordinator.CoordinatorName != "local" && coordinator.CoordinatorName != "zksnacks")
|
|
{
|
|
<button name="command" type="submit" value="remove-coordinator:@(coordinator.CoordinatorName)" class="btn btn-link txt-danger" permission="@Policies.CanModifyServerSettings">Remove</button>
|
|
}
|
|
</div>
|
|
|
|
<span class="text-muted">@coordinator.Coordinator</span>
|
|
<div>
|
|
<div>@(!coordinator.WasabiCoordinatorStatusFetcher.Connected ? "Coordinator Status: Not connected" : "Coordinator Status: Connected")</div>
|
|
|
|
@if (!string.IsNullOrEmpty(coordinator.Description))
|
|
{
|
|
<p class="text-muted">@coordinator.Description</p>
|
|
}
|
|
@if (coordinator.RoundStateUpdater.AnyRound && coordinator.RoundStateUpdater.RoundStates.Any(pair => pair.Value.BlameOf == uint256.Zero))
|
|
{
|
|
var round = coordinator.RoundStateUpdater.RoundStates.Last(pair => pair.Value.BlameOf == uint256.Zero).Value;
|
|
var roundParameters = round.CoinjoinState.Parameters;
|
|
|
|
<div class="modal modal-lg fade" id="config-@s.Coordinator">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="exampleModalLabel">@coordinator.CoordinatorDisplayName Last round parameters </h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body" style="white-space: pre-line">
|
|
<table class="table table-responsive my-0">
|
|
|
|
<tr>
|
|
<th scope="row">Fee charged</th>
|
|
@{
|
|
var fee = $"{roundParameters.CoordinationFeeRate.Rate * 100}%";
|
|
}
|
|
<td>
|
|
@(fee)
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">Allowed input amounts</th>
|
|
<td>@roundParameters.AllowedInputAmounts.Min.ToDecimal(MoneyUnit.BTC) BTC - @roundParameters.AllowedInputAmounts.Max.ToDecimal(MoneyUnit.BTC) BTC</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">Allowed input types</th>
|
|
<td>@string.Join(", ", roundParameters.AllowedInputTypes)</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">Allowed output amounts</th>
|
|
<td>@roundParameters.AllowedOutputAmounts.Min.ToDecimal(MoneyUnit.BTC) BTC - @roundParameters.AllowedOutputAmounts.Max.ToDecimal(MoneyUnit.BTC) BTC</td>
|
|
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">Allowed output types</th>
|
|
|
|
<td>@string.Join(", ", roundParameters.AllowedOutputTypes)</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">Minimum inputs</th><td>@roundParameters.MinInputCountByRound</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">Maximum inputs</th><td>@roundParameters.MaxInputCountByRound</td>
|
|
</tr>
|
|
<tr>
|
|
<th scope="row">Maximum round registration time</th><td>@roundParameters.StandardInputRegistrationTimeout.ToString()</td>
|
|
</tr>
|
|
|
|
</table>
|
|
<div class="alert alert-info">Please note that a coordinator can change its configuration at will. This is only a display of the last round received from them.</div>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<a class="px-2 cursor-pointer"
|
|
data-bs-toggle="modal" data-bs-target="#config-@s.Coordinator">
|
|
Coordinator Config
|
|
</a>
|
|
@if (Model.Settings[index].RoundWhenEnabled is not null && !BTCPayWallet.IsRoundOk(roundParameters, Model.Settings[index]))
|
|
{
|
|
<div class="alert alert-danger w-100 mb-0 p-1">Round fees/parameters changed. Coinjoins will not occur unless you accept the new parameters.<button class="btn btn-link alert-link p-0" name="command" type="submit" value="accept-terms:@s.Coordinator"> Accept new terms</button></div>
|
|
}
|
|
}
|
|
|
|
<a class="px-2 w-100 cursor-pointer"
|
|
data-bs-toggle="modal" data-bs-target="#terms-@s.Coordinator"
|
|
style="
|
|
right: 0;
|
|
text-align: right;
|
|
">
|
|
By enabling this coordinator, you agree to their terms and conditions.
|
|
</a>
|
|
</div>
|
|
|
|
</div>
|
|
@{
|
|
var canEnable = coordinator.WasabiCoordinatorStatusFetcher.Connected && coordinator.RoundStateUpdater.AnyRound;
|
|
}
|
|
<div class="form-group form-check form" data-bs-toggle="tooltip" title="@(!canEnable ? "You cannot enable this coordinator until it is connected and a round has been seen" : string.Empty)">
|
|
@if (Model.Settings[index].RoundWhenEnabled is not null)
|
|
{
|
|
<input type="hidden" asp-for="Settings[index].RoundWhenEnabled.CoordinationFeeRate"/>
|
|
<input type="hidden" asp-for="Settings[index].RoundWhenEnabled.MinInputCountByRound"/>
|
|
}
|
|
|
|
<input asp-for="Settings[index].Enabled"
|
|
|
|
type="checkbox" class="form-check-input form-control-lg toggle-settings"
|
|
data-coordinator="@s.Coordinator"
|
|
disabled="@(!Model.Settings[index].Enabled && !canEnable)"/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
<div class="modal modal-lg fade" id="terms-@s.Coordinator">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header">
|
|
<h5 class="modal-title" id="exampleModalLabel">@coordinator.CoordinatorName Terms & Conditions </h5>
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body" style="white-space: pre-line">
|
|
@Safe.Raw(coordinator.TermsConditions)
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
}
|
|
|
|
|
|
</form>
|
|
@if (!liteMode)
|
|
{
|
|
<partial name="Wabisabi/AddCoordinatorPrompt" model="@(new DiscoveredCoordinator())"/>
|
|
}
|
|
|
|
|
|
@section PageFootContent {
|
|
<partial name="_ValidationScriptsPartial"/>
|
|
}
|
|
|
|
<script type="text/javascript">
|
|
|
|
function handlePlebModeChange(evt){
|
|
const isPlebMode = evt.target.value === "true";
|
|
|
|
const el = document.querySelector(`#advanced`);
|
|
if (isPlebMode){
|
|
el.classList.add("d-none");
|
|
}else{
|
|
|
|
el.classList.remove("d-none");
|
|
}
|
|
}
|
|
|
|
function handleCoordinatorEnabled(evt){
|
|
let enabled = evt.target.checked;
|
|
if (!enabled){
|
|
|
|
for (const settings of document.querySelectorAll("input.toggle-settings")) {
|
|
if (settings.checked){
|
|
enabled = true;
|
|
break;
|
|
}
|
|
}}
|
|
const el = document.querySelector(`.coordinator-settings`);
|
|
if (!enabled){
|
|
el.classList.add("d-none");
|
|
}else{
|
|
el.classList.remove("d-none");
|
|
}
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
const batchPaymentsEl = document.getElementById("BatchPayments");
|
|
const paranoidPaymentsEl = document.getElementById("ParanoidPayments");
|
|
const consolidationModeEl = document.getElementById("ConsolidationMode");
|
|
const lowFeeTargetEl = document.getElementById("LowFeeTarget");
|
|
|
|
|
|
function handle(){
|
|
|
|
if (consolidationModeEl.value.startsWith("WhenLowFee")){
|
|
lowFeeTargetEl.parentElement.parentElement.classList.remove("d-none");
|
|
} else{
|
|
lowFeeTargetEl.parentElement.parentElement.classList.add("d-none");
|
|
}
|
|
if (!batchPaymentsEl.checked){
|
|
paranoidPaymentsEl.parentElement.parentElement.classList.add("d-none");
|
|
} else{
|
|
paranoidPaymentsEl.parentElement.parentElement.classList.remove("d-none");
|
|
}
|
|
}
|
|
handle();
|
|
batchPaymentsEl.addEventListener("change", handle);
|
|
consolidationModeEl.addEventListener("change", handle);
|
|
|
|
document.querySelectorAll("input.toggle-settings").forEach(value => value.addEventListener("change", handleCoordinatorEnabled));
|
|
document.querySelectorAll("input.plebModeRadio").forEach(value => value.addEventListener("change", handlePlebModeChange));
|
|
});
|
|
|
|
|
|
</script> |