mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-23 06:35:13 +01:00
Add PSBT support in the send screen
This commit is contained in:
parent
2a145f4350
commit
03713f9bd8
2 changed files with 70 additions and 49 deletions
|
@ -202,7 +202,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, CancellationToken cancellation = default)
|
||||||
{
|
{
|
||||||
if (walletId?.StoreId == null)
|
if (walletId?.StoreId == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
@ -227,14 +227,58 @@ namespace BTCPayServer.Controllers
|
||||||
if (!ModelState.IsValid)
|
if (!ModelState.IsValid)
|
||||||
return View(vm);
|
return View(vm);
|
||||||
|
|
||||||
return RedirectToAction(nameof(WalletSendLedger), new WalletSendLedgerModel()
|
var sendModel = new WalletSendLedgerModel()
|
||||||
{
|
{
|
||||||
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
|
NoChange = vm.NoChange
|
||||||
});
|
};
|
||||||
|
if (command == "ledger")
|
||||||
|
{
|
||||||
|
return RedirectToAction(nameof(WalletSendLedger), sendModel);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var storeData = (await Repository.FindStore(walletId.StoreId, GetUserId()));
|
||||||
|
var derivationScheme = GetPaymentMethod(walletId, storeData).DerivationStrategyBase;
|
||||||
|
var psbt = await CreatePSBT(network, derivationScheme, sendModel, cancellation);
|
||||||
|
return File(psbt.PSBT.ToBytes(), "application/octet-stream", $"Send-{vm.Amount.Value}-{network.CryptoCode}-to-{destination[0].ToString()}.psbt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<CreatePSBTResponse> CreatePSBT(BTCPayNetwork network, DerivationStrategyBase derivationScheme, WalletSendLedgerModel sendModel, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var nbx = ExplorerClientProvider.GetExplorerClient(network);
|
||||||
|
CreatePSBTRequest psbtRequest = new CreatePSBTRequest();
|
||||||
|
CreatePSBTDestination psbtDestination = new CreatePSBTDestination();
|
||||||
|
psbtRequest.Destinations.Add(psbtDestination);
|
||||||
|
psbtDestination.Destination = BitcoinAddress.Create(sendModel.Destination, network.NBitcoinNetwork);
|
||||||
|
psbtDestination.Amount = Money.Coins(sendModel.Amount);
|
||||||
|
psbtRequest.FeePreference = new FeePreference();
|
||||||
|
psbtRequest.FeePreference.ExplicitFeeRate = new FeeRate(Money.Satoshis(sendModel.FeeSatoshiPerByte), 1);
|
||||||
|
if (sendModel.NoChange)
|
||||||
|
{
|
||||||
|
psbtRequest.ExplicitChangeAddress = psbtDestination.Destination;
|
||||||
|
}
|
||||||
|
psbtDestination.SubstractFees = sendModel.SubstractFees;
|
||||||
|
|
||||||
|
var psbt = (await nbx.CreatePSBTAsync(derivationScheme, psbtRequest, cancellationToken));
|
||||||
|
if (psbt == null)
|
||||||
|
throw new NotSupportedException("You need to update your version of NBXplorer");
|
||||||
|
|
||||||
|
if (network.MinFee != null)
|
||||||
|
{
|
||||||
|
psbt.PSBT.TryGetFee(out var fee);
|
||||||
|
if (fee < network.MinFee)
|
||||||
|
{
|
||||||
|
psbtRequest.FeePreference = new FeePreference() { ExplicitFee = network.MinFee };
|
||||||
|
psbt = (await nbx.CreatePSBTAsync(derivationScheme, psbtRequest, cancellationToken));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return psbt;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
|
@ -403,7 +447,7 @@ namespace BTCPayServer.Controllers
|
||||||
int account = 0,
|
int account = 0,
|
||||||
// sendtoaddress
|
// sendtoaddress
|
||||||
bool noChange = false,
|
bool noChange = false,
|
||||||
string destination = null, string amount = null, string feeRate = null, string substractFees = null
|
string destination = null, string amount = null, string feeRate = null, bool substractFees = false
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
if (!HttpContext.WebSockets.IsWebSocketRequest)
|
if (!HttpContext.WebSockets.IsWebSocketRequest)
|
||||||
|
@ -413,7 +457,6 @@ namespace BTCPayServer.Controllers
|
||||||
var storeData = (await Repository.FindStore(walletId.StoreId, GetUserId()));
|
var storeData = (await Repository.FindStore(walletId.StoreId, GetUserId()));
|
||||||
var derivationScheme = GetPaymentMethod(walletId, storeData).DerivationStrategyBase;
|
var derivationScheme = GetPaymentMethod(walletId, storeData).DerivationStrategyBase;
|
||||||
|
|
||||||
var psbtRequest = new CreatePSBTRequest();
|
|
||||||
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
||||||
|
|
||||||
using (var normalOperationTimeout = new CancellationTokenSource())
|
using (var normalOperationTimeout = new CancellationTokenSource())
|
||||||
|
@ -421,6 +464,7 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
normalOperationTimeout.CancelAfter(TimeSpan.FromMinutes(30));
|
normalOperationTimeout.CancelAfter(TimeSpan.FromMinutes(30));
|
||||||
var hw = new HardwareWalletService(webSocket);
|
var hw = new HardwareWalletService(webSocket);
|
||||||
|
var model = new WalletSendLedgerModel();
|
||||||
object result = null;
|
object result = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
@ -432,33 +476,25 @@ namespace BTCPayServer.Controllers
|
||||||
throw new FormatException("Invalid value for crypto code");
|
throw new FormatException("Invalid value for crypto code");
|
||||||
}
|
}
|
||||||
|
|
||||||
CreatePSBTDestination destinationPSBT = null;
|
|
||||||
if (destination != null)
|
if (destination != null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
destinationPSBT = new CreatePSBTDestination()
|
BitcoinAddress.Create(destination.Trim(), network.NBitcoinNetwork);
|
||||||
{
|
model.Destination = destination.Trim();
|
||||||
Destination = BitcoinAddress.Create(destination.Trim(), network.NBitcoinNetwork)
|
|
||||||
};
|
|
||||||
psbtRequest.Destinations.Add(destinationPSBT);
|
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
if (destinationPSBT == null)
|
|
||||||
throw new FormatException("Invalid value for destination");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (feeRate != null)
|
if (feeRate != null)
|
||||||
{
|
{
|
||||||
psbtRequest.FeePreference = new FeePreference();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
psbtRequest.FeePreference.ExplicitFeeRate = new FeeRate(Money.Satoshis(int.Parse(feeRate, CultureInfo.InvariantCulture)), 1);
|
model.FeeSatoshiPerByte = int.Parse(feeRate, CultureInfo.InvariantCulture);
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
if (psbtRequest.FeePreference.ExplicitFeeRate == null ||
|
if (model.FeeSatoshiPerByte <= 0)
|
||||||
psbtRequest.FeePreference.ExplicitFeeRate.FeePerK <= Money.Zero)
|
|
||||||
throw new FormatException("Invalid value for fee rate");
|
throw new FormatException("Invalid value for fee rate");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,21 +502,15 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
destinationPSBT.Amount = Money.Parse(amount);
|
model.Amount = Money.Parse(amount).ToDecimal(MoneyUnit.BTC);
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
if (destinationPSBT.Amount == null || destinationPSBT.Amount <= Money.Zero)
|
if (model.Amount <= 0m)
|
||||||
throw new FormatException("Invalid value for amount");
|
throw new FormatException("Invalid value for amount");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (substractFees != null)
|
model.SubstractFees = substractFees;
|
||||||
{
|
model.NoChange = noChange;
|
||||||
try
|
|
||||||
{
|
|
||||||
destinationPSBT.SubstractFees = bool.Parse(substractFees);
|
|
||||||
}
|
|
||||||
catch { throw new FormatException("Invalid value for subtract fees"); }
|
|
||||||
}
|
|
||||||
if (command == "test")
|
if (command == "test")
|
||||||
{
|
{
|
||||||
result = await hw.Test(normalOperationTimeout.Token);
|
result = await hw.Test(normalOperationTimeout.Token);
|
||||||
|
@ -489,27 +519,10 @@ namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
if (!_dashboard.IsFullySynched(network.CryptoCode, out var summary))
|
if (!_dashboard.IsFullySynched(network.CryptoCode, out var summary))
|
||||||
throw new Exception($"{network.CryptoCode}: not started or fully synched");
|
throw new Exception($"{network.CryptoCode}: not started or fully synched");
|
||||||
|
|
||||||
|
var psbt = await CreatePSBT(network, derivationScheme, model, normalOperationTimeout.Token);
|
||||||
|
|
||||||
var strategy = GetDirectDerivationStrategy(derivationScheme);
|
var strategy = GetDirectDerivationStrategy(derivationScheme);
|
||||||
var nbx = ExplorerClientProvider.GetExplorerClient(network);
|
|
||||||
|
|
||||||
if (noChange)
|
|
||||||
{
|
|
||||||
psbtRequest.ExplicitChangeAddress = destinationPSBT.Destination;
|
|
||||||
}
|
|
||||||
var psbt = (await nbx.CreatePSBTAsync(derivationScheme, psbtRequest, normalOperationTimeout.Token));
|
|
||||||
if (psbt == null)
|
|
||||||
throw new Exception("You need to update your version of NBXplorer");
|
|
||||||
|
|
||||||
if (network.MinFee != null)
|
|
||||||
{
|
|
||||||
psbt.PSBT.TryGetFee(out var fee);
|
|
||||||
if (fee < network.MinFee)
|
|
||||||
{
|
|
||||||
psbtRequest.FeePreference = new FeePreference() { ExplicitFee = network.MinFee };
|
|
||||||
psbt = (await nbx.CreatePSBTAsync(derivationScheme, psbtRequest, normalOperationTimeout.Token));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
||||||
|
@ -549,7 +562,7 @@ namespace BTCPayServer.Controllers
|
||||||
var transaction = psbt.PSBT.ExtractTransaction();
|
var transaction = psbt.PSBT.ExtractTransaction();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var broadcastResult = await nbx.BroadcastAsync(transaction);
|
var broadcastResult = await ExplorerClientProvider.GetExplorerClient(network).BroadcastAsync(transaction);
|
||||||
if (!broadcastResult.Success)
|
if (!broadcastResult.Success)
|
||||||
{
|
{
|
||||||
throw new Exception($"RPC Error while broadcasting: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}");
|
throw new Exception($"RPC Error while broadcasting: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}");
|
||||||
|
|
|
@ -75,7 +75,15 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<button name="command" type="submit" class="btn btn-primary" style="margin-top:16px;">Confirm</button>
|
<div class="dropdown" style="margin-top:16px;">
|
||||||
|
<button class="btn btn-primary dropdown-toggle" type="button" id="SendMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
|
Sign with...
|
||||||
|
</button>
|
||||||
|
<div class="dropdown-menu" aria-labelledby="SendMenu">
|
||||||
|
<button name="command" type="submit" class="dropdown-item" value="ledger">... your Ledger Wallet device</button>
|
||||||
|
<button name="command" type="submit" class="dropdown-item" value="save-psbt">... a wallet supporting PSBT</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Add table
Reference in a new issue