コード例 #1
0
 public string GetDestination(BTCPayNetwork network)
 {
     return(GetPaymentId());
 }
コード例 #2
0
        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));
        }
コード例 #3
0
        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) },
コード例 #4
0
 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();
 }
コード例 #5
0
        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());
        }
コード例 #6
0
        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()
            });
        }
コード例 #7
0
        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);
        }
コード例 #8
0
 private string ValueToString(Money v, BTCPayNetwork network)
 {
     return(v.ToString() + " " + network.CryptoCode);
 }
コード例 #9
0
        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);
        }
コード例 #10
0
 private string GetDisplayName(PaymentMethodId paymentMethodId, BTCPayNetwork network)
 {
     return(paymentMethodId.PaymentType == PaymentTypes.BTCLike ?
            network.DisplayName : network.DisplayName + " (Lightning)");
 }
コード例 #11
0
 private string GetImage(PaymentMethodId paymentMethodId, BTCPayNetwork network)
 {
     return(paymentMethodId.PaymentType == PaymentTypes.BTCLike ?
            this.Request.GetRelativePathOrAbsolute(network.CryptoImagePath) : this.Request.GetRelativePathOrAbsolute(network.LightningImagePath));
 }
コード例 #12
0
        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);
        }
コード例 #13
0
 public bool PaymentConfirmed(PaymentEntity entity, SpeedPolicy speedPolicy, BTCPayNetwork network)
 {
     return(true);
 }
コード例 #14
0
 public bool PaymentCompleted(PaymentEntity entity, BTCPayNetwork network)
 {
     return(true);
 }
コード例 #15
0
        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);
            }
        }
コード例 #16
0
        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);
            }
        }
コード例 #17
0
        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))
コード例 #18
0
        /// <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);
            }
        }
コード例 #19
0
        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));
        }
コード例 #20
0
ファイル: StoreData.cs プロジェクト: self20/btcpayserver
 public decimal Apply(BTCPayNetwork network, decimal rate)
 {
     return(rate * (decimal)Multiplier);
 }
コード例 #21
0
 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);
 }
コード例 #23
0
        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)));
        }
コード例 #24
0
 public ILightningClient CreateClient(BTCPayNetwork network)
 {
     return(LightningClientFactory.CreateClient(this.GetLightningUrl(), network.NBitcoinNetwork));
 }
コード例 #25
0
        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));
        }
コード例 #26
0
 public IFeeProvider CreateFeeProvider(BTCPayNetwork network)
 {
     return(new NBXplorerFeeProvider(this, _ExplorerClients.GetExplorerClient(network)));
 }
コード例 #27
0
        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);
            }
        }
コード例 #28
0
        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()
            });
        }
コード例 #29
0
        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));
        }
コード例 #30
0
        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);
        }