mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Add expert mode to BTCPay with No Change UTXO option
This commit is contained in:
@@ -146,7 +146,7 @@ namespace BTCPayServer.Controllers
|
|||||||
[Route("{walletId}/send")]
|
[Route("{walletId}/send")]
|
||||||
public async Task<IActionResult> WalletSend(
|
public async Task<IActionResult> WalletSend(
|
||||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||||
WalletId walletId, string defaultDestination = null, string defaultAmount = null)
|
WalletId walletId, string defaultDestination = null, string defaultAmount = null, bool advancedMode = false)
|
||||||
{
|
{
|
||||||
if (walletId?.StoreId == null)
|
if (walletId?.StoreId == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
@@ -195,6 +195,7 @@ namespace BTCPayServer.Controllers
|
|||||||
}
|
}
|
||||||
catch (Exception ex) { model.RateError = ex.Message; }
|
catch (Exception ex) { model.RateError = ex.Message; }
|
||||||
}
|
}
|
||||||
|
model.AdvancedMode = advancedMode;
|
||||||
return View(model);
|
return View(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +203,7 @@ namespace BTCPayServer.Controllers
|
|||||||
[Route("{walletId}/send")]
|
[Route("{walletId}/send")]
|
||||||
public async Task<IActionResult> WalletSend(
|
public async Task<IActionResult> WalletSend(
|
||||||
[ModelBinder(typeof(WalletIdModelBinder))]
|
[ModelBinder(typeof(WalletIdModelBinder))]
|
||||||
WalletId walletId, WalletSendModel vm)
|
WalletId walletId, WalletSendModel vm, string command = null)
|
||||||
{
|
{
|
||||||
if (walletId?.StoreId == null)
|
if (walletId?.StoreId == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
@@ -212,6 +213,14 @@ namespace BTCPayServer.Controllers
|
|||||||
var network = this.NetworkProvider.GetNetwork(walletId?.CryptoCode);
|
var network = this.NetworkProvider.GetNetwork(walletId?.CryptoCode);
|
||||||
if (network == null)
|
if (network == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|
||||||
|
if (command == "noob" || command == "expert")
|
||||||
|
{
|
||||||
|
ModelState.Clear();
|
||||||
|
vm.AdvancedMode = command == "expert";
|
||||||
|
return View(vm);
|
||||||
|
}
|
||||||
|
|
||||||
var destination = ParseDestination(vm.Destination, network.NBitcoinNetwork);
|
var destination = ParseDestination(vm.Destination, network.NBitcoinNetwork);
|
||||||
if (destination == null)
|
if (destination == null)
|
||||||
ModelState.AddModelError(nameof(vm.Destination), "Invalid address");
|
ModelState.AddModelError(nameof(vm.Destination), "Invalid address");
|
||||||
@@ -231,7 +240,8 @@ namespace BTCPayServer.Controllers
|
|||||||
Destination = vm.Destination,
|
Destination = vm.Destination,
|
||||||
Amount = vm.Amount.Value,
|
Amount = vm.Amount.Value,
|
||||||
SubstractFees = vm.SubstractFees,
|
SubstractFees = vm.SubstractFees,
|
||||||
FeeSatoshiPerByte = vm.FeeSatoshiPerByte
|
FeeSatoshiPerByte = vm.FeeSatoshiPerByte,
|
||||||
|
NoChange = vm.NoChange
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -403,6 +413,7 @@ namespace BTCPayServer.Controllers
|
|||||||
// getxpub
|
// getxpub
|
||||||
int account = 0,
|
int account = 0,
|
||||||
// sendtoaddress
|
// sendtoaddress
|
||||||
|
bool noChange = false,
|
||||||
string destination = null, string amount = null, string feeRate = null, string substractFees = null
|
string destination = null, string amount = null, string feeRate = null, string substractFees = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@@ -487,24 +498,16 @@ namespace BTCPayServer.Controllers
|
|||||||
var strategy = GetDirectDerivationStrategy(derivationScheme);
|
var strategy = GetDirectDerivationStrategy(derivationScheme);
|
||||||
var wallet = _walletProvider.GetWallet(network);
|
var wallet = _walletProvider.GetWallet(network);
|
||||||
var change = wallet.GetChangeAddressAsync(derivationScheme);
|
var change = wallet.GetChangeAddressAsync(derivationScheme);
|
||||||
|
var keypaths = new Dictionary<Script, KeyPath>();
|
||||||
var unspentCoins = await wallet.GetUnspentCoins(derivationScheme);
|
List<Coin> availableCoins = new List<Coin>();
|
||||||
var changeAddress = await change;
|
foreach (var c in await wallet.GetUnspentCoins(derivationScheme))
|
||||||
var send = new[] { (
|
|
||||||
destination: destinationAddress as IDestination,
|
|
||||||
amount: amountBTC,
|
|
||||||
substractFees: subsctractFeesValue) };
|
|
||||||
|
|
||||||
foreach (var element in send)
|
|
||||||
{
|
{
|
||||||
if (element.destination == null)
|
keypaths.TryAdd(c.Coin.ScriptPubKey, c.KeyPath);
|
||||||
throw new ArgumentNullException(nameof(element.destination));
|
availableCoins.Add(c.Coin);
|
||||||
if (element.amount == null)
|
|
||||||
throw new ArgumentNullException(nameof(element.amount));
|
|
||||||
if (element.amount <= Money.Zero)
|
|
||||||
throw new ArgumentOutOfRangeException(nameof(element.amount), "The amount should be above zero");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var changeAddress = await change;
|
||||||
|
|
||||||
var storeBlob = storeData.GetStoreBlob();
|
var storeBlob = storeData.GetStoreBlob();
|
||||||
var paymentId = new Payments.PaymentMethodId(cryptoCode, Payments.PaymentTypes.BTCLike);
|
var paymentId = new Payments.PaymentMethodId(cryptoCode, Payments.PaymentTypes.BTCLike);
|
||||||
var foundKeyPath = storeBlob.GetWalletKeyPathRoot(paymentId);
|
var foundKeyPath = storeBlob.GetWalletKeyPathRoot(paymentId);
|
||||||
@@ -520,10 +523,25 @@ namespace BTCPayServer.Controllers
|
|||||||
storeData.SetStoreBlob(storeBlob);
|
storeData.SetStoreBlob(storeBlob);
|
||||||
await Repository.UpdateStore(storeData);
|
await Repository.UpdateStore(storeData);
|
||||||
}
|
}
|
||||||
|
retry:
|
||||||
|
var send = new[] { (
|
||||||
|
destination: destinationAddress as IDestination,
|
||||||
|
amount: amountBTC,
|
||||||
|
substractFees: subsctractFeesValue) };
|
||||||
|
|
||||||
|
foreach (var element in send)
|
||||||
|
{
|
||||||
|
if (element.destination == null)
|
||||||
|
throw new ArgumentNullException(nameof(element.destination));
|
||||||
|
if (element.amount == null)
|
||||||
|
throw new ArgumentNullException(nameof(element.amount));
|
||||||
|
if (element.amount <= Money.Zero)
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(element.amount), "The amount should be above zero");
|
||||||
|
}
|
||||||
|
|
||||||
TransactionBuilder builder = network.NBitcoinNetwork.CreateTransactionBuilder();
|
TransactionBuilder builder = network.NBitcoinNetwork.CreateTransactionBuilder();
|
||||||
builder.StandardTransactionPolicy.MinRelayTxFee = summary.Status.BitcoinStatus.MinRelayTxFee;
|
builder.StandardTransactionPolicy.MinRelayTxFee = summary.Status.BitcoinStatus.MinRelayTxFee;
|
||||||
builder.AddCoins(unspentCoins.Select(c => c.Coin).ToArray());
|
builder.AddCoins(availableCoins);
|
||||||
|
|
||||||
foreach (var element in send)
|
foreach (var element in send)
|
||||||
{
|
{
|
||||||
@@ -531,6 +549,7 @@ namespace BTCPayServer.Controllers
|
|||||||
if (element.substractFees)
|
if (element.substractFees)
|
||||||
builder.SubtractFees();
|
builder.SubtractFees();
|
||||||
}
|
}
|
||||||
|
|
||||||
builder.SetChange(changeAddress.Item1);
|
builder.SetChange(changeAddress.Item1);
|
||||||
|
|
||||||
if (network.MinFee == null)
|
if (network.MinFee == null)
|
||||||
@@ -547,13 +566,15 @@ namespace BTCPayServer.Controllers
|
|||||||
}
|
}
|
||||||
var unsigned = builder.BuildTransaction(false);
|
var unsigned = builder.BuildTransaction(false);
|
||||||
|
|
||||||
var keypaths = new Dictionary<Script, KeyPath>();
|
var hasChange = unsigned.Outputs.Any(o => o.ScriptPubKey == changeAddress.Item1.ScriptPubKey);
|
||||||
foreach (var c in unspentCoins)
|
if (noChange && hasChange)
|
||||||
{
|
{
|
||||||
keypaths.TryAdd(c.Coin.ScriptPubKey, c.KeyPath);
|
availableCoins = builder.FindSpentCoins(unsigned).Cast<Coin>().ToList();
|
||||||
|
amountBTC = builder.FindSpentCoins(unsigned).Select(c => c.TxOut.Value).Sum();
|
||||||
|
subsctractFeesValue = true;
|
||||||
|
goto retry;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasChange = unsigned.Outputs.Count == 2;
|
|
||||||
var usedCoins = builder.FindSpentCoins(unsigned);
|
var usedCoins = builder.FindSpentCoins(unsigned);
|
||||||
|
|
||||||
Dictionary<uint256, Transaction> parentTransactions = new Dictionary<uint256, Transaction>();
|
Dictionary<uint256, Transaction> parentTransactions = new Dictionary<uint256, Transaction>();
|
||||||
|
|||||||
@@ -11,5 +11,6 @@ namespace BTCPayServer.Models.WalletViewModels
|
|||||||
public bool SubstractFees { get; set; }
|
public bool SubstractFees { get; set; }
|
||||||
public decimal Amount { get; set; }
|
public decimal Amount { get; set; }
|
||||||
public string Destination { get; set; }
|
public string Destination { get; set; }
|
||||||
|
public bool NoChange { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ namespace BTCPayServer.Models.WalletViewModels
|
|||||||
[Display(Name = "Fee rate (satoshi per byte)")]
|
[Display(Name = "Fee rate (satoshi per byte)")]
|
||||||
[Required]
|
[Required]
|
||||||
public int FeeSatoshiPerByte { get; set; }
|
public int FeeSatoshiPerByte { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Make sure no change UTXO is created")]
|
||||||
|
public bool NoChange { get; set; }
|
||||||
|
public bool AdvancedMode { get; set; }
|
||||||
public decimal? Rate { get; set; }
|
public decimal? Rate { get; set; }
|
||||||
public int Divisibility { get; set; }
|
public int Divisibility { get; set; }
|
||||||
public string Fiat { get; set; }
|
public string Fiat { get; set; }
|
||||||
|
|||||||
@@ -55,7 +55,23 @@
|
|||||||
<label asp-for="SubstractFees"></label>
|
<label asp-for="SubstractFees"></label>
|
||||||
<input asp-for="SubstractFees" class="form-check" />
|
<input asp-for="SubstractFees" class="form-check" />
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-primary">Confirm</button>
|
@if (Model.AdvancedMode)
|
||||||
|
{
|
||||||
|
<div class="form-group">
|
||||||
|
<label asp-for="NoChange"></label>
|
||||||
|
<a href="https://docs.btcpayserver.org/features/wallet#make-sure-no-change-utxo-is-created-expert-mode" target="_blank"><span class="fa fa-question-circle-o" title="More information..."></span></a>
|
||||||
|
<input asp-for="NoChange" class="form-check" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
<button name="command" type="submit" class="btn btn-primary">Confirm</button>
|
||||||
|
@if (Model.AdvancedMode)
|
||||||
|
{
|
||||||
|
<button name="command" type="submit" value="noob" class="btn btn-secondary">Use noob mode</button>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<button name="command" type="submit" value="expert" class="btn btn-secondary">Use expert mode</button>
|
||||||
|
}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
<input type="hidden" asp-for="Amount" />
|
<input type="hidden" asp-for="Amount" />
|
||||||
<input type="hidden" asp-for="FeeSatoshiPerByte" />
|
<input type="hidden" asp-for="FeeSatoshiPerByte" />
|
||||||
<input type="hidden" asp-for="SubstractFees" />
|
<input type="hidden" asp-for="SubstractFees" />
|
||||||
|
<input type="hidden" asp-for="NoChange" />
|
||||||
<p>
|
<p>
|
||||||
You can send money received by this store to an address with the help of your Ledger Wallet. <br />
|
You can send money received by this store to an address with the help of your Ledger Wallet. <br />
|
||||||
If you don't have a Ledger Wallet, use Electrum with your favorite hardware wallet to transfer crypto. <br />
|
If you don't have a Ledger Wallet, use Electrum with your favorite hardware wallet to transfer crypto. <br />
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
var amount = $("#Amount").val();
|
var amount = $("#Amount").val();
|
||||||
var fee = $("#FeeSatoshiPerByte").val();
|
var fee = $("#FeeSatoshiPerByte").val();
|
||||||
var substractFee = $("#SubstractFees").val();
|
var substractFee = $("#SubstractFees").val();
|
||||||
|
var noChange = $("#NoChange").val();
|
||||||
|
|
||||||
var loc = window.location, ws_uri;
|
var loc = window.location, ws_uri;
|
||||||
if (loc.protocol === "https:") {
|
if (loc.protocol === "https:") {
|
||||||
@@ -48,8 +49,14 @@
|
|||||||
args += "&amount=" + amount;
|
args += "&amount=" + amount;
|
||||||
args += "&feeRate=" + fee;
|
args += "&feeRate=" + fee;
|
||||||
args += "&substractFees=" + substractFee;
|
args += "&substractFees=" + substractFee;
|
||||||
|
args += "&noChange=" + noChange;
|
||||||
|
|
||||||
WriteAlert("warning", 'Please validate the transaction on your ledger');
|
if (noChange === "True") {
|
||||||
|
WriteAlert("warning", 'WARNING: Because you want to make sure no change UTXO is created, you will end up sending more than the chosen amount to your destination. Please validate the transaction on your ledger');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
WriteAlert("warning", 'Please validate the transaction on your ledger');
|
||||||
|
}
|
||||||
|
|
||||||
var confirmButton = $("#confirm-button");
|
var confirmButton = $("#confirm-button");
|
||||||
confirmButton.prop("disabled", true);
|
confirmButton.prop("disabled", true);
|
||||||
|
|||||||
Reference in New Issue
Block a user