public async Task <IActionResult> LedgerConnection( string command, // getinfo string cryptoCode = null, // getxpub [ModelBinder(typeof(ModelBinders.DerivationSchemeModelBinder))] DerivationStrategyBase derivationScheme = null, int account = 0, // sendtoaddress string destination = null, string amount = null, string feeRate = null, string substractFees = null ) { if (!HttpContext.WebSockets.IsWebSocketRequest) { return(NotFound()); } var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); using (var normalOperationTimeout = new CancellationTokenSource()) using (var signTimeout = new CancellationTokenSource()) { normalOperationTimeout.CancelAfter(TimeSpan.FromMinutes(30)); var hw = new HardwareWalletService(webSocket); object result = null; try { BTCPayNetwork network = null; if (cryptoCode != null) { network = _NetworkProvider.GetNetwork(cryptoCode); if (network == null) { throw new FormatException("Invalid value for crypto code"); } } BitcoinAddress destinationAddress = null; if (destination != null) { try { destinationAddress = BitcoinAddress.Create(destination, network.NBitcoinNetwork); } catch { } if (destinationAddress == null) { throw new FormatException("Invalid value for destination"); } } FeeRate feeRateValue = null; if (feeRate != null) { try { feeRateValue = new FeeRate(Money.Satoshis(int.Parse(feeRate, CultureInfo.InvariantCulture)), 1); } catch { } if (feeRateValue == null || feeRateValue.FeePerK <= Money.Zero) { throw new FormatException("Invalid value for fee rate"); } } Money amountBTC = null; if (amount != null) { try { amountBTC = Money.Parse(amount); } catch { } if (amountBTC == null || amountBTC <= Money.Zero) { throw new FormatException("Invalid value for amount"); } } bool subsctractFeesValue = false; if (substractFees != null) { try { subsctractFeesValue = bool.Parse(substractFees); } catch { throw new FormatException("Invalid value for subtract fees"); } } if (command == "test") { result = await hw.Test(normalOperationTimeout.Token); } if (command == "getxpub") { var getxpubResult = await hw.GetExtPubKey(network, account, normalOperationTimeout.Token); result = getxpubResult; } if (command == "getinfo") { var strategy = GetDirectDerivationStrategy(derivationScheme); if (strategy == null || await hw.GetKeyPath(network, strategy, normalOperationTimeout.Token) == null) { throw new Exception($"This store is not configured to use this ledger"); } var feeProvider = _feeRateProvider.CreateFeeProvider(network); var recommendedFees = feeProvider.GetFeeRateAsync(); var balance = _walletProvider.GetWallet(network).GetBalance(derivationScheme); result = new GetInfoResult() { Balance = (double)(await balance).ToDecimal(MoneyUnit.BTC), RecommendedSatoshiPerByte = (int)(await recommendedFees).GetFee(1).Satoshi }; } if (command == "sendtoaddress") { if (!_dashboard.IsFullySynched(network.CryptoCode, out var summary)) { throw new Exception($"{network.CryptoCode}: not started or fully synched"); } var strategy = GetDirectDerivationStrategy(derivationScheme); var wallet = _walletProvider.GetWallet(network); var change = wallet.GetChangeAddressAsync(derivationScheme); var unspentCoins = await wallet.GetUnspentCoins(derivationScheme); var changeAddress = await change; var send = new[] { (
public async Task <IActionResult> LedgerConnection( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, string command, // getinfo // getxpub int account = 0, // sendtoaddress bool noChange = false, string destination = null, string amount = null, string feeRate = null, bool substractFees = false, bool disableRBF = false ) { if (!HttpContext.WebSockets.IsWebSocketRequest) { return(NotFound()); } var cryptoCode = walletId.CryptoCode; var storeData = (await Repository.FindStore(walletId.StoreId, GetUserId())); var derivationSettings = GetPaymentMethod(walletId, storeData); var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); using (var normalOperationTimeout = new CancellationTokenSource()) using (var signTimeout = new CancellationTokenSource()) { normalOperationTimeout.CancelAfter(TimeSpan.FromMinutes(30)); var hw = new HardwareWalletService(webSocket); var model = new WalletSendLedgerModel(); object result = null; try { BTCPayNetwork network = null; if (cryptoCode != null) { network = NetworkProvider.GetNetwork(cryptoCode); if (network == null) { throw new FormatException("Invalid value for crypto code"); } } if (destination != null) { try { BitcoinAddress.Create(destination.Trim(), network.NBitcoinNetwork); model.Destination = destination.Trim(); } catch { } } if (feeRate != null) { try { model.FeeSatoshiPerByte = int.Parse(feeRate, CultureInfo.InvariantCulture); } catch { } if (model.FeeSatoshiPerByte <= 0) { throw new FormatException("Invalid value for fee rate"); } } if (amount != null) { try { model.Amount = Money.Parse(amount).ToDecimal(MoneyUnit.BTC); } catch { } if (model.Amount <= 0m) { throw new FormatException("Invalid value for amount"); } } model.SubstractFees = substractFees; model.NoChange = noChange; model.DisableRBF = disableRBF; if (command == "test") { result = await hw.Test(normalOperationTimeout.Token); } if (command == "sendtoaddress") { if (!_dashboard.IsFullySynched(network.CryptoCode, out var summary)) { throw new Exception($"{network.CryptoCode}: not started or fully synched"); } var strategy = GetDirectDerivationStrategy(derivationSettings.AccountDerivation); // Some deployment have the wallet root key path saved in the store blob // If it does, we only have to make 1 call to the hw to check if it can sign the given strategy, if (derivationSettings.AccountKeyPath == null || !await hw.CanSign(network, strategy, derivationSettings.AccountKeyPath, normalOperationTimeout.Token)) { // If the saved wallet key path is not present or incorrect, let's scan the wallet to see if it can sign strategy var foundKeyPath = await hw.FindKeyPath(network, strategy, normalOperationTimeout.Token); if (foundKeyPath == null) { throw new HardwareWalletException($"This store is not configured to use this ledger"); } derivationSettings.AccountKeyPath = foundKeyPath; storeData.SetSupportedPaymentMethod(derivationSettings); await Repository.UpdateStore(storeData); } var psbt = await CreatePSBT(network, derivationSettings, model, normalOperationTimeout.Token); signTimeout.CancelAfter(TimeSpan.FromMinutes(5)); psbt.PSBT = await hw.SignTransactionAsync(psbt.PSBT, psbt.ChangeAddress?.ScriptPubKey, signTimeout.Token); if (!psbt.PSBT.TryFinalize(out var errors)) { throw new Exception($"Error while finalizing the transaction ({new PSBTException(errors).ToString()})"); } var transaction = psbt.PSBT.ExtractTransaction(); try { var broadcastResult = await ExplorerClientProvider.GetExplorerClient(network).BroadcastAsync(transaction); if (!broadcastResult.Success) { throw new Exception($"RPC Error while broadcasting: {broadcastResult.RPCCode} {broadcastResult.RPCCodeMessage} {broadcastResult.RPCMessage}"); } } catch (Exception ex) { throw new Exception("Error while broadcasting: " + ex.Message); } var wallet = _walletProvider.GetWallet(network); wallet.InvalidateCache(derivationSettings.AccountDerivation); result = new SendToAddressResult() { TransactionId = transaction.GetHash().ToString() }; } } catch (OperationCanceledException) { result = new LedgerTestResult() { Success = false, Error = "Timeout" }; } catch (Exception ex) { result = new LedgerTestResult() { Success = false, Error = ex.Message }; } finally { hw.Dispose(); } try { if (result != null) { UTF8Encoding UTF8NOBOM = new UTF8Encoding(false); var bytes = UTF8NOBOM.GetBytes(JsonConvert.SerializeObject(result, _mvcJsonOptions.Value.SerializerSettings)); await webSocket.SendAsync(new ArraySegment <byte>(bytes), WebSocketMessageType.Text, true, new CancellationTokenSource(2000).Token); } } catch { } finally { await webSocket.CloseSocket(); } } return(new EmptyResult()); }
public async Task <IActionResult> LedgerConnection( string storeId, string command, // getinfo string cryptoCode = null, // sendtoaddress string destination = null, string amount = null, string feeRate = null, string substractFees = null ) { if (!HttpContext.WebSockets.IsWebSocketRequest) { return(NotFound()); } var store = await _Repo.FindStore(storeId, GetUserId()); if (store == null) { return(NotFound()); } var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); var hw = new HardwareWalletService(webSocket); object result = null; try { BTCPayNetwork network = null; if (cryptoCode != null) { network = _NetworkProvider.GetNetwork(cryptoCode); if (network == null) { throw new FormatException("Invalid value for crypto code"); } } BitcoinAddress destinationAddress = null; if (destination != null) { try { destinationAddress = BitcoinAddress.Create(destination); } catch { } if (destinationAddress == null) { throw new FormatException("Invalid value for destination"); } } FeeRate feeRateValue = null; if (feeRate != null) { try { feeRateValue = new FeeRate(Money.Satoshis(int.Parse(feeRate)), 1); } catch { } if (feeRateValue == null || feeRateValue.FeePerK <= Money.Zero) { throw new FormatException("Invalid value for fee rate"); } } Money amountBTC = null; if (amount != null) { try { amountBTC = Money.Parse(amount); } catch { } if (amountBTC == null || amountBTC <= Money.Zero) { throw new FormatException("Invalid value for amount"); } } bool subsctractFeesValue = false; if (substractFees != null) { try { subsctractFeesValue = bool.Parse(substractFees); } catch { throw new FormatException("Invalid value for substract fees"); } } if (command == "test") { result = await hw.Test(); } if (command == "getxpub") { result = await hw.GetExtPubKey(network); } if (command == "getinfo") { var strategy = GetDirectDerivationStrategy(store, network); var strategyBase = GetDerivationStrategy(store, network); if (!await hw.SupportDerivation(network, strategy)) { throw new Exception($"This store is not configured to use this ledger"); } var feeProvider = _FeeRateProvider.CreateFeeProvider(network); var recommendedFees = feeProvider.GetFeeRateAsync(); var balance = _WalletProvider.GetWallet(network).GetBalance(strategyBase); result = new GetInfoResult() { Balance = (double)(await balance).ToDecimal(MoneyUnit.BTC), RecommendedSatoshiPerByte = (int)(await recommendedFees).GetFee(1).Satoshi }; } if (command == "sendtoaddress") { var strategy = GetDirectDerivationStrategy(store, network); var strategyBase = GetDerivationStrategy(store, network); var wallet = _WalletProvider.GetWallet(network); var change = wallet.GetChangeAddressAsync(strategyBase); var unspentCoins = await wallet.GetUnspentCoins(strategyBase); var changeAddress = await change; unspentCoins.Item2.TryAdd(changeAddress.Item1.ScriptPubKey, changeAddress.Item2); var transaction = await hw.SendToAddress(strategy, unspentCoins.Item1, network, new[] { (destinationAddress as IDestination, amountBTC, subsctractFeesValue) },
public async Task <IActionResult> AddDerivationSchemeLedger( string storeId, string cryptoCode, string command, int account = 0) { if (!HttpContext.WebSockets.IsWebSocketRequest) { return(NotFound()); } var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); var hw = new HardwareWalletService(webSocket); object result = null; var network = _NetworkProvider.GetNetwork(cryptoCode); using (var normalOperationTimeout = new CancellationTokenSource()) { normalOperationTimeout.CancelAfter(TimeSpan.FromMinutes(30)); try { if (command == "test") { result = await hw.Test(normalOperationTimeout.Token); } if (command == "getxpub") { var getxpubResult = await hw.GetExtPubKey(network, account, normalOperationTimeout.Token); result = getxpubResult; } } catch (OperationCanceledException) { result = new LedgerTestResult() { Success = false, Error = "Timeout" }; } catch (Exception ex) { result = new LedgerTestResult() { Success = false, Error = ex.Message }; } finally { hw.Dispose(); } try { if (result != null) { UTF8Encoding UTF8NOBOM = new UTF8Encoding(false); var bytes = UTF8NOBOM.GetBytes(JsonConvert.SerializeObject(result, MvcJsonOptions.Value.SerializerSettings)); await webSocket.SendAsync(new ArraySegment <byte>(bytes), WebSocketMessageType.Text, true, new CancellationTokenSource(2000).Token); } } catch { } finally { await webSocket.CloseSocket(); } } return(new EmptyResult()); }
public async Task <IActionResult> LedgerConnection( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, string command, // getinfo // getxpub int account = 0, // sendtoaddress string destination = null, string amount = null, string feeRate = null, string substractFees = null ) { if (!HttpContext.WebSockets.IsWebSocketRequest) { return(NotFound()); } var cryptoCode = walletId.CryptoCode; var storeData = (await Repository.FindStore(walletId.StoreId, GetUserId())); var derivationScheme = GetPaymentMethod(walletId, storeData).DerivationStrategyBase; var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); using (var normalOperationTimeout = new CancellationTokenSource()) using (var signTimeout = new CancellationTokenSource()) { normalOperationTimeout.CancelAfter(TimeSpan.FromMinutes(30)); var hw = new HardwareWalletService(webSocket); object result = null; try { BTCPayNetwork network = null; if (cryptoCode != null) { network = NetworkProvider.GetNetwork(cryptoCode); if (network == null) { throw new FormatException("Invalid value for crypto code"); } } BitcoinAddress destinationAddress = null; if (destination != null) { try { destinationAddress = BitcoinAddress.Create(destination, network.NBitcoinNetwork); } catch { } if (destinationAddress == null) { throw new FormatException("Invalid value for destination"); } } FeeRate feeRateValue = null; if (feeRate != null) { try { feeRateValue = new FeeRate(Money.Satoshis(int.Parse(feeRate, CultureInfo.InvariantCulture)), 1); } catch { } if (feeRateValue == null || feeRateValue.FeePerK <= Money.Zero) { throw new FormatException("Invalid value for fee rate"); } } Money amountBTC = null; if (amount != null) { try { amountBTC = Money.Parse(amount); } catch { } if (amountBTC == null || amountBTC <= Money.Zero) { throw new FormatException("Invalid value for amount"); } } bool subsctractFeesValue = false; if (substractFees != null) { try { subsctractFeesValue = bool.Parse(substractFees); } catch { throw new FormatException("Invalid value for subtract fees"); } } if (command == "test") { result = await hw.Test(normalOperationTimeout.Token); } if (command == "sendtoaddress") { if (!_dashboard.IsFullySynched(network.CryptoCode, out var summary)) { throw new Exception($"{network.CryptoCode}: not started or fully synched"); } var strategy = GetDirectDerivationStrategy(derivationScheme); var wallet = _walletProvider.GetWallet(network); var change = wallet.GetChangeAddressAsync(derivationScheme); var unspentCoins = await wallet.GetUnspentCoins(derivationScheme); var changeAddress = await change; var send = new[] { (
public async Task <IActionResult> LedgerConnection( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, string command, // getinfo // getxpub int account = 0, // sendtoaddress bool noChange = false, string destination = null, string amount = null, string feeRate = null, string substractFees = null ) { if (!HttpContext.WebSockets.IsWebSocketRequest) { return(NotFound()); } var cryptoCode = walletId.CryptoCode; var storeData = (await Repository.FindStore(walletId.StoreId, GetUserId())); var derivationScheme = GetPaymentMethod(walletId, storeData).DerivationStrategyBase; var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); using (var normalOperationTimeout = new CancellationTokenSource()) using (var signTimeout = new CancellationTokenSource()) { normalOperationTimeout.CancelAfter(TimeSpan.FromMinutes(30)); var hw = new HardwareWalletService(webSocket); object result = null; try { BTCPayNetwork network = null; if (cryptoCode != null) { network = NetworkProvider.GetNetwork(cryptoCode); if (network == null) { throw new FormatException("Invalid value for crypto code"); } } BitcoinAddress destinationAddress = null; if (destination != null) { try { destinationAddress = BitcoinAddress.Create(destination.Trim(), network.NBitcoinNetwork); } catch { } if (destinationAddress == null) { throw new FormatException("Invalid value for destination"); } } FeeRate feeRateValue = null; if (feeRate != null) { try { feeRateValue = new FeeRate(Money.Satoshis(int.Parse(feeRate, CultureInfo.InvariantCulture)), 1); } catch { } if (feeRateValue == null || feeRateValue.FeePerK <= Money.Zero) { throw new FormatException("Invalid value for fee rate"); } } Money amountBTC = null; if (amount != null) { try { amountBTC = Money.Parse(amount); } catch { } if (amountBTC == null || amountBTC <= Money.Zero) { throw new FormatException("Invalid value for amount"); } } bool subsctractFeesValue = false; if (substractFees != null) { try { subsctractFeesValue = bool.Parse(substractFees); } catch { throw new FormatException("Invalid value for subtract fees"); } } if (command == "test") { result = await hw.Test(normalOperationTimeout.Token); } if (command == "sendtoaddress") { if (!_dashboard.IsFullySynched(network.CryptoCode, out var summary)) { throw new Exception($"{network.CryptoCode}: not started or fully synched"); } var strategy = GetDirectDerivationStrategy(derivationScheme); var wallet = _walletProvider.GetWallet(network); var change = wallet.GetChangeAddressAsync(derivationScheme); var keypaths = new Dictionary <Script, KeyPath>(); List <Coin> availableCoins = new List <Coin>(); foreach (var c in await wallet.GetUnspentCoins(derivationScheme)) { keypaths.TryAdd(c.Coin.ScriptPubKey, c.KeyPath); availableCoins.Add(c.Coin); } var changeAddress = await change; var storeBlob = storeData.GetStoreBlob(); var paymentId = new Payments.PaymentMethodId(cryptoCode, Payments.PaymentTypes.BTCLike); var foundKeyPath = storeBlob.GetWalletKeyPathRoot(paymentId); // Some deployment have the wallet root key path saved in the store blob // If it does, we only have to make 1 call to the hw to check if it can sign the given strategy, if (foundKeyPath == null || !await hw.CanSign(network, strategy, foundKeyPath, normalOperationTimeout.Token)) { // If the saved wallet key path is not present or incorrect, let's scan the wallet to see if it can sign strategy foundKeyPath = await hw.FindKeyPath(network, strategy, normalOperationTimeout.Token); if (foundKeyPath == null) { throw new HardwareWalletException($"This store is not configured to use this ledger"); } storeBlob.SetWalletKeyPathRoot(paymentId, foundKeyPath); storeData.SetStoreBlob(storeBlob); await Repository.UpdateStore(storeData); } retry: var send = new[] { (