mirror of
https://github.com/aljazceru/BTCPayServerPlugins.git
synced 2025-12-17 07:34:24 +01:00
wabisabi fixes + aff
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net.Mime;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NBitcoin.DataEncoders;
|
||||
using WalletWasabi.Affiliation.Models;
|
||||
using WalletWasabi.Affiliation.Models.CoinjoinRequest;
|
||||
|
||||
namespace BTCPayServer.Plugins.Wabisabi.AffiliateServer;
|
||||
|
||||
public class WabisabiAffiliateSettings
|
||||
{
|
||||
public bool Enabled { get; set; }
|
||||
public string SigningKey { get; set; }
|
||||
|
||||
|
||||
}
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Authorize(Policy = Policies.CanModifyServerSettings, AuthenticationSchemes = AuthenticationSchemes.Cookie)]
|
||||
[Route("plugins/wabisabi-affiliate")]
|
||||
public class AffiliateServerController:Controller
|
||||
{
|
||||
private readonly SettingsRepository _settingsRepository;
|
||||
private readonly IOptions<DataDirectories> _dataDirectories;
|
||||
private readonly ILogger<AffiliateServerController> _logger;
|
||||
|
||||
public AffiliateServerController(SettingsRepository settingsRepository, IOptions<DataDirectories> dataDirectories, ILogger<AffiliateServerController> logger)
|
||||
{
|
||||
_settingsRepository = settingsRepository;
|
||||
_dataDirectories = dataDirectories;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
[HttpGet("edit")]
|
||||
public async Task<IActionResult> Edit()
|
||||
{
|
||||
var settings =
|
||||
await _settingsRepository.GetSettingAsync<WabisabiAffiliateSettings>();
|
||||
return View(settings);
|
||||
}
|
||||
[HttpPost("edit")]
|
||||
public async Task<IActionResult> Edit(WabisabiAffiliateSettings settings)
|
||||
{
|
||||
await _settingsRepository.UpdateSetting(settings);
|
||||
return RedirectToAction("Edit");
|
||||
}
|
||||
[HttpGet("history")]
|
||||
public async Task<IActionResult> ViewRequests()
|
||||
{
|
||||
var path = Path.Combine(_dataDirectories.Value.DataDir, "Plugins", "CoinjoinAffiliate", "History.txt");
|
||||
if (!System.IO.File.Exists(path))
|
||||
return NotFound();
|
||||
|
||||
return File(path, MediaTypeNames.Text.Plain);
|
||||
}
|
||||
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("get_status")]
|
||||
public async Task<IActionResult> GetStatus()
|
||||
{
|
||||
|
||||
var settings =
|
||||
await _settingsRepository.GetSettingAsync<WabisabiAffiliateSettings>();
|
||||
if(settings?.Enabled is true&& !string.IsNullOrEmpty(settings.SigningKey))
|
||||
return Ok(new { });
|
||||
return NotFound();
|
||||
|
||||
}
|
||||
|
||||
private static ECDsa ecdsa = ECDsa.Create();
|
||||
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpPost("get_coinjoin_request")]
|
||||
public async Task<IActionResult> GetCoinjoinRequest([FromBody] GetCoinjoinRequestRequest request)
|
||||
{
|
||||
var settings = await _settingsRepository.GetSettingAsync<WabisabiAffiliateSettings>();
|
||||
if (settings?.Enabled is not true)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var keyB = Encoders.Hex.DecodeData(settings.SigningKey);
|
||||
ecdsa.ImportSubjectPublicKeyInfo(keyB.AsSpan(), out _);
|
||||
|
||||
Payload payload = new(new Header(), request.Body);
|
||||
try
|
||||
{
|
||||
|
||||
var valid = ecdsa.VerifyData(payload.GetCanonicalSerialization(), request.Signature, HashAlgorithmName.SHA256);
|
||||
if(!valid)
|
||||
return NotFound();
|
||||
|
||||
var path = Path.Combine(_dataDirectories.Value.DataDir, "Plugins", "CoinjoinAffiliate", "History.txt");
|
||||
string rawBody;
|
||||
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
|
||||
{
|
||||
rawBody = (await reader.ReadToEndAsync());
|
||||
}
|
||||
|
||||
await System.IO.File.AppendAllLinesAsync(path, new[] {rawBody.Replace(Environment.NewLine, "")}, Encoding.UTF8);
|
||||
return Ok(new GetCoinjoinRequestResponse(Array.Empty<byte>()));
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogError(e, "Failed on GetCoinjoinRequest" );
|
||||
return NotFound();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using NBitcoin;
|
||||
using WalletWasabi.WabiSabi.Models;
|
||||
|
||||
namespace WalletWasabi.Affiliation.Models;
|
||||
|
||||
public record AffiliateInformation
|
||||
(
|
||||
ImmutableArray<AffiliationFlag> RunningAffiliateServers,
|
||||
ImmutableDictionary<string, ImmutableDictionary<AffiliationFlag, byte[]>> CoinjoinRequests
|
||||
)
|
||||
{
|
||||
public static readonly AffiliateInformation Empty = new(ImmutableArray<AffiliationFlag>.Empty, ImmutableDictionary<string, ImmutableDictionary<AffiliationFlag, byte[]>>.Empty);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using WalletWasabi.Affiliation.Serialization;
|
||||
using System.Text;
|
||||
|
||||
namespace WalletWasabi.Affiliation.Models.CoinjoinRequest;
|
||||
|
||||
public record Body
|
||||
{
|
||||
public Body(IEnumerable<Input> inputs, IEnumerable<Output> outputs, long slip44CoinType, decimal feeRate, long noFeeThreshold, long minRegistrableAmount, long timestamp)
|
||||
{
|
||||
Inputs = inputs;
|
||||
Outputs = outputs;
|
||||
Slip44CoinType = slip44CoinType;
|
||||
FeeRate = feeRate;
|
||||
NoFeeThreshold = noFeeThreshold;
|
||||
MinRegistrableAmount = minRegistrableAmount;
|
||||
Timestamp = timestamp;
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "inputs")]
|
||||
public IEnumerable<Input> Inputs { get; }
|
||||
|
||||
[JsonProperty(PropertyName = "outputs")]
|
||||
public IEnumerable<Output> Outputs { get; }
|
||||
|
||||
[JsonProperty(PropertyName = "slip44_coin_type")]
|
||||
public long Slip44CoinType { get; }
|
||||
|
||||
[JsonProperty(PropertyName = "fee_rate")]
|
||||
[JsonConverter(typeof(AffiliationFeeRateJsonConverter))]
|
||||
public decimal FeeRate { get; }
|
||||
|
||||
[JsonProperty(PropertyName = "no_fee_threshold")]
|
||||
public long NoFeeThreshold { get; }
|
||||
|
||||
[JsonProperty(PropertyName = "min_registrable_amount")]
|
||||
public long MinRegistrableAmount { get; }
|
||||
|
||||
[JsonProperty(PropertyName = "timestamp")]
|
||||
public long Timestamp { get; }
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace WalletWasabi.Affiliation.Models.CoinjoinRequest;
|
||||
|
||||
public record Header
|
||||
{
|
||||
[JsonProperty(PropertyName = "title")]
|
||||
public static readonly string Title = "payment request";
|
||||
|
||||
[JsonProperty(PropertyName = "version")]
|
||||
public static readonly int Version = 1;
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using WalletWasabi.Affiliation.Serialization;
|
||||
using WalletWasabi.WabiSabi.Models;
|
||||
|
||||
namespace WalletWasabi.Affiliation.Models.CoinjoinRequest;
|
||||
|
||||
public record Input
|
||||
{
|
||||
public Input(Outpoint prevout, byte[] scriptPubkey, bool isAffiliated, bool isNoFee)
|
||||
{
|
||||
Prevout = prevout;
|
||||
ScriptPubkey = scriptPubkey;
|
||||
IsAffiliated = isAffiliated;
|
||||
IsNoFee = isNoFee;
|
||||
|
||||
if (isNoFee && isAffiliated)
|
||||
{
|
||||
Logging.Logger.LogWarning($"Detected input with redundant affiliation flag: {Convert.ToHexString(prevout.Hash)}, {prevout.Index}");
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "prevout")]
|
||||
public Outpoint Prevout { get; }
|
||||
|
||||
[JsonProperty(PropertyName = "script_pubkey")]
|
||||
[JsonConverter(typeof(AffiliationByteArrayJsonConverter))]
|
||||
public byte[] ScriptPubkey { get; }
|
||||
|
||||
[JsonProperty(PropertyName = "is_affiliated")]
|
||||
public bool IsAffiliated { get; }
|
||||
|
||||
[JsonProperty(PropertyName = "is_no_fee")]
|
||||
public bool IsNoFee { get; }
|
||||
|
||||
public static Input FromAffiliateInput(AffiliateInput affiliateInput, AffiliationFlag affiliationFlag)
|
||||
{
|
||||
return new Input(Outpoint.FromOutPoint(affiliateInput.Prevout), affiliateInput.ScriptPubKey.ToBytes(), affiliateInput.AffiliationFlag == affiliationFlag, affiliateInput.IsNoFee);
|
||||
}
|
||||
}
|
||||
|
||||
public record AffiliateInput
|
||||
{
|
||||
public AffiliateInput(OutPoint prevout, Script scriptPubKey, AffiliationFlag affiliationFlag, bool isNoFee)
|
||||
{
|
||||
Prevout = prevout;
|
||||
ScriptPubKey = scriptPubKey;
|
||||
AffiliationFlag = affiliationFlag;
|
||||
IsNoFee = isNoFee;
|
||||
}
|
||||
|
||||
public AffiliateInput(Coin coin, AffiliationFlag affiliationFlag, bool isNoFee)
|
||||
: this(coin.Outpoint, coin.ScriptPubKey, affiliationFlag, isNoFee)
|
||||
{
|
||||
}
|
||||
|
||||
public OutPoint Prevout { get; }
|
||||
public Script ScriptPubKey { get; }
|
||||
public AffiliationFlag AffiliationFlag { get; }
|
||||
public bool IsNoFee { get; }
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using WalletWasabi.Affiliation.Serialization;
|
||||
|
||||
namespace WalletWasabi.Affiliation.Models.CoinjoinRequest;
|
||||
|
||||
public record Outpoint
|
||||
{
|
||||
public Outpoint(byte[] hash, long index)
|
||||
{
|
||||
Hash = hash;
|
||||
Index = index;
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "hash")]
|
||||
[JsonConverter(typeof(AffiliationByteArrayJsonConverter))]
|
||||
public byte[] Hash { get; }
|
||||
|
||||
[JsonProperty(PropertyName = "index")]
|
||||
public long Index { get; }
|
||||
|
||||
public static Outpoint FromOutPoint(OutPoint outPoint)
|
||||
{
|
||||
return new Outpoint(outPoint.Hash.ToBytes(lendian: true), outPoint.N);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json;
|
||||
using WalletWasabi.Affiliation.Serialization;
|
||||
|
||||
namespace WalletWasabi.Affiliation.Models.CoinjoinRequest;
|
||||
|
||||
public record Output
|
||||
{
|
||||
public Output(long amount, byte[] script_pubkey)
|
||||
{
|
||||
Amount = amount;
|
||||
ScriptPubkey = script_pubkey;
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "amount")]
|
||||
public long Amount { get; }
|
||||
|
||||
[JsonProperty(PropertyName = "script_pubkey")]
|
||||
[JsonConverter(typeof(AffiliationByteArrayJsonConverter))]
|
||||
public byte[] ScriptPubkey { get; }
|
||||
|
||||
public static Output FromTxOut(TxOut txOut)
|
||||
{
|
||||
return new Output(txOut.Value, txOut.ScriptPubKey.ToBytes());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Newtonsoft.Json;
|
||||
using WalletWasabi.Affiliation.Serialization;
|
||||
using System.Text;
|
||||
|
||||
namespace WalletWasabi.Affiliation.Models.CoinjoinRequest;
|
||||
|
||||
public record Payload
|
||||
{
|
||||
public Payload(Header header, Body body)
|
||||
{
|
||||
Header = header;
|
||||
Body = body;
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "header")]
|
||||
public Header Header { get; }
|
||||
|
||||
[JsonProperty(PropertyName = "body")]
|
||||
public Body Body { get; }
|
||||
|
||||
public byte[] GetCanonicalSerialization()
|
||||
{
|
||||
return Encoding.ASCII.GetBytes(JsonConvert.SerializeObject(this, CanonicalJsonSerializationOptions.Settings));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Newtonsoft.Json;
|
||||
using WalletWasabi.Affiliation.Serialization;
|
||||
using WalletWasabi.Affiliation.Models.CoinjoinRequest;
|
||||
|
||||
namespace WalletWasabi.Affiliation.Models;
|
||||
|
||||
public record GetCoinjoinRequestRequest
|
||||
{
|
||||
public GetCoinjoinRequestRequest(Body body, byte[] signature)
|
||||
{
|
||||
Body = body;
|
||||
Signature = signature;
|
||||
}
|
||||
|
||||
[JsonProperty(PropertyName = "signature")]
|
||||
[JsonConverter(typeof(AffiliationByteArrayJsonConverter))]
|
||||
public byte[] Signature { get; }
|
||||
|
||||
[JsonProperty(PropertyName = "body")]
|
||||
public Body Body { get; }
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace WalletWasabi.Affiliation.Models;
|
||||
|
||||
public class GetCoinjoinRequestResponse
|
||||
{
|
||||
[JsonProperty(PropertyName = "coinjoin_request")]
|
||||
public byte[] CoinjoinRequest;
|
||||
|
||||
public GetCoinjoinRequestResponse(byte[] coinjoinRequest)
|
||||
{
|
||||
CoinjoinRequest = coinjoinRequest;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace WalletWasabi.Affiliation.Models;
|
||||
|
||||
public record StatusRequest();
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace WalletWasabi.Affiliation.Models;
|
||||
|
||||
public record StatusResponse();
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using NBitcoin.DataEncoders;
|
||||
using Newtonsoft.Json;
|
||||
using WalletWasabi.Helpers;
|
||||
|
||||
namespace WalletWasabi.Affiliation.Serialization;
|
||||
|
||||
public class AffiliationByteArrayJsonConverter : JsonConverter<byte[]>
|
||||
{
|
||||
public override byte[]? ReadJson(JsonReader reader, Type objectType, byte[]? existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.Value is string serialized)
|
||||
{
|
||||
return Convert.FromHexString(serialized);
|
||||
}
|
||||
throw new JsonSerializationException("Cannot deserialize object.");
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, byte[]? value, JsonSerializer serializer)
|
||||
{
|
||||
Guard.NotNull(nameof(value), value);
|
||||
writer.WriteValue(Convert.ToHexString(value).ToLower());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace WalletWasabi.Affiliation.Serialization;
|
||||
|
||||
public class AffiliationFeeRateJsonConverter : JsonConverter<decimal>
|
||||
{
|
||||
public static readonly decimal Base = 1e-8m;
|
||||
|
||||
private static long EncodeDecimal(decimal value)
|
||||
{
|
||||
return (long)decimal.Round(value / Base);
|
||||
}
|
||||
|
||||
private static decimal DecodeDecimal(long value)
|
||||
{
|
||||
return value * Base;
|
||||
}
|
||||
|
||||
private static bool IsEncodable(decimal value)
|
||||
{
|
||||
return DecodeDecimal(EncodeDecimal(value)) == value;
|
||||
}
|
||||
|
||||
public override decimal ReadJson(JsonReader reader, Type objectType, decimal existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.Value is long number)
|
||||
{
|
||||
return DecodeDecimal(number);
|
||||
}
|
||||
throw new JsonSerializationException("Cannot deserialize object.");
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, decimal value, JsonSerializer serializer)
|
||||
{
|
||||
if (!IsEncodable(value))
|
||||
{
|
||||
throw new ArgumentException("Decimal cannot be unambiguously encodable.", nameof(value));
|
||||
}
|
||||
writer.WriteValue(EncodeDecimal(value));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using WalletWasabi.WabiSabi.Models;
|
||||
|
||||
namespace WalletWasabi.Affiliation.Serialization;
|
||||
|
||||
public class AffiliationFlagConverter : TypeConverter
|
||||
{
|
||||
public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
|
||||
{
|
||||
if (sourceType == typeof(string))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return base.CanConvertFrom(context, sourceType);
|
||||
}
|
||||
|
||||
public override object ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
|
||||
{
|
||||
if (value is string)
|
||||
{
|
||||
return new AffiliationFlag((string)value);
|
||||
}
|
||||
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
|
||||
{
|
||||
if (destinationType == typeof(string))
|
||||
{
|
||||
if (value is AffiliationFlag)
|
||||
{
|
||||
return ((AffiliationFlag)value).Name;
|
||||
}
|
||||
}
|
||||
return base.ConvertTo(context, culture, value, destinationType);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace WalletWasabi.Affiliation.Serialization;
|
||||
|
||||
public static class AffiliationJsonSerializationOptions
|
||||
{
|
||||
public static readonly List<JsonConverter> Converters = new()
|
||||
{
|
||||
new AffiliationByteArrayJsonConverter(),
|
||||
new AffiliationFeeRateJsonConverter()
|
||||
};
|
||||
|
||||
public static readonly JsonSerializerSettings Settings = new() { Converters = Converters };
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace WalletWasabi.Affiliation.Serialization;
|
||||
|
||||
public static class CanonicalJsonSerializationOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON settings that enforces JSON's objects' properties' to be serialized in alphabetical order.
|
||||
/// </summary>
|
||||
public static readonly JsonSerializerSettings Settings = new()
|
||||
{
|
||||
ContractResolver = new OrderedContractResolver(),
|
||||
Converters = AffiliationJsonSerializationOptions.Converters,
|
||||
|
||||
// Intentionally enforced default value.
|
||||
Formatting = Formatting.None
|
||||
};
|
||||
|
||||
/// <seealso href="https://stackoverflow.com/a/11309106/3744182"/>
|
||||
private class OrderedContractResolver : DefaultContractResolver
|
||||
{
|
||||
private static bool IsValidCharacter(char c)
|
||||
{
|
||||
return char.IsAscii(c) && ((char.IsLetter(c) && char.IsLower(c)) || char.IsDigit(c) || c == '_');
|
||||
}
|
||||
|
||||
private static bool IsValidPropertyName(string name)
|
||||
{
|
||||
return name.All(IsValidCharacter);
|
||||
}
|
||||
|
||||
protected override IList<JsonProperty> CreateProperties(Type type, MemberSerialization memberSerialization)
|
||||
{
|
||||
IEnumerable<JsonProperty> properties = base.CreateProperties(type, memberSerialization);
|
||||
|
||||
foreach (JsonProperty property in properties)
|
||||
{
|
||||
if (property.PropertyName is null)
|
||||
{
|
||||
throw new JsonSerializationException("Property name is not set");
|
||||
}
|
||||
|
||||
if (!IsValidPropertyName(property.PropertyName))
|
||||
{
|
||||
throw new JsonSerializationException("Object property contains an invalid character.");
|
||||
}
|
||||
}
|
||||
|
||||
return properties.OrderBy(p => p.PropertyName, StringComparer.Ordinal).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel;
|
||||
using WalletWasabi.WabiSabi.Models;
|
||||
|
||||
namespace WalletWasabi.Affiliation.Serialization;
|
||||
|
||||
public class DefaultAffiliateServersAttribute : DefaultValueAttribute
|
||||
{
|
||||
public DefaultAffiliateServersAttribute() : base(ImmutableDictionary<AffiliationFlag, string>.Empty)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System.ComponentModel;
|
||||
using WalletWasabi.WabiSabi.Models;
|
||||
|
||||
namespace WalletWasabi.Affiliation.Serialization;
|
||||
|
||||
public class DefaultAffiliationFlagAttribute : DefaultValueAttribute
|
||||
{
|
||||
public DefaultAffiliationFlagAttribute() : base(AffiliationFlag.Default)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -46,7 +46,12 @@ public class BTCPayCoinjoinCoinSelector : IRoundCoinSelector
|
||||
})
|
||||
.Where(coin =>
|
||||
{
|
||||
|
||||
if (!_wallet.WabisabiStoreSettings.PlebMode &&
|
||||
_wallet.WabisabiStoreSettings.CrossMixBetweenCoordinatorsMode ==
|
||||
WabisabiStoreSettings.CrossMixMode.Always)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (!coin.HdPubKey.Label.Contains("coinjoin") || coin.HdPubKey.Label.Contains(utxoSelectionParameters.CoordinatorName))
|
||||
{
|
||||
return true;
|
||||
|
||||
@@ -150,7 +150,7 @@ public class WabisabiCoordinatorService : PeriodicRunner
|
||||
cacheKey,
|
||||
action: async (_, cancellationToken) =>
|
||||
{
|
||||
var result = await _explorerClient.GetFeeRateAsync(confirmationTarget, cancellationToken);
|
||||
var result = await _explorerClient.GetFeeRateAsync(confirmationTarget, new FeeRate(100m), cancellationToken);
|
||||
return new EstimateSmartFeeResponse() {FeeRate = result.FeeRate, Blocks = result.BlockCount};
|
||||
},
|
||||
options: CacheOptionsWithExpirationToken(size: 1, expireInSeconds: 60),
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
dotnet publish -c Altcoins-Release -o bin/Altcoins-Debug/net6.0
|
||||
dotnet publish BTCPayServer.Plugins.Wabisabi.csproj -c Release -o bin/publish/BTCPayServer.Plugins.Wabisabi
|
||||
dotnet run -p ../../submodules/btcpayserver/BTCPayServer.PluginPacker bin/publish/BTCPayServer.Plugins.Wabisabi BTCPayServer.Plugins.Wabisabi ../packed
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
@using Microsoft.AspNetCore.Mvc.TagHelpers
|
||||
@model BTCPayServer.Plugins.Wabisabi.AffiliateServer.WabisabiAffiliateSettings
|
||||
@{
|
||||
Layout = "../Shared/_NavLayout.cshtml";
|
||||
ViewData["NavPartialName"] = "../UIServer/_Nav";
|
||||
}
|
||||
|
||||
<h2 class="mb-4">Coinjoin affiliate configuration</h2>
|
||||
|
||||
<form method="post">
|
||||
<div class="row">
|
||||
<div class="col-xxl-constrain col-xl-8">
|
||||
<div class="form-group form-check">
|
||||
<label asp-for="Enabled" class="form-check-label">Enable </label>
|
||||
<input asp-for="Enabled" type="checkbox" class="form-check-input"/>
|
||||
</div>
|
||||
|
||||
<div class="form-group pt-3">
|
||||
<label class="form-label" for="config">Coordinator Key</label>
|
||||
|
||||
<input type="text" class="form-control " asp-for="SigningKey">
|
||||
</input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button name="command" type="submit" value="save" class="btn btn-primary mt-2">Save</button>
|
||||
|
||||
|
||||
</form>
|
||||
|
||||
@section PageFootContent {
|
||||
<partial name="_ValidationScriptsPartial"/>
|
||||
}
|
||||
@@ -41,7 +41,7 @@
|
||||
}
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<table class="table table-hover w-100">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-125px">Round</th>
|
||||
|
||||
@@ -63,9 +63,7 @@
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="widget store-wallet-balance " style="
|
||||
max-height: 500px;
|
||||
overflow-y: auto;">
|
||||
<div class="widget store-wallet-balance" >
|
||||
<header>
|
||||
<h3>Recent Coinjoins</h3>
|
||||
@if (cjHistory.Any())
|
||||
@@ -97,9 +95,7 @@
|
||||
|
||||
var privacyPercentage = Math.Round(privacy * 100);
|
||||
var colorCoins = coins.GroupBy(coin => coin.CoinColor(wallet.AnonymitySetTarget)).ToDictionary(grouping => grouping.Key, grouping => grouping);
|
||||
<div class="widget store-numbers" style="
|
||||
max-height: 500px;
|
||||
overflow-y: auto;">
|
||||
<div class="widget store-numbers" >
|
||||
|
||||
@if (wallet is BTCPayWallet btcPayWallet)
|
||||
{
|
||||
|
||||
@@ -163,14 +163,10 @@ namespace BTCPayServer.Plugins.Wabisabi
|
||||
return View(vm);
|
||||
|
||||
case "save":
|
||||
foreach (WabisabiStoreCoordinatorSettings settings in vm.Settings)
|
||||
{
|
||||
vm.InputLabelsAllowed = vm.InputLabelsAllowed.Where(s => !string.IsNullOrEmpty(s)).Distinct()
|
||||
.ToList();
|
||||
vm.InputLabelsExcluded = vm.InputLabelsExcluded.Where(s => !string.IsNullOrEmpty(s)).Distinct()
|
||||
.ToList();
|
||||
}
|
||||
|
||||
vm.InputLabelsAllowed = vm.InputLabelsAllowed.Where(s => !string.IsNullOrEmpty(s)).Distinct()
|
||||
.ToList();
|
||||
vm.InputLabelsExcluded = vm.InputLabelsExcluded.Where(s => !string.IsNullOrEmpty(s)).Distinct()
|
||||
.ToList();
|
||||
await _WabisabiService.SetWabisabiForStore(storeId, vm);
|
||||
TempData["SuccessMessage"] = "Wabisabi settings modified";
|
||||
return RedirectToAction(nameof(UpdateWabisabiStoreSettings), new {storeId});
|
||||
|
||||
Reference in New Issue
Block a user