public string GetDestination(BTCPayNetwork network) { return(GetPaymentId()); }
private async Task <PSBT> GetPayjoinProposedTX(BitcoinUrlBuilder bip21, PSBT psbt, DerivationSchemeSettings derivationSchemeSettings, BTCPayNetwork btcPayNetwork, CancellationToken cancellationToken) { var cloned = psbt.Clone(); cloned = cloned.Finalize(); await _broadcaster.Schedule(DateTimeOffset.UtcNow + TimeSpan.FromMinutes(2.0), cloned.ExtractTransaction(), btcPayNetwork); return(await _payjoinClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, cancellationToken)); }
public async Task <IActionResult> LedgerConnection( string storeId, string command, // getinfo string cryptoCode = null, // getxpub int account = 0, // 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, 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(); } if (command == "getxpub") { var getxpubResult = await hw.GetExtPubKey(network, account); result = getxpubResult; } if (command == "getinfo") { var strategy = GetDirectDerivationStrategy(store, network); var strategyBase = GetDerivationStrategy(store, network); if (strategy == null || !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") { if (!_Dashboard.IsFullySynched(network.CryptoCode, out var summary)) { throw new Exception($"{network.CryptoCode}: not started or fully synched"); } 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; var transaction = await hw.SendToAddress(strategy, unspentCoins, network, new[] { (destinationAddress as IDestination, amountBTC, subsctractFeesValue) },
public static ISupportedPaymentMethod Deserialize(PaymentMethodId paymentMethodId, JToken value, BTCPayNetwork network) { // Legacy if (paymentMethodId.PaymentType == PaymentTypes.BTCLike) { return(BTCPayServer.DerivationStrategy.Parse(((JValue)value).Value <string>(), network)); } ////////// else if (paymentMethodId.PaymentType == PaymentTypes.LightningLike) { return(JsonConvert.DeserializeObject <Payments.Lightning.LightningSupportedPaymentMethod>(value.ToString())); } throw new NotSupportedException(); }
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 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); 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 psbt = await CreatePSBT(network, derivationScheme, model, normalOperationTimeout.Token); var strategy = GetDirectDerivationStrategy(derivationScheme); 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); } // NBX only know the path relative to the account xpub. // Here we rebase the hd_keys in the PSBT to have a keypath relative to the root HD so the wallet can sign // Note that the fingerprint of the hd keys are now 0, which is wrong // However, hardware wallets does not give a damn, and sometimes does not even allow us to get this fingerprint anyway. foreach (var o in psbt.PSBT.Inputs.OfType <PSBTCoin>().Concat(psbt.PSBT.Outputs)) { foreach (var keypath in o.HDKeyPaths.ToList()) { var newKeyPath = foundKeyPath.Derive(keypath.Value.Item2); o.HDKeyPaths.Remove(keypath.Key); o.HDKeyPaths.Add(keypath.Key, Tuple.Create(default(HDFingerprint), newKeyPath)); } } 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(derivationScheme); 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 override async Task <IPaymentMethodDetails> CreatePaymentMethodDetails(LightningSupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, StoreData store, BTCPayNetwork network) { var storeBlob = store.GetStoreBlob(); var test = Test(supportedPaymentMethod, network); var invoice = paymentMethod.ParentEntity; var due = Extensions.RoundUp(invoice.ProductInformation.Price / paymentMethod.Rate, 8); var client = _LightningClientFactory.CreateClient(supportedPaymentMethod, network); var expiry = invoice.ExpirationTime - DateTimeOffset.UtcNow; if (expiry < TimeSpan.Zero) { expiry = TimeSpan.FromSeconds(1); } LightningInvoice lightningInvoice = null; try { string description = storeBlob.LightningDescriptionTemplate; description = description.Replace("{StoreName}", store.StoreName ?? "", StringComparison.OrdinalIgnoreCase) .Replace("{ItemDescription}", invoice.ProductInformation.ItemDesc ?? "", StringComparison.OrdinalIgnoreCase) .Replace("{OrderId}", invoice.OrderId ?? "", StringComparison.OrdinalIgnoreCase); lightningInvoice = await client.CreateInvoice(new LightMoney(due, LightMoneyUnit.BTC), description, expiry); } catch (Exception ex) { throw new PaymentMethodUnavailableException($"Impossible to create lightning invoice ({ex.Message})", ex); } var nodeInfo = await test; return(new LightningLikePaymentMethodDetails() { BOLT11 = lightningInvoice.BOLT11, InvoiceId = lightningInvoice.Id, NodeInfo = nodeInfo.ToString() }); }
private async Task <PaymentMethod> CreatePaymentMethodAsync(Dictionary <CurrencyPair, Task <RateResult> > fetchingByCurrencyPair, IPaymentMethodHandler handler, ISupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network, InvoiceEntity entity, StoreData store, InvoiceLogs logs) { try { var storeBlob = store.GetStoreBlob(); var preparePayment = handler.PreparePayment(supportedPaymentMethod, store, network); var rate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, entity.ProductInformation.Currency)]; if (rate.BidAsk == null) { return(null); } PaymentMethod paymentMethod = new PaymentMethod(); paymentMethod.ParentEntity = entity; paymentMethod.Network = network; paymentMethod.SetId(supportedPaymentMethod.PaymentId); paymentMethod.Rate = rate.BidAsk.Bid; paymentMethod.PreferOnion = this.Request.IsOnion(); var paymentDetails = await handler.CreatePaymentMethodDetails(supportedPaymentMethod, paymentMethod, store, network, preparePayment); paymentMethod.SetPaymentMethodDetails(paymentDetails); Func <Money, Money, bool> compare = null; CurrencyValue limitValue = null; string errorMessage = null; if (supportedPaymentMethod.PaymentId.PaymentType == PaymentTypes.LightningLike && storeBlob.LightningMaxValue != null) { compare = (a, b) => a > b; limitValue = storeBlob.LightningMaxValue; errorMessage = "The amount of the invoice is too high to be paid with lightning"; } else if (supportedPaymentMethod.PaymentId.PaymentType == PaymentTypes.BTCLike && storeBlob.OnChainMinValue != null) { compare = (a, b) => a < b; limitValue = storeBlob.OnChainMinValue; errorMessage = "The amount of the invoice is too low to be paid on chain"; } if (compare != null) { var limitValueRate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, limitValue.Currency)]; if (limitValueRate.BidAsk != null) { var limitValueCrypto = Money.Coins(limitValue.Value / limitValueRate.BidAsk.Bid); if (compare(paymentMethod.Calculate().Due, limitValueCrypto)) { logs.Write($"{supportedPaymentMethod.PaymentId.CryptoCode}: {errorMessage}"); return(null); } } } /////////////// #pragma warning disable CS0618 if (paymentMethod.GetId().IsBTCOnChain) { entity.TxFee = paymentMethod.NextNetworkFee; entity.Rate = paymentMethod.Rate; entity.DepositAddress = paymentMethod.DepositAddress; } #pragma warning restore CS0618 return(paymentMethod); } catch (PaymentMethodUnavailableException ex) { logs.Write($"{supportedPaymentMethod.PaymentId.CryptoCode}: Payment method unavailable ({ex.Message})"); } catch (Exception ex) { logs.Write($"{supportedPaymentMethod.PaymentId.CryptoCode}: Unexpected exception ({ex.ToString()})"); } return(null); }
private string ValueToString(Money v, BTCPayNetwork network) { return(v.ToString() + " " + network.CryptoCode); }
private async Task <PSBT> UpdatePSBT(DerivationSchemeSettings derivationSchemeSettings, PSBT psbt, BTCPayNetwork network) { var result = await ExplorerClientProvider.GetExplorerClient(network).UpdatePSBTAsync(new UpdatePSBTRequest() { PSBT = psbt, DerivationScheme = derivationSchemeSettings.AccountDerivation, }); if (result == null) { return(null); } derivationSchemeSettings.RebaseKeyPaths(result.PSBT); return(result.PSBT); }
private string GetDisplayName(PaymentMethodId paymentMethodId, BTCPayNetwork network) { return(paymentMethodId.PaymentType == PaymentTypes.BTCLike ? network.DisplayName : network.DisplayName + " (Lightning)"); }
private string GetImage(PaymentMethodId paymentMethodId, BTCPayNetwork network) { return(paymentMethodId.PaymentType == PaymentTypes.BTCLike ? this.Request.GetRelativePathOrAbsolute(network.CryptoImagePath) : this.Request.GetRelativePathOrAbsolute(network.LightningImagePath)); }
private async Task <PaymentMethod> CreatePaymentMethodAsync(Dictionary <CurrencyPair, Task <RateResult> > fetchingByCurrencyPair, IPaymentMethodHandler handler, ISupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network, InvoiceEntity entity, StoreData store, InvoiceLogs logs) { try { var logPrefix = $"{supportedPaymentMethod.PaymentId.ToString(true)}:"; var storeBlob = store.GetStoreBlob(); var preparePayment = handler.PreparePayment(supportedPaymentMethod, store, network); var rate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, entity.ProductInformation.Currency)]; if (rate.BidAsk == null) { return(null); } PaymentMethod paymentMethod = new PaymentMethod(); paymentMethod.ParentEntity = entity; paymentMethod.Network = network; paymentMethod.SetId(supportedPaymentMethod.PaymentId); paymentMethod.Rate = rate.BidAsk.Bid; paymentMethod.PreferOnion = this.Request.IsOnion(); using (logs.Measure($"{logPrefix} Formas de pago detalles creación")) { var paymentDetails = await handler.CreatePaymentMethodDetails(supportedPaymentMethod, paymentMethod, store, network, preparePayment); paymentMethod.SetPaymentMethodDetails(paymentDetails); } Func <Money, Money, bool> compare = null; CurrencyValue limitValue = null; string errorMessage = null; if (supportedPaymentMethod.PaymentId.PaymentType == PaymentTypes.LightningLike && storeBlob.LightningMaxValue != null) { compare = (a, b) => a > b; limitValue = storeBlob.LightningMaxValue; errorMessage = "El importe de la factura es demasiado alto para ser pagado con lightning"; } else if (supportedPaymentMethod.PaymentId.PaymentType == PaymentTypes.BTCLike && storeBlob.OnChainMinValue != null) { compare = (a, b) => a < b; limitValue = storeBlob.OnChainMinValue; errorMessage = "El importe de la factura es demasiado bajo para ser pagado en la cadena"; } if (compare != null) { var limitValueRate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, limitValue.Currency)]; if (limitValueRate.BidAsk != null) { var limitValueCrypto = Money.Coins(limitValue.Value / limitValueRate.BidAsk.Bid); if (compare(paymentMethod.Calculate().Due, limitValueCrypto)) { logs.Write($"{logPrefix} {errorMessage}"); return(null); } } } /////////////// #pragma warning disable CS0618 if (paymentMethod.GetId().IsBTCOnChain) { entity.TxFee = paymentMethod.NextNetworkFee; entity.Rate = paymentMethod.Rate; entity.DepositAddress = paymentMethod.DepositAddress; } #pragma warning restore CS0618 return(paymentMethod); } catch (PaymentMethodUnavailableException ex) { logs.Write($"{supportedPaymentMethod.PaymentId.CryptoCode}: Método de pago no disponible ({ex.Message})"); } catch (Exception ex) { logs.Write($"{supportedPaymentMethod.PaymentId.CryptoCode}: Excepción inesperada ({ex.ToString()})"); } return(null); }
public bool PaymentConfirmed(PaymentEntity entity, SpeedPolicy speedPolicy, BTCPayNetwork network) { return(true); }
public bool PaymentCompleted(PaymentEntity entity, BTCPayNetwork network) { return(true); }
public async Task <NodeInfo> GetNodeInfo(bool preferOnion, LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network) { if (!_Dashboard.IsFullySynched(network.CryptoCode, out var summary)) { throw new PaymentMethodUnavailableException($"Full node not available"); } using (var cts = new CancellationTokenSource(LIGHTNING_TIMEOUT)) { var client = _lightningClientFactory.Create(supportedPaymentMethod.GetLightningUrl(), network); LightningNodeInformation info = null; try { info = await client.GetInfo(cts.Token); } catch (OperationCanceledException) when(cts.IsCancellationRequested) { throw new PaymentMethodUnavailableException($"The lightning node did not reply in a timely manner"); } catch (Exception ex) { throw new PaymentMethodUnavailableException($"Error while connecting to the API ({ex.Message})"); } var nodeInfo = info.NodeInfoList.FirstOrDefault(i => i.IsTor == preferOnion) ?? info.NodeInfoList.FirstOrDefault(); if (nodeInfo == null) { throw new PaymentMethodUnavailableException($"No lightning node public address has been configured"); } var blocksGap = summary.Status.ChainHeight - info.BlockHeight; if (blocksGap > 10) { throw new PaymentMethodUnavailableException($"The lightning node is not synched ({blocksGap} blocks left)"); } return(nodeInfo); } }
public async Task <bool> NewAddress(string invoiceId, Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod paymentMethod, BTCPayNetwork network) { using (var context = _ContextFactory.CreateContext()) { var invoice = await context.Invoices.FirstOrDefaultAsync(i => i.Id == invoiceId); if (invoice == null) { return(false); } var invoiceEntity = ToObject <InvoiceEntity>(invoice.Blob, network.NBitcoinNetwork); var currencyData = invoiceEntity.GetPaymentMethod(network, paymentMethod.GetPaymentType(), null); if (currencyData == null) { return(false); } var existingPaymentMethod = (Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod)currencyData.GetPaymentMethodDetails(); if (existingPaymentMethod.GetPaymentDestination() != null) { MarkUnassigned(invoiceId, invoiceEntity, context, currencyData.GetId()); } existingPaymentMethod.SetPaymentDestination(paymentMethod.GetPaymentDestination()); currencyData.SetPaymentMethodDetails(existingPaymentMethod); #pragma warning disable CS0618 if (network.IsBTC) { invoiceEntity.DepositAddress = currencyData.DepositAddress; } #pragma warning restore CS0618 invoiceEntity.SetPaymentMethod(currencyData); invoice.Blob = ToBytes(invoiceEntity, network.NBitcoinNetwork); context.AddressInvoices.Add(new AddressInvoiceData() { InvoiceDataId = invoiceId, CreatedTime = DateTimeOffset.UtcNow } .Set(GetDestination(currencyData, network.NBitcoinNetwork), currencyData.GetId())); context.HistoricalAddressInvoices.Add(new HistoricalAddressInvoiceData() { InvoiceDataId = invoiceId, Assigned = DateTimeOffset.UtcNow }.SetAddress(paymentMethod.GetPaymentDestination(), network.CryptoCode)); await context.SaveChangesAsync(); AddToTextSearch(invoice.Id, paymentMethod.GetPaymentDestination()); return(true); } }
private DerivationStrategyBase ParseDerivationStrategy(string derivationScheme, string format, BTCPayNetwork network) { if (format == "Electrum") { //Unsupported Electrum //var p2wsh_p2sh = 0x295b43fU; //var p2wsh = 0x2aa7ed3U; Dictionary <uint, string[]> electrumMapping = new Dictionary <uint, string[]>(); //Source https://github.com/spesmilo/electrum/blob/9edffd17542de5773e7284a8c8a2673c766bb3c3/lib/bitcoin.py var standard = 0x0488b21eU; electrumMapping.Add(standard, new[] { "legacy" }); var p2wpkh_p2sh = 0x049d7cb2U; electrumMapping.Add(p2wpkh_p2sh, new string[] { "p2sh" }); var p2wpkh = 0x4b24746U; electrumMapping.Add(p2wpkh, new string[] { }); var data = Encoders.Base58Check.DecodeData(derivationScheme); if (data.Length < 4) { throw new FormatException("data.Length < 4"); } var prefix = Utils.ToUInt32(data, false); if (!electrumMapping.TryGetValue(prefix, out string[] labels))
/// <summary> /// Add a payment to an invoice /// </summary> /// <param name="invoiceId"></param> /// <param name="date"></param> /// <param name="paymentData"></param> /// <param name="cryptoCode"></param> /// <param name="accounted"></param> /// <returns>The PaymentEntity or null if already added</returns> public async Task <PaymentEntity> AddPayment(string invoiceId, DateTimeOffset date, CryptoPaymentData paymentData, BTCPayNetwork network, bool accounted = false) { using (var context = _ContextFactory.CreateContext()) { var invoice = context.Invoices.Find(invoiceId); if (invoice == null) { return(null); } InvoiceEntity invoiceEntity = ToObject <InvoiceEntity>(invoice.Blob, network.NBitcoinNetwork); PaymentMethod paymentMethod = invoiceEntity.GetPaymentMethod(new PaymentMethodId(network.CryptoCode, paymentData.GetPaymentType()), null); IPaymentMethodDetails paymentMethodDetails = paymentMethod.GetPaymentMethodDetails(); PaymentEntity entity = new PaymentEntity { Version = 1, #pragma warning disable CS0618 CryptoCode = network.CryptoCode, #pragma warning restore CS0618 ReceivedTime = date.UtcDateTime, Accounted = accounted, NetworkFee = paymentMethodDetails.GetNextNetworkFee() }; entity.SetCryptoPaymentData(paymentData); if (paymentMethodDetails is Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod bitcoinPaymentMethod && bitcoinPaymentMethod.NetworkFeeMode == NetworkFeeMode.MultiplePaymentsOnly && bitcoinPaymentMethod.NextNetworkFee == Money.Zero) { bitcoinPaymentMethod.NextNetworkFee = bitcoinPaymentMethod.FeeRate.GetFee(100); // assume price for 100 bytes paymentMethod.SetPaymentMethodDetails(bitcoinPaymentMethod); invoiceEntity.SetPaymentMethod(paymentMethod); invoice.Blob = ToBytes(invoiceEntity, network.NBitcoinNetwork); } PaymentData data = new PaymentData { Id = paymentData.GetPaymentId(), Blob = ToBytes(entity, null), InvoiceDataId = invoiceId, Accounted = accounted }; context.Payments.Add(data); try { await context.SaveChangesAsync().ConfigureAwait(false); } catch (DbUpdateException) { return(null); } // Already exists AddToTextSearch(invoiceId, paymentData.GetSearchTerms()); return(entity); } }
public async Task <NodeInfo> Test(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network) { if (!_Dashboard.IsFullySynched(network.CryptoCode, out var summary)) { throw new PaymentMethodUnavailableException($"Full node not available"); } var cts = new CancellationTokenSource(5000); var client = _LightningClientFactory.CreateClient(supportedPaymentMethod, network); LightningNodeInformation info = null; try { info = await client.GetInfo(cts.Token); } catch (OperationCanceledException) when(cts.IsCancellationRequested) { throw new PaymentMethodUnavailableException($"The lightning node did not replied in a timely maner"); } catch (Exception ex) { throw new PaymentMethodUnavailableException($"Error while connecting to the API ({ex.Message})"); } if (info.Address == null) { throw new PaymentMethodUnavailableException($"No lightning node public address has been configured"); } var blocksGap = Math.Abs(info.BlockHeight - summary.Status.ChainHeight); if (blocksGap > 10) { throw new PaymentMethodUnavailableException($"The lightning is not synched ({blocksGap} blocks)"); } return(new NodeInfo(info.NodeId, info.Address, info.P2PPort)); }
public decimal Apply(BTCPayNetwork network, decimal rate) { return(rate * (decimal)Multiplier); }
private string GetImage(PaymentMethodId paymentMethodId, BTCPayNetwork network) { return(paymentMethodId.PaymentType == PaymentTypes.BTCLike ? Url.Content(network.CryptoImagePath) : Url.Content(network.LightningImagePath)); }
private bool GetNetwork(string cryptoCode, out BTCPayNetwork network) { network = _btcPayNetworkProvider.GetNetwork <BTCPayNetwork>(cryptoCode); network = network?.SupportLightning is true ? network : null; return(network != null); }
public async Task <IActionResult> CreatePullPayment(string storeId, CreatePullPaymentRequest request) { if (request is null) { ModelState.AddModelError(string.Empty, "Missing body"); return(this.CreateValidationError(ModelState)); } if (request.Amount <= 0.0m) { ModelState.AddModelError(nameof(request.Amount), "The amount should more than 0."); } if (request.Name is String name && name.Length > 50) { ModelState.AddModelError(nameof(request.Name), "The name should be maximum 50 characters."); } BTCPayNetwork network = null; if (request.Currency is String currency) { network = _networkProvider.GetNetwork <BTCPayNetwork>(currency); if (network is null) { ModelState.AddModelError(nameof(request.Currency), $"Only crypto currencies are supported this field. (More will be supported soon)"); } } else { ModelState.AddModelError(nameof(request.Currency), "This field is required"); } if (request.ExpiresAt is DateTimeOffset expires && request.StartsAt is DateTimeOffset start && expires < start) { ModelState.AddModelError(nameof(request.ExpiresAt), $"expiresAt should be higher than startAt"); } if (request.Period <= TimeSpan.Zero) { ModelState.AddModelError(nameof(request.Period), $"The period should be positive"); } if (request.PaymentMethods is string[] paymentMethods) { if (paymentMethods.Length != 1 && paymentMethods[0] != request.Currency) { ModelState.AddModelError(nameof(request.PaymentMethods), "We expect this array to only contains the same element as the `currency` field. (More will be supported soon)"); } } else { ModelState.AddModelError(nameof(request.PaymentMethods), "This field is required"); } if (!ModelState.IsValid) { return(this.CreateValidationError(ModelState)); } var ppId = await _pullPaymentService.CreatePullPayment(new HostedServices.CreatePullPayment() { StartsAt = request.StartsAt, ExpiresAt = request.ExpiresAt, Period = request.Period, Name = request.Name, Amount = request.Amount, Currency = network.CryptoCode, StoreId = storeId, PaymentMethodIds = new[] { new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike) } }); var pp = await _pullPaymentService.GetPullPayment(ppId); return(this.Ok(CreatePullPaymentData(pp))); }
public ILightningClient CreateClient(BTCPayNetwork network) { return(LightningClientFactory.CreateClient(this.GetLightningUrl(), network.NBitcoinNetwork)); }
private DerivationStrategy ParseDerivationStrategy(string derivationScheme, Script hint, BTCPayNetwork network) { var parser = new DerivationSchemeParser(network.NBitcoinNetwork, network.DefaultSettings.ChainType); parser.HintScriptPubKey = hint; return(new DerivationStrategy(parser.Parse(derivationScheme), network)); }
public IFeeProvider CreateFeeProvider(BTCPayNetwork network) { return(new NBXplorerFeeProvider(this, _ExplorerClients.GetExplorerClient(network))); }
private async Task FetchTransactionDetails(DerivationSchemeSettings derivationSchemeSettings, WalletPSBTReadyViewModel vm, BTCPayNetwork network) { var psbtObject = PSBT.Parse(vm.SigningContext.PSBT, network.NBitcoinNetwork); if (!psbtObject.IsAllFinalized()) { psbtObject = await ExplorerClientProvider.UpdatePSBT(derivationSchemeSettings, psbtObject) ?? psbtObject; } IHDKey signingKey = null; RootedKeyPath signingKeyPath = null; try { signingKey = new BitcoinExtPubKey(vm.SigningKey, network.NBitcoinNetwork); } catch { } try { signingKey = signingKey ?? new BitcoinExtKey(vm.SigningKey, network.NBitcoinNetwork); } catch { } try { signingKeyPath = RootedKeyPath.Parse(vm.SigningKeyPath); } catch { } if (signingKey == null || signingKeyPath == null) { var signingKeySettings = derivationSchemeSettings.GetSigningAccountKeySettings(); if (signingKey == null) { signingKey = signingKeySettings.AccountKey; vm.SigningKey = signingKey.ToString(); } if (vm.SigningKeyPath == null) { signingKeyPath = signingKeySettings.GetRootedKeyPath(); vm.SigningKeyPath = signingKeyPath?.ToString(); } } if (psbtObject.IsAllFinalized()) { vm.CanCalculateBalance = false; } else { var balanceChange = psbtObject.GetBalance(derivationSchemeSettings.AccountDerivation, signingKey, signingKeyPath); vm.BalanceChange = ValueToString(balanceChange, network); vm.CanCalculateBalance = true; vm.Positive = balanceChange >= Money.Zero; } vm.Inputs = new List <WalletPSBTReadyViewModel.InputViewModel>(); foreach (var input in psbtObject.Inputs) { var inputVm = new WalletPSBTReadyViewModel.InputViewModel(); vm.Inputs.Add(inputVm); var mine = input.HDKeysFor(derivationSchemeSettings.AccountDerivation, signingKey, signingKeyPath).Any(); var balanceChange2 = input.GetTxOut()?.Value ?? Money.Zero; if (mine) { balanceChange2 = -balanceChange2; } inputVm.BalanceChange = ValueToString(balanceChange2, network); inputVm.Positive = balanceChange2 >= Money.Zero; inputVm.Index = (int)input.Index; } vm.Destinations = new List <WalletPSBTReadyViewModel.DestinationViewModel>(); foreach (var output in psbtObject.Outputs) { var dest = new WalletPSBTReadyViewModel.DestinationViewModel(); vm.Destinations.Add(dest); var mine = output.HDKeysFor(derivationSchemeSettings.AccountDerivation, signingKey, signingKeyPath).Any(); var balanceChange2 = output.Value; if (!mine) { balanceChange2 = -balanceChange2; } dest.Balance = ValueToString(balanceChange2, network); dest.Positive = balanceChange2 >= Money.Zero; dest.Destination = output.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork)?.ToString() ?? output.ScriptPubKey.ToString(); } if (psbtObject.TryGetFee(out var fee)) { vm.Destinations.Add(new WalletPSBTReadyViewModel.DestinationViewModel() { Positive = false, Balance = ValueToString(-fee, network), Destination = "Mining fees" }); } if (psbtObject.TryGetEstimatedFeeRate(out var feeRate)) { vm.FeeRate = feeRate.ToString(); } var sanityErrors = psbtObject.CheckSanity(); if (sanityErrors.Count != 0) { vm.SetErrors(sanityErrors); } else if (!psbtObject.IsAllFinalized() && !psbtObject.TryFinalize(out var errors)) { vm.SetErrors(errors); } }
public override async Task <IPaymentMethodDetails> CreatePaymentMethodDetails(LightningSupportedPaymentMethod supportedPaymentMethod, PaymentMethod paymentMethod, StoreData store, BTCPayNetwork network, object preparePaymentObject) { var storeBlob = store.GetStoreBlob(); var test = GetNodeInfo(paymentMethod.PreferOnion, supportedPaymentMethod, network); var invoice = paymentMethod.ParentEntity; var due = Extensions.RoundUp(invoice.ProductInformation.Price / paymentMethod.Rate, 8); var client = _lightningClientFactory.Create(supportedPaymentMethod.GetLightningUrl(), network); var expiry = invoice.ExpirationTime - DateTimeOffset.UtcNow; if (expiry < TimeSpan.Zero) { expiry = TimeSpan.FromSeconds(1); } LightningInvoice lightningInvoice = null; string description = storeBlob.LightningDescriptionTemplate; description = description.Replace("{StoreName}", store.StoreName ?? "", StringComparison.OrdinalIgnoreCase) .Replace("{ItemDescription}", invoice.ProductInformation.ItemDesc ?? "", StringComparison.OrdinalIgnoreCase) .Replace("{OrderId}", invoice.OrderId ?? "", StringComparison.OrdinalIgnoreCase); using (var cts = new CancellationTokenSource(LIGHTNING_TIMEOUT)) { try { lightningInvoice = await client.CreateInvoice(new LightMoney(due, LightMoneyUnit.BTC), description, expiry, cts.Token); } catch (OperationCanceledException) when(cts.IsCancellationRequested) { throw new PaymentMethodUnavailableException($"The lightning node did not reply in a timely maner"); } catch (Exception ex) { throw new PaymentMethodUnavailableException($"Impossible to create lightning invoice ({ex.Message})", ex); } } var nodeInfo = await test; return(new LightningLikePaymentMethodDetails() { BOLT11 = lightningInvoice.BOLT11, InvoiceId = lightningInvoice.Id, NodeInfo = nodeInfo.ToString() }); }
private async Task <PSBT> GetPayjoinProposedTX(string bpu, PSBT psbt, DerivationSchemeSettings derivationSchemeSettings, BTCPayNetwork btcPayNetwork, CancellationToken cancellationToken) { if (string.IsNullOrEmpty(bpu) || !Uri.TryCreate(bpu, UriKind.Absolute, out var endpoint)) { throw new InvalidOperationException("No payjoin url available"); } var cloned = psbt.Clone(); cloned = cloned.Finalize(); await _broadcaster.Schedule(DateTimeOffset.UtcNow + TimeSpan.FromMinutes(2.0), cloned.ExtractTransaction(), btcPayNetwork); return(await _payjoinClient.RequestPayjoin(endpoint, derivationSchemeSettings, psbt, cancellationToken)); }
public override async Task <IPaymentMethodDetails> CreatePaymentMethodDetails(DerivationStrategy supportedPaymentMethod, PaymentMethod paymentMethod, StoreData store, BTCPayNetwork network, object preparePaymentObject) { if (!_ExplorerProvider.IsAvailable(network)) { throw new PaymentMethodUnavailableException($"Full node not available"); } var prepare = (Prepare)preparePaymentObject; Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod onchainMethod = new Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod(); onchainMethod.NetworkFeeMode = store.GetStoreBlob().NetworkFeeMode; onchainMethod.FeeRate = await prepare.GetFeeRate; switch (onchainMethod.NetworkFeeMode) { case NetworkFeeMode.Always: onchainMethod.NextNetworkFee = onchainMethod.FeeRate.GetFee(100); // assume price for 100 bytes break; case NetworkFeeMode.Never: case NetworkFeeMode.MultiplePaymentsOnly: onchainMethod.NextNetworkFee = Money.Zero; break; } onchainMethod.DepositAddress = (await prepare.ReserveAddress).ToString(); return(onchainMethod); }