mirror of
https://github.com/aljazceru/BTCPayServerPlugins.git
synced 2025-12-18 16:14:25 +01:00
better send tools
This commit is contained in:
@@ -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()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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>
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user