public override async Task <IPaymentMethodDetails> CreatePaymentMethodDetails(
            DerivationSchemeSettings 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);
        }
Beispiel #2
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.FeeRate        = await prepare.GetFeeRate;
            onchainMethod.TxFee          = onchainMethod.FeeRate.GetFee(100); // assume price for 100 bytes
            onchainMethod.DepositAddress = (await prepare.ReserveAddress).ToString();
            return(onchainMethod);
        }
Beispiel #3
0
        public override async Task <IPaymentMethodDetails> CreatePaymentMethodDetails(DerivationStrategy supportedPaymentMethod, PaymentMethod paymentMethod, BTCPayNetwork network)
        {
            if (!_ExplorerProvider.IsAvailable(network))
            {
                throw new PaymentMethodUnavailableException($"Full node not available");
            }
            var getFeeRate = _FeeRateProviderFactory.CreateFeeProvider(network).GetFeeRateAsync();
            var getAddress = _WalletProvider.GetWallet(network).ReserveAddressAsync(supportedPaymentMethod.DerivationStrategyBase);

            Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod onchainMethod = new Payments.Bitcoin.BitcoinLikeOnChainPaymentMethod();
            onchainMethod.FeeRate        = await getFeeRate;
            onchainMethod.TxFee          = onchainMethod.FeeRate.GetFee(100); // assume price for 100 bytes
            onchainMethod.DepositAddress = (await getAddress).ToString();
            return(onchainMethod);
        }
Beispiel #4
0
        public async Task Test(LightningSupportedPaymentMethod supportedPaymentMethod, BTCPayNetwork network)
        {
            if (!_ExplorerClientProvider.IsAvailable(network))
            {
                throw new Exception($"Full node not available");
            }

            var             explorerClient = _ExplorerClientProvider.GetExplorerClient(network);
            var             cts            = new CancellationTokenSource(5000);
            var             client         = GetClient(supportedPaymentMethod, network);
            var             status         = explorerClient.GetStatusAsync();
            GetInfoResponse info           = null;

            try
            {
                info = await client.GetInfoAsync(cts.Token);
            }
            catch (Exception ex)
            {
                throw new Exception($"Error while connecting to the lightning charge {client.Uri} ({ex.Message})");
            }
            var address = info.Address.Select(a => a.Address).FirstOrDefault();
            var port    = info.Port;

            address = address ?? client.Uri.DnsSafeHost;

            if (info.Network != network.CLightningNetworkName)
            {
                throw new Exception($"Lightning node network {info.Network}, but expected is {network.CLightningNetworkName}");
            }

            var blocksGap = Math.Abs(info.BlockHeight - (await status).ChainHeight);

            if (blocksGap > 10)
            {
                throw new Exception($"The lightning is not synched ({blocksGap} blocks)");
            }

            try
            {
                await TestConnection(address, port, cts.Token);
            }
            catch (Exception ex)
            {
                throw new Exception($"Error while connecting to the lightning node via {address}:{port} ({ex.Message})");
            }
        }
Beispiel #5
0
        private async Task <NamedWallet[]> GetNamedWallets(string fromCrypto)
        {
            var stores = await Repository.GetStoresByUserId(GetUserId());

            return(stores
                   .SelectMany(s => s.GetSupportedPaymentMethods(NetworkProvider)
                               .OfType <DerivationSchemeSettings>()
                               .Where(p => p.Network.BlockTime != null)
                               .Where(p => p != null && ExplorerClientProvider.IsAvailable(p.Network))
                               .Select(p => new NamedWallet()
            {
                Name = $"{p.PaymentId.CryptoCode}: {s.StoreName}",
                DerivationStrategy = p,
                CryptoCode = p.PaymentId.CryptoCode,
                WalletId = new WalletId(s.Id, p.PaymentId.CryptoCode),
                Rule = GetRuleNoSpread(s.GetStoreBlob(), fromCrypto, p.PaymentId.CryptoCode),
                Spread = s.GetStoreBlob().Spread
            }))
                   .ToArray());
        }
Beispiel #6
0
        protected override async Task Process(ISupportedPaymentMethod paymentMethod, PayoutData[] payouts)
        {
            var storePaymentMethod = paymentMethod as DerivationSchemeSettings;

            if (storePaymentMethod?.IsHotWallet is not true)
            {
                Logs.PayServer.LogInformation($"Wallet is not a hot wallet.");
                return;
            }

            if (!_explorerClientProvider.IsAvailable(PaymentMethodId.CryptoCode))
            {
                Logs.PayServer.LogInformation($"{paymentMethod.PaymentId.CryptoCode} node is not available");
                return;
            }
            var explorerClient  = _explorerClientProvider.GetExplorerClient(PaymentMethodId.CryptoCode);
            var paymentMethodId = PaymentMethodId.Parse(PaymentMethodId.CryptoCode);
            var network         = _btcPayNetworkProvider.GetNetwork <BTCPayNetwork>(paymentMethodId.CryptoCode);

            var extKeyStr = await explorerClient.GetMetadataAsync <string>(
                storePaymentMethod.AccountDerivation,
                WellknownMetadataKeys.AccountHDKey);

            if (extKeyStr == null)
            {
                Logs.PayServer.LogInformation($"Wallet keys not found.");
                return;
            }

            var wallet = _btcPayWalletProvider.GetWallet(PaymentMethodId.CryptoCode);

            var reccoins = (await wallet.GetUnspentCoins(storePaymentMethod.AccountDerivation)).ToArray();
            var coins    = reccoins.Select(coin => coin.Coin).ToArray();

            var         accountKey    = ExtKey.Parse(extKeyStr, network.NBitcoinNetwork);
            var         keys          = reccoins.Select(coin => accountKey.Derive(coin.KeyPath).PrivateKey).ToArray();
            Transaction workingTx     = null;
            decimal?    failedAmount  = null;
            var         changeAddress = await explorerClient.GetUnusedAsync(
                storePaymentMethod.AccountDerivation, DerivationFeature.Change, 0, true);

            var feeRate = await explorerClient.GetFeeRateAsync(1, new FeeRate(1m));

            var transfersProcessing = new List <PayoutData>();

            foreach (var transferRequest in payouts)
            {
                var blob = transferRequest.GetBlob(_btcPayNetworkJsonSerializerSettings);
                if (failedAmount.HasValue && blob.CryptoAmount >= failedAmount)
                {
                    continue;
                }

                var claimDestination =
                    await _bitcoinLikePayoutHandler.ParseClaimDestination(paymentMethodId, blob.Destination);

                if (!string.IsNullOrEmpty(claimDestination.error))
                {
                    Logs.PayServer.LogInformation($"Could not process payout {transferRequest.Id} because {claimDestination.error}.");
                    continue;
                }

                var bitcoinClaimDestination = (IBitcoinLikeClaimDestination)claimDestination.destination;
                var txBuilder = network.NBitcoinNetwork.CreateTransactionBuilder()
                                .AddCoins(coins)
                                .AddKeys(keys);

                if (workingTx is not null)
                {
                    foreach (var txout in workingTx.Outputs.Where(txout =>
                                                                  !txout.IsTo(changeAddress.Address)))
                    {
                        txBuilder.Send(txout.ScriptPubKey, txout.Value);
                    }
                }

                txBuilder.Send(bitcoinClaimDestination.Address,
                               new Money(blob.CryptoAmount.Value, MoneyUnit.BTC));

                try
                {
                    txBuilder.SetChange(changeAddress.Address);
                    txBuilder.SendEstimatedFees(feeRate.FeeRate);
                    workingTx = txBuilder.BuildTransaction(true);
                    transfersProcessing.Add(transferRequest);
                }
                catch (NotEnoughFundsException e)
                {
                    Logs.PayServer.LogInformation($"Could not process payout {transferRequest.Id} because of not enough funds. ({e.Missing.GetValue(network)})");
                    failedAmount = blob.CryptoAmount;
                    //keep going, we prioritize withdraws by time but if there is some other we can fit, we should
                }
            }

            if (workingTx is not null)
            {
                try
                {
                    await using var context = _applicationDbContextFactory.CreateContext();
                    var txHash = workingTx.GetHash();
                    Logs.PayServer.LogInformation($"Processing {transfersProcessing.Count} payouts in tx {txHash}");
                    foreach (PayoutData payoutData in transfersProcessing)
                    {
                        context.Attach(payoutData);
                        payoutData.State = PayoutState.InProgress;
                        _bitcoinLikePayoutHandler.SetProofBlob(payoutData,
                                                               new PayoutTransactionOnChainBlob()
                        {
                            Accounted     = true,
                            TransactionId = txHash,
                            Candidates    = new HashSet <uint256>()
                            {
                                txHash
                            }
                        });
                        await context.SaveChangesAsync();
                    }
                    TaskCompletionSource <bool> tcs = new();
                    var cts = new CancellationTokenSource();
                    cts.CancelAfter(TimeSpan.FromSeconds(20));
                    var task = _eventAggregator.WaitNext <NewOnChainTransactionEvent>(
                        e => e.NewTransactionEvent.TransactionData.TransactionHash == txHash,
                        cts.Token);
                    var broadcastResult = await explorerClient.BroadcastAsync(workingTx, cts.Token);

                    if (!broadcastResult.Success)
                    {
                        tcs.SetResult(false);
                    }
                    var walletId = new WalletId(_PayoutProcesserSettings.StoreId, PaymentMethodId.CryptoCode);
                    foreach (PayoutData payoutData in transfersProcessing)
                    {
                        _eventAggregator.Publish(new UpdateTransactionLabel(walletId,
                                                                            txHash,
                                                                            UpdateTransactionLabel.PayoutTemplate(payoutData.Id, payoutData.PullPaymentDataId,
                                                                                                                  walletId.ToString())));
                    }
                    await Task.WhenAny(tcs.Task, task);
                }
                catch (OperationCanceledException)
                {
                }
                catch (Exception e)
                {
                    Logs.PayServer.LogError(e, "Could not finalize and broadcast");
                }
            }
        }
Beispiel #7
0
 public bool IsAvailable(BTCPayNetwork network)
 {
     return(_Client.IsAvailable(network));
 }
        public override async Task <IPaymentMethodDetails> CreatePaymentMethodDetails(
            InvoiceLogs logs,
            DerivationSchemeSettings 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();
            var blob = store.GetStoreBlob();

            onchainMethod.NetworkFeeMode = blob.NetworkFeeMode;
            onchainMethod.FeeRate        = await prepare.GetFeeRate;
            switch (onchainMethod.NetworkFeeMode)
            {
            case NetworkFeeMode.Always:
                onchainMethod.NetworkFeeRate = (await prepare.GetNetworkFeeRate);
                onchainMethod.NextNetworkFee =
                    onchainMethod.NetworkFeeRate.GetFee(100);     // assume price for 100 bytes
                break;

            case NetworkFeeMode.Never:
                onchainMethod.NetworkFeeRate = FeeRate.Zero;
                onchainMethod.NextNetworkFee = Money.Zero;
                break;

            case NetworkFeeMode.MultiplePaymentsOnly:
                onchainMethod.NetworkFeeRate = (await prepare.GetNetworkFeeRate);
                onchainMethod.NextNetworkFee = Money.Zero;
                break;
            }

            onchainMethod.DepositAddress = (await prepare.ReserveAddress).Address.ToString();
            onchainMethod.PayjoinEnabled = blob.PayJoinEnabled &&
                                           PayjoinClient.SupportedFormats.Contains(supportedPaymentMethod
                                                                                   .AccountDerivation.ScriptPubKeyType()) &&
                                           network.SupportPayJoin;
            if (onchainMethod.PayjoinEnabled)
            {
                var prefix      = $"{supportedPaymentMethod.PaymentId.ToPrettyString()}:";
                var nodeSupport = _dashboard?.Get(network.CryptoCode)?.Status?.BitcoinStatus?.Capabilities
                                  ?.CanSupportTransactionCheck is true;
                onchainMethod.PayjoinEnabled &= supportedPaymentMethod.IsHotWallet && nodeSupport;
                if (!isHotwallet)
                {
                    logs.Write($"{prefix} Payjoin should have been enabled, but your store is not a hotwallet");
                }
                if (!nodeSupport)
                {
                    logs.Write($"{prefix} Payjoin should have been enabled, but your version of NBXplorer or full node does not support it.");
                }
                if (onchainMethod.PayjoinEnabled)
                {
                    logs.Write($"{prefix} Payjoin is enabled for this invoice.");
                }
            }

            return(onchainMethod);
        }
Beispiel #9
0
        public override async Task <IPaymentMethodDetails> CreatePaymentMethodDetails(
            InvoiceLogs logs,
            DerivationSchemeSettings supportedPaymentMethod, PaymentMethod paymentMethod, StoreData store,
            BTCPayNetwork network, object preparePaymentObject)
        {
            if (preparePaymentObject is null)
            {
                return(new BitcoinLikeOnChainPaymentMethod()
                {
                    Activated = false
                });
            }
            if (!_ExplorerProvider.IsAvailable(network))
            {
                throw new PaymentMethodUnavailableException($"Full node not available");
            }
            var prepare       = (Prepare)preparePaymentObject;
            var onchainMethod = new BitcoinLikeOnChainPaymentMethod();
            var blob          = store.GetStoreBlob();

            onchainMethod.Activated = true;
            // TODO: this needs to be refactored to move this logic into BitcoinLikeOnChainPaymentMethod
            // This is likely a constructor code
            onchainMethod.NetworkFeeMode = blob.NetworkFeeMode;
            onchainMethod.FeeRate        = await prepare.GetFeeRate;
            switch (onchainMethod.NetworkFeeMode)
            {
            case NetworkFeeMode.Always:
                onchainMethod.NetworkFeeRate = (await prepare.GetNetworkFeeRate);
                onchainMethod.NextNetworkFee =
                    onchainMethod.NetworkFeeRate.GetFee(100);     // assume price for 100 bytes
                break;

            case NetworkFeeMode.Never:
                onchainMethod.NetworkFeeRate = FeeRate.Zero;
                onchainMethod.NextNetworkFee = Money.Zero;
                break;

            case NetworkFeeMode.MultiplePaymentsOnly:
                onchainMethod.NetworkFeeRate = (await prepare.GetNetworkFeeRate);
                onchainMethod.NextNetworkFee = Money.Zero;
                break;
            }

            var reserved = await prepare.ReserveAddress;

            onchainMethod.DepositAddress = reserved.Address.ToString();
            onchainMethod.KeyPath        = reserved.KeyPath;
            onchainMethod.PayjoinEnabled = blob.PayJoinEnabled &&
                                           supportedPaymentMethod
                                           .AccountDerivation.ScriptPubKeyType() != ScriptPubKeyType.Legacy &&
                                           network.SupportPayJoin;
            if (onchainMethod.PayjoinEnabled)
            {
                var prefix      = $"{supportedPaymentMethod.PaymentId.ToPrettyString()}:";
                var nodeSupport = _dashboard?.Get(network.CryptoCode)?.Status?.BitcoinStatus?.Capabilities
                                  ?.CanSupportTransactionCheck is true;
                onchainMethod.PayjoinEnabled &= supportedPaymentMethod.IsHotWallet && nodeSupport;
                if (!supportedPaymentMethod.IsHotWallet)
                {
                    logs.Write($"{prefix} Payjoin should have been enabled, but your store is not a hotwallet", InvoiceEventData.EventSeverity.Warning);
                }
                if (!nodeSupport)
                {
                    logs.Write($"{prefix} Payjoin should have been enabled, but your version of NBXplorer or full node does not support it.", InvoiceEventData.EventSeverity.Warning);
                }
                if (onchainMethod.PayjoinEnabled)
                {
                    logs.Write($"{prefix} Payjoin is enabled for this invoice.", InvoiceEventData.EventSeverity.Info);
                }
            }

            return(onchainMethod);
        }
 public override Task <bool> IsAvailable(DerivationStrategy supportedPaymentMethod, BTCPayNetwork network)
 {
     return(Task.FromResult(_ExplorerProvider.IsAvailable(network)));
 }
Beispiel #11
0
        internal async Task <DataWrapper <InvoiceResponse> > CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl)
        {
            var derivationStrategies = store.GetDerivationStrategies(_NetworkProvider).Where(c => _ExplorerClients.IsAvailable(c.Network.CryptoCode)).ToList();

            if (derivationStrategies.Count == 0)
            {
                throw new BitpayHttpException(400, "No derivation strategy are available now for this store");
            }
            var entity = new InvoiceEntity
            {
                InvoiceTime = DateTimeOffset.UtcNow
            };

            entity.SetDerivationStrategies(derivationStrategies);

            var storeBlob       = store.GetStoreBlob();
            Uri notificationUri = Uri.IsWellFormedUriString(invoice.NotificationURL, UriKind.Absolute) ? new Uri(invoice.NotificationURL, UriKind.Absolute) : null;

            if (notificationUri == null || (notificationUri.Scheme != "http" && notificationUri.Scheme != "https")) //TODO: Filer non routable addresses ?
            {
                notificationUri = null;
            }
            EmailAddressAttribute emailValidator = new EmailAddressAttribute();

            entity.ExpirationTime       = entity.InvoiceTime.AddMinutes(storeBlob.InvoiceExpiration);
            entity.MonitoringExpiration = entity.ExpirationTime + TimeSpan.FromMinutes(storeBlob.MonitoringExpiration);
            entity.OrderId               = invoice.OrderId;
            entity.ServerUrl             = serverUrl;
            entity.FullNotifications     = invoice.FullNotifications || invoice.ExtendedNotifications;
            entity.ExtendedNotifications = invoice.ExtendedNotifications;
            entity.NotificationURL       = notificationUri?.AbsoluteUri;
            entity.BuyerInformation      = Map <Invoice, BuyerInformation>(invoice);
            //Another way of passing buyer info to support
            FillBuyerInfo(invoice.Buyer, entity.BuyerInformation);
            if (entity?.BuyerInformation?.BuyerEmail != null)
            {
                if (!EmailValidator.IsEmail(entity.BuyerInformation.BuyerEmail))
                {
                    throw new BitpayHttpException(400, "Invalid email");
                }
                entity.RefundMail = entity.BuyerInformation.BuyerEmail;
            }
            entity.ProductInformation = Map <Invoice, ProductInformation>(invoice);
            entity.RedirectURL        = invoice.RedirectURL ?? store.StoreWebsite;
            entity.Status             = "new";
            entity.SpeedPolicy        = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);

            var queries = derivationStrategies
                          .Select(derivationStrategy => (Wallet: _WalletProvider.GetWallet(derivationStrategy.Network),
                                                         DerivationStrategy: derivationStrategy.DerivationStrategyBase,
                                                         Network: derivationStrategy.Network,
                                                         RateProvider: _RateProviders.GetRateProvider(derivationStrategy.Network, false),
                                                         FeeRateProvider: _FeeProviderFactory.CreateFeeProvider(derivationStrategy.Network)))
                          .Where(_ => _.Wallet != null &&
                                 _.FeeRateProvider != null &&
                                 _.RateProvider != null)
                          .Select(_ =>
            {
                return(new
                {
                    network = _.Network,
                    getFeeRate = _.FeeRateProvider.GetFeeRateAsync(),
                    getRate = storeBlob.ApplyRateRules(_.Network, _.RateProvider).GetRateAsync(invoice.Currency),
                    getAddress = _.Wallet.ReserveAddressAsync(_.DerivationStrategy)
                });
            });

            bool legacyBTCisSet = false;
            var  cryptoDatas    = new Dictionary <string, CryptoData>();

            foreach (var q in queries)
            {
                CryptoData cryptoData = new CryptoData();
                cryptoData.CryptoCode     = q.network.CryptoCode;
                cryptoData.FeeRate        = (await q.getFeeRate);
                cryptoData.TxFee          = GetTxFee(storeBlob, cryptoData.FeeRate); // assume price for 100 bytes
                cryptoData.Rate           = await q.getRate;
                cryptoData.DepositAddress = (await q.getAddress).ToString();

#pragma warning disable CS0618
                if (q.network.IsBTC)
                {
                    legacyBTCisSet        = true;
                    entity.TxFee          = cryptoData.TxFee;
                    entity.Rate           = cryptoData.Rate;
                    entity.DepositAddress = cryptoData.DepositAddress;
                }
#pragma warning restore CS0618
                cryptoDatas.Add(cryptoData.CryptoCode, cryptoData);
            }

            if (!legacyBTCisSet)
            {
                // Legacy Bitpay clients expect information for BTC information, even if the store do not support it
#pragma warning disable CS0618
                var btc          = _NetworkProvider.BTC;
                var feeProvider  = _FeeProviderFactory.CreateFeeProvider(btc);
                var rateProvider = storeBlob.ApplyRateRules(btc, _RateProviders.GetRateProvider(btc, false));
                if (feeProvider != null && rateProvider != null)
                {
                    var gettingFee  = feeProvider.GetFeeRateAsync();
                    var gettingRate = rateProvider.GetRateAsync(invoice.Currency);
                    entity.TxFee = GetTxFee(storeBlob, await gettingFee);
                    entity.Rate  = await gettingRate;
                }
#pragma warning restore CS0618
            }

            entity.SetCryptoData(cryptoDatas);
            entity.PosData = invoice.PosData;
            entity         = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, _NetworkProvider);

            _EventAggregator.Publish(new Events.InvoiceEvent(entity, 1001, "invoice_created"));
            var resp = entity.EntityToDTO(_NetworkProvider);
            return(new DataWrapper <InvoiceResponse>(resp)
            {
                Facade = "pos/invoice"
            });
        }