better send tools

This commit is contained in:
Kukks
2023-01-30 17:06:19 +01:00
parent 2c84539bc3
commit 9e67a6ee06
5 changed files with 130 additions and 13 deletions

View File

@@ -336,6 +336,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
{ {
var stopwatch = new Stopwatch(); var stopwatch = new Stopwatch();
stopwatch.Start(); stopwatch.Start();
var txHash = result.UnsignedCoinJoin.GetHash();
var kp = await ExplorerClient.GetMetadataAsync<RootedKeyPath>(DerivationScheme, var kp = await ExplorerClient.GetMetadataAsync<RootedKeyPath>(DerivationScheme,
WellknownMetadataKeys.AccountKeyPath); WellknownMetadataKeys.AccountKeyPath);
@@ -409,7 +410,7 @@ public class BTCPayWallet : IWallet, IDestinationProvider
{ {
Round = result.RoundId.ToString(), Round = result.RoundId.ToString(),
CoordinatorName = coordinatorName, CoordinatorName = coordinatorName,
Transaction = result.UnsignedCoinJoin.GetHash().ToString(), Transaction = txHash.ToString(),
CoinsIn = smartTx.WalletInputs.Select(coin => new CoinjoinData.CoinjoinDataCoin() CoinsIn = smartTx.WalletInputs.Select(coin => new CoinjoinData.CoinjoinDataCoin()
{ {
AnonymitySet = coin.AnonymitySet, AnonymitySet = coin.AnonymitySet,
@@ -455,12 +456,12 @@ public class BTCPayWallet : IWallet, IDestinationProvider
{ {
await _walletRepository.AddWalletTransactionAttachment( await _walletRepository.AddWalletTransactionAttachment(
new WalletId(storeIdForutxo, "BTC"), new WalletId(storeIdForutxo, "BTC"),
result.UnsignedCoinJoin.GetHash(), txHash,
new List<Attachment>() new List<Attachment>()
{ {
new Attachment("coinjoin", result.RoundId.ToString(), JObject.FromObject(new CoinjoinData() new Attachment("coinjoin", result.RoundId.ToString(), JObject.FromObject(new CoinjoinData()
{ {
Transaction = result.UnsignedCoinJoin.GetHash().ToString(), Transaction = txHash.ToString(),
Round = result.RoundId.ToString(), Round = result.RoundId.ToString(),
CoinsOut = mixedCoins.Select(coin => new CoinjoinData.CoinjoinDataCoin() CoinsOut = mixedCoins.Select(coin => new CoinjoinData.CoinjoinDataCoin()
{ {

View File

@@ -0,0 +1,89 @@
@using BTCPayServer.Security
@using NBitcoin
@using BTCPayServer.Abstractions.Contracts
@using BTCPayServer.Plugins.Wabisabi
@using WalletWasabi.Blockchain.Analysis
@model BTCPayServer.Models.WalletViewModels.WalletSendModel
@inject ContentSecurityPolicies contentSecurityPolicies
@inject WalletProvider _walletProvider
@inject IScopeProvider ScopeProvider
@{
var nonce = RandomUtils.GetUInt256().ToString().Substring(0, 32);
contentSecurityPolicies.Add("script-src", $"'nonce-{nonce}'");
contentSecurityPolicies.AllowUnsafeHashes();
var storeId = ScopeProvider.GetCurrentStoreId();
var w = await _walletProvider.GetWalletAsync(storeId);
}
@if (w is not null)
{
<div style="display: none">
<div id="wabisabitemplate">
<button type="button"
id="privatesend"
class="btn btn-primary"
title="Optimzie coin selection for privacy">
Private coin selection
<i class="fa fa-user-secret"></i>
</button>
</div>
</div>
<datalist id="wabisabidenominations">
@foreach (var stdDenom in BlockchainAnalyzer.StdDenoms)
{
var num = new Money(stdDenom).ToDecimal(MoneyUnit.BTC);
<option value="@num">Privacy suggestion (used in coinjoins)</option>
}
</datalist>
<script type="text/javascript" nonce="@nonce">
document.addEventListener("DOMContentLoaded", function () {
const amountElements = document.querySelectorAll("[name^='Outputs'][name$='Amount']");
amountElements.forEach(value => {
value.setAttribute("list","wabisabidenominations" );
});
document.querySelector("#SignTransaction").insertAdjacentElement("beforeBegin", document.getElementById("wabisabitemplate"))
document.getElementById("privatesend").addEventListener("click", async ev => {
document.getElementById("InputSelection").value = "True";
const amountElements = document.querySelectorAll("[name^='Outputs'][name$='Amount']");
let amount = 0;
amountElements.forEach(value => {
try {
amount+= parseFloat(value.value);
}catch{}
});
if (!amount){
return;
}
const url =@Safe.Json(@Url.Action("ComputeCoinSelection", "WabisabiStore", new { storeId }));
const response = await fetch(`${url}?amount=${amount}`);
const coins = await response.json();
let selectedInputsElement = document.getElementById("SelectedInputs");
if (!selectedInputsElement){
selectedInputsElement = document.createElement("select");
selectedInputsElement.setAttribute("name", "SelectedInputs")
selectedInputsElement.setAttribute("id", "SelectedInputs")
selectedInputsElement.setAttribute("multiple", "multiple")
selectedInputsElement.style.display = "none";
ev.target.insertAdjacentElement("beforeBegin",selectedInputsElement);
}else{
while (selectedInputsElement.options.length > 0) {
selectedInputsElement.remove(0);
}
}
for (const coin of coins) {
selectedInputsElement.add(new Option(coin, coin, true, true),undefined);
}
document.getElementsByTagName("form")[0].submit();
});
});
</script>
}

View File

@@ -94,6 +94,8 @@ public class WabisabiPlugin : BaseBTCPayServerPlugin
"store-integrations-nav")); "store-integrations-nav"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("Wabisabi/WabisabiDashboard", applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("Wabisabi/WabisabiDashboard",
"dashboard")); "dashboard"));
applicationBuilder.AddSingleton<IUIExtension>(new UIExtension("Wabisabi/WabisabiWalletSend",
"onchain-wallet-send"));
applicationBuilder.AddSingleton<IPayoutProcessorFactory, WabisabiPayoutProcessor>(); applicationBuilder.AddSingleton<IPayoutProcessorFactory, WabisabiPayoutProcessor>();
Logger.SetMinimumLevel(LogLevel.Info); Logger.SetMinimumLevel(LogLevel.Info);

View File

@@ -34,7 +34,6 @@ namespace BTCPayServer.Plugins.Wabisabi
_walletRepository = walletRepository; _walletRepository = walletRepository;
_payoutProcessorService = payoutProcessorService; _payoutProcessorService = payoutProcessorService;
_eventAggregator = eventAggregator; _eventAggregator = eventAggregator;
_eventAggregator.Subscribe()
} }
public async Task<WabisabiStoreSettings> GetWabisabiForStore(string storeId) public async Task<WabisabiStoreSettings> GetWabisabiForStore(string storeId)

View File

@@ -11,6 +11,8 @@ using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Client; using BTCPayServer.Client;
using BTCPayServer.Client.Models; using BTCPayServer.Client.Models;
using BTCPayServer.Common; using BTCPayServer.Common;
using BTCPayServer.Filters;
using BTCPayServer.Models.WalletViewModels;
using BTCPayServer.Services; using BTCPayServer.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@@ -90,7 +92,7 @@ namespace BTCPayServer.Plugins.Wabisabi
if (Uri.TryCreate(relay, UriKind.Absolute, out var relayUri)) if (Uri.TryCreate(relay, UriKind.Absolute, out var relayUri))
{ {
ViewBag.DiscoveredCoordinators =await Nostr.Discover(relayUri, ViewBag.DiscoveredCoordinators = await Nostr.Discover(relayUri,
_explorerClientProvider.GetExplorerClient("BTC").Network.NBitcoinNetwork, _explorerClientProvider.GetExplorerClient("BTC").Network.NBitcoinNetwork,
coordSettings.Key?.CreateXOnlyPubKey().ToHex(), CancellationToken.None); coordSettings.Key?.CreateXOnlyPubKey().ToHex(), CancellationToken.None);
} }
@@ -177,10 +179,12 @@ namespace BTCPayServer.Plugins.Wabisabi
return View(vm); return View(vm);
} }
} }
[Route("coinjoins")] [Route("coinjoins")]
public async Task<IActionResult> ListCoinjoins(string storeId, CoinjoinsViewModel viewModel, [FromServices] WalletRepository walletRepository) public async Task<IActionResult> ListCoinjoins(string storeId, CoinjoinsViewModel viewModel,
[FromServices] WalletRepository walletRepository)
{ {
var objects =await _WabisabiService.GetCoinjoinHistory(storeId); var objects = await _WabisabiService.GetCoinjoinHistory(storeId);
viewModel ??= new CoinjoinsViewModel(); viewModel ??= new CoinjoinsViewModel();
viewModel.Coinjoins = objects viewModel.Coinjoins = objects
@@ -190,6 +194,7 @@ namespace BTCPayServer.Plugins.Wabisabi
return View(viewModel); return View(viewModel);
} }
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.SameOrigin)]
[HttpGet("spend")] [HttpGet("spend")]
public async Task<IActionResult> Spend(string storeId) public async Task<IActionResult> Spend(string storeId)
{ {
@@ -201,6 +206,7 @@ namespace BTCPayServer.Plugins.Wabisabi
return View(new SpendViewModel() { }); return View(new SpendViewModel() { });
} }
[XFrameOptions(XFrameOptionsAttribute.XFrameOptions.SameOrigin)]
[HttpPost("spend")] [HttpPost("spend")]
public async Task<IActionResult> Spend(string storeId, SpendViewModel spendViewModel, string command) public async Task<IActionResult> Spend(string storeId, SpendViewModel spendViewModel, string command)
{ {
@@ -274,7 +280,7 @@ namespace BTCPayServer.Plugins.Wabisabi
{ {
if (spendViewModel.SelectedCoins?.Any() is true) if (spendViewModel.SelectedCoins?.Any() is true)
{ {
coins = (CoinsView)coins.FilterBy(coin => coins = (CoinsView) coins.FilterBy(coin =>
spendViewModel.SelectedCoins.Contains(coin.Outpoint.ToString())); spendViewModel.SelectedCoins.Contains(coin.Outpoint.ToString()));
} }
} }
@@ -290,11 +296,11 @@ namespace BTCPayServer.Plugins.Wabisabi
var defaultCoinSelector = new DefaultCoinSelector(); var defaultCoinSelector = new DefaultCoinSelector();
var defaultSelection = var defaultSelection =
(defaultCoinSelector.Select(coins.Select(coin => coin.Coin).ToArray(), (defaultCoinSelector.Select(coins.Select(coin => coin.Coin).ToArray(),
new Money((decimal)spendViewModel.Amount, MoneyUnit.BTC)) ?? Array.Empty<ICoin>()) new Money((decimal) spendViewModel.Amount, MoneyUnit.BTC)) ?? Array.Empty<ICoin>())
.ToArray(); .ToArray();
var selector = new SmartCoinSelector(coins.ToList()); var selector = new SmartCoinSelector(coins.ToList());
var smartSelection = selector.Select(defaultSelection, var smartSelection = selector.Select(defaultSelection,
new Money((decimal)spendViewModel.Amount, MoneyUnit.BTC)); new Money((decimal) spendViewModel.Amount, MoneyUnit.BTC));
spendViewModel.SelectedCoins = smartSelection.Select(coin => coin.Outpoint.ToString()).ToArray(); spendViewModel.SelectedCoins = smartSelection.Select(coin => coin.Outpoint.ToString()).ToArray();
return View(spendViewModel); return View(spendViewModel);
} }
@@ -326,6 +332,26 @@ namespace BTCPayServer.Plugins.Wabisabi
return View(spendViewModel); return View(spendViewModel);
} }
[HttpGet("select-coins")]
public async Task<IActionResult> ComputeCoinSelection(string storeId, decimal amount)
{
if ((await _walletProvider.GetWalletAsync(storeId)) is not BTCPayWallet wallet)
{
return NotFound();
}
var coins = await wallet.GetAllCoins();
var defaultCoinSelector = new DefaultCoinSelector();
var defaultSelection =
(defaultCoinSelector.Select(coins.Select(coin => coin.Coin).ToArray(),
new Money(amount, MoneyUnit.BTC)) ?? Array.Empty<ICoin>())
.ToArray();
var selector = new SmartCoinSelector(coins.ToList());
var smartSelection = selector.Select(defaultSelection,
new Money((decimal) amount, MoneyUnit.BTC));
return Ok(smartSelection.Select(coin => coin.Outpoint.ToString()).ToArray());
}
public class SpendViewModel public class SpendViewModel
{ {