public override object PreparePayment(DerivationSchemeSettings supportedPaymentMethod, StoreData store, BTCPayNetworkBase network) { var storeBlob = store.GetStoreBlob(); return(new Prepare() { GetFeeRate = _FeeRateProviderFactory.CreateFeeProvider(network).GetFeeRateAsync(storeBlob.RecommendedFeeBlockTarget), GetNetworkFeeRate = storeBlob.NetworkFeeMode == NetworkFeeMode.Never ? null : _FeeRateProviderFactory.CreateFeeProvider(network).GetFeeRateAsync(), ReserveAddress = _WalletProvider.GetWallet(network) .ReserveAddressAsync(supportedPaymentMethod.AccountDerivation) }); }
public override object PreparePayment(DerivationSchemeSettings supportedPaymentMethod, StoreData store, BTCPayNetwork network) { return(new Prepare() { GetFeeRate = _FeeRateProviderFactory.CreateFeeProvider(network).GetFeeRateAsync(), ReserveAddress = _WalletProvider.GetWallet(network).ReserveAddressAsync(supportedPaymentMethod.AccountDerivation) }); }
public override async Task <IPaymentMethodDetails> CreatePaymentMethodDetails(DerivationStrategy supportedPaymentMethod, PaymentMethod paymentMethod, BTCPayNetwork network) { 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); }
public override async Task <IPaymentMethodDetails> CreatePaymentMethodDetails(DerivationStrategy supportedPaymentMethod, PaymentMethod paymentMethod, StoreData store, 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); }
public async Task <IActionResult> GetOnChainFeeRate(string storeId, string cryptoCode, int?blockTarget = null) { if (IsInvalidWalletRequest(cryptoCode, out var network, out var derivationScheme, out var actionResult)) { return(actionResult); } var feeRateTarget = blockTarget ?? Store.GetStoreBlob().RecommendedFeeBlockTarget; return(Ok(new OnChainWalletFeeRateData() { FeeRate = await _feeProviderFactory.CreateFeeProvider(network) .GetFeeRateAsync(feeRateTarget), })); }
public async Task <IActionResult> ShowOnChainWalletOverview(string storeId, string cryptoCode) { if (IsInvalidWalletRequest(cryptoCode, out BTCPayNetwork network, out DerivationSchemeSettings derivationScheme, out IActionResult actionResult)) { return(actionResult); } var wallet = _btcPayWalletProvider.GetWallet(network); var feeRateTarget = Store.GetStoreBlob().RecommendedFeeBlockTarget; return(Ok(new OnChainWalletOverviewData() { Balance = await wallet.GetBalance(derivationScheme.AccountDerivation), FeeRate = await _feeProviderFactory.CreateFeeProvider(network) .GetFeeRateAsync(feeRateTarget), })); }
public async Task <IActionResult> LedgerConnection( string command, // getinfo string cryptoCode = null, // getxpub [ModelBinder(typeof(ModelBinders.DerivationSchemeModelBinder))] DerivationStrategyBase derivationScheme = null, int account = 0, // sendtoaddress string destination = null, string amount = null, string feeRate = null, string substractFees = null ) { if (!HttpContext.WebSockets.IsWebSocketRequest) { return(NotFound()); } var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); using (var normalOperationTimeout = new CancellationTokenSource()) using (var signTimeout = new CancellationTokenSource()) { normalOperationTimeout.CancelAfter(TimeSpan.FromMinutes(30)); var hw = new HardwareWalletService(webSocket); object result = null; try { BTCPayNetwork network = null; if (cryptoCode != null) { network = _NetworkProvider.GetNetwork(cryptoCode); if (network == null) { throw new FormatException("Invalid value for crypto code"); } } BitcoinAddress destinationAddress = null; if (destination != null) { try { destinationAddress = BitcoinAddress.Create(destination, network.NBitcoinNetwork); } catch { } if (destinationAddress == null) { throw new FormatException("Invalid value for destination"); } } FeeRate feeRateValue = null; if (feeRate != null) { try { feeRateValue = new FeeRate(Money.Satoshis(int.Parse(feeRate, CultureInfo.InvariantCulture)), 1); } catch { } if (feeRateValue == null || feeRateValue.FeePerK <= Money.Zero) { throw new FormatException("Invalid value for fee rate"); } } Money amountBTC = null; if (amount != null) { try { amountBTC = Money.Parse(amount); } catch { } if (amountBTC == null || amountBTC <= Money.Zero) { throw new FormatException("Invalid value for amount"); } } bool subsctractFeesValue = false; if (substractFees != null) { try { subsctractFeesValue = bool.Parse(substractFees); } catch { throw new FormatException("Invalid value for subtract fees"); } } if (command == "test") { result = await hw.Test(normalOperationTimeout.Token); } if (command == "getxpub") { var getxpubResult = await hw.GetExtPubKey(network, account, normalOperationTimeout.Token); result = getxpubResult; } if (command == "getinfo") { var strategy = GetDirectDerivationStrategy(derivationScheme); if (strategy == null || await hw.GetKeyPath(network, strategy, normalOperationTimeout.Token) == null) { throw new Exception($"This store is not configured to use this ledger"); } var feeProvider = _feeRateProvider.CreateFeeProvider(network); var recommendedFees = feeProvider.GetFeeRateAsync(); var balance = _walletProvider.GetWallet(network).GetBalance(derivationScheme); result = new GetInfoResult() { Balance = (double)(await balance).ToDecimal(MoneyUnit.BTC), RecommendedSatoshiPerByte = (int)(await recommendedFees).GetFee(1).Satoshi }; } if (command == "sendtoaddress") { if (!_dashboard.IsFullySynched(network.CryptoCode, out var summary)) { throw new Exception($"{network.CryptoCode}: not started or fully synched"); } var strategy = GetDirectDerivationStrategy(derivationScheme); var wallet = _walletProvider.GetWallet(network); var change = wallet.GetChangeAddressAsync(derivationScheme); var unspentCoins = await wallet.GetUnspentCoins(derivationScheme); var changeAddress = await change; var send = new[] { (
public async Task <IActionResult> WalletSend( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, string defaultDestination = null, string defaultAmount = null) { if (walletId?.StoreId == null) { return(NotFound()); } var store = await Repository.FindStore(walletId.StoreId, GetUserId()); DerivationSchemeSettings paymentMethod = GetDerivationSchemeSettings(walletId); if (paymentMethod == null) { return(NotFound()); } var network = this.NetworkProvider.GetNetwork <BTCPayNetwork>(walletId?.CryptoCode); if (network == null || network.ReadonlyWallet) { return(NotFound()); } var storeData = store.GetStoreBlob(); var rateRules = store.GetStoreBlob().GetRateRules(NetworkProvider); rateRules.Spread = 0.0m; var currencyPair = new Rating.CurrencyPair(paymentMethod.PaymentId.CryptoCode, GetCurrencyCode(storeData.DefaultLang) ?? "USD"); double.TryParse(defaultAmount, out var amount); var model = new WalletSendModel() { Outputs = new List <WalletSendModel.TransactionOutput>() { new WalletSendModel.TransactionOutput() { Amount = Convert.ToDecimal(amount), DestinationAddress = defaultDestination } }, CryptoCode = walletId.CryptoCode }; var feeProvider = _feeRateProvider.CreateFeeProvider(network); var recommendedFees = feeProvider.GetFeeRateAsync(); var balance = _walletProvider.GetWallet(network).GetBalance(paymentMethod.AccountDerivation); model.CurrentBalance = await balance; model.RecommendedSatoshiPerByte = (int)(await recommendedFees).GetFee(1).Satoshi; model.FeeSatoshiPerByte = model.RecommendedSatoshiPerByte; model.SupportRBF = network.SupportRBF; using (CancellationTokenSource cts = new CancellationTokenSource()) { try { cts.CancelAfter(TimeSpan.FromSeconds(5)); var result = await RateFetcher.FetchRate(currencyPair, rateRules, cts.Token).WithCancellation(cts.Token); if (result.BidAsk != null) { model.Rate = result.BidAsk.Center; model.Divisibility = _currencyTable.GetNumberFormatInfo(currencyPair.Right, true).CurrencyDecimalDigits; model.Fiat = currencyPair.Right; } else { model.RateError = $"{result.EvaluatedRule} ({string.Join(", ", result.Errors.OfType<object>().ToArray())})"; } } catch (Exception ex) { model.RateError = ex.Message; } } return(View(model)); }
public async Task <IActionResult> LedgerConnection( string storeId, string command, // getinfo string cryptoCode = null, // sendtoaddress string destination = null, string amount = null, string feeRate = null, string substractFees = null ) { if (!HttpContext.WebSockets.IsWebSocketRequest) { return(NotFound()); } var store = await _Repo.FindStore(storeId, GetUserId()); if (store == null) { return(NotFound()); } var webSocket = await HttpContext.WebSockets.AcceptWebSocketAsync(); var hw = new HardwareWalletService(webSocket); object result = null; try { BTCPayNetwork network = null; if (cryptoCode != null) { network = _NetworkProvider.GetNetwork(cryptoCode); if (network == null) { throw new FormatException("Invalid value for crypto code"); } } BitcoinAddress destinationAddress = null; if (destination != null) { try { destinationAddress = BitcoinAddress.Create(destination); } catch { } if (destinationAddress == null) { throw new FormatException("Invalid value for destination"); } } FeeRate feeRateValue = null; if (feeRate != null) { try { feeRateValue = new FeeRate(Money.Satoshis(int.Parse(feeRate)), 1); } catch { } if (feeRateValue == null || feeRateValue.FeePerK <= Money.Zero) { throw new FormatException("Invalid value for fee rate"); } } Money amountBTC = null; if (amount != null) { try { amountBTC = Money.Parse(amount); } catch { } if (amountBTC == null || amountBTC <= Money.Zero) { throw new FormatException("Invalid value for amount"); } } bool subsctractFeesValue = false; if (substractFees != null) { try { subsctractFeesValue = bool.Parse(substractFees); } catch { throw new FormatException("Invalid value for substract fees"); } } if (command == "test") { result = await hw.Test(); } if (command == "getxpub") { result = await hw.GetExtPubKey(network); } if (command == "getinfo") { var strategy = GetDirectDerivationStrategy(store, network); var strategyBase = GetDerivationStrategy(store, network); if (!await hw.SupportDerivation(network, strategy)) { throw new Exception($"This store is not configured to use this ledger"); } var feeProvider = _FeeRateProvider.CreateFeeProvider(network); var recommendedFees = feeProvider.GetFeeRateAsync(); var balance = _WalletProvider.GetWallet(network).GetBalance(strategyBase); result = new GetInfoResult() { Balance = (double)(await balance).ToDecimal(MoneyUnit.BTC), RecommendedSatoshiPerByte = (int)(await recommendedFees).GetFee(1).Satoshi }; } if (command == "sendtoaddress") { var strategy = GetDirectDerivationStrategy(store, network); var strategyBase = GetDerivationStrategy(store, network); var wallet = _WalletProvider.GetWallet(network); var change = wallet.GetChangeAddressAsync(strategyBase); var unspentCoins = await wallet.GetUnspentCoins(strategyBase); var changeAddress = await change; unspentCoins.Item2.TryAdd(changeAddress.Item1.ScriptPubKey, changeAddress.Item2); var transaction = await hw.SendToAddress(strategy, unspentCoins.Item1, network, new[] { (destinationAddress as IDestination, amountBTC, subsctractFeesValue) },
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" }); }
internal async Task <DataWrapper <InvoiceResponse> > CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl, double expiryMinutes = 15) { var derivationStrategy = store.DerivationStrategy; var entity = new InvoiceEntity { InvoiceTime = DateTimeOffset.UtcNow, DerivationStrategy = derivationStrategy ?? throw new BitpayHttpException(400, "This store has not configured the derivation strategy") }; 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(expiryMinutes); entity.MonitoringExpiration = entity.ExpirationTime + TimeSpan.FromMinutes(storeBlob.MonitoringExpiration); entity.OrderId = invoice.OrderId; entity.ServerUrl = serverUrl; entity.FullNotifications = invoice.FullNotifications; 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 = storeBlob.GetSupportedCryptoCurrencies() .Select(n => _NetworkProvider.GetNetwork(n)) .Where(n => n != null) .Select(network => { return(new { network = network, getFeeRate = _FeeProviderFactory.CreateFeeProvider(network).GetFeeRateAsync(), getRate = _RateProvider.GetRateAsync(invoice.Currency), getAddress = _Wallet.ReserveAddressAsync(ParseDerivationStrategy(derivationStrategy, network)) }); }); 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 = storeBlob.NetworkFeeDisabled ? Money.Zero : cryptoData.FeeRate.GetFee(100); // assume price for 100 bytes cryptoData.Rate = await q.getRate; cryptoData.DepositAddress = (await q.getAddress).ToString(); #pragma warning disable CS0618 if (q.network.CryptoCode == "BTC") { entity.TxFee = cryptoData.TxFee; entity.Rate = cryptoData.Rate; entity.DepositAddress = cryptoData.DepositAddress; } #pragma warning restore CS0618 cryptoDatas.Add(cryptoData.CryptoCode, cryptoData); } entity.SetCryptoData(cryptoDatas); entity.PosData = invoice.PosData; entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, _NetworkProvider); _Watcher.Watch(entity.Id); var resp = entity.EntityToDTO(_NetworkProvider); return(new DataWrapper <InvoiceResponse>(resp) { Facade = "pos/invoice" }); }
public async Task <IActionResult> WalletSend( [ModelBinder(typeof(WalletIdModelBinder))] WalletId walletId, string defaultDestination = null, string defaultAmount = null, bool advancedMode = false) { if (walletId?.StoreId == null) { return(NotFound()); } var store = await Repository.FindStore(walletId.StoreId, GetUserId()); DerivationStrategy paymentMethod = GetPaymentMethod(walletId, store); if (paymentMethod == null) { return(NotFound()); } var network = this.NetworkProvider.GetNetwork(walletId?.CryptoCode); if (network == null) { return(NotFound()); } var storeData = store.GetStoreBlob(); var rateRules = store.GetStoreBlob().GetRateRules(NetworkProvider); rateRules.Spread = 0.0m; var currencyPair = new Rating.CurrencyPair(paymentMethod.PaymentId.CryptoCode, GetCurrencyCode(storeData.DefaultLang) ?? "USD"); WalletSendModel model = new WalletSendModel() { Destination = defaultDestination, CryptoCode = walletId.CryptoCode }; if (double.TryParse(defaultAmount, out var amount)) { model.Amount = (decimal)amount; } var feeProvider = _feeRateProvider.CreateFeeProvider(network); var recommendedFees = feeProvider.GetFeeRateAsync(); var balance = _walletProvider.GetWallet(network).GetBalance(paymentMethod.DerivationStrategyBase); model.CurrentBalance = (await balance).ToDecimal(MoneyUnit.BTC); model.RecommendedSatoshiPerByte = (int)(await recommendedFees).GetFee(1).Satoshi; model.FeeSatoshiPerByte = model.RecommendedSatoshiPerByte; using (CancellationTokenSource cts = new CancellationTokenSource()) { try { cts.CancelAfter(TimeSpan.FromSeconds(5)); var result = await RateFetcher.FetchRate(currencyPair, rateRules).WithCancellation(cts.Token); if (result.BidAsk != null) { model.Rate = result.BidAsk.Center; model.Divisibility = _currencyTable.GetNumberFormatInfo(currencyPair.Right, true).CurrencyDecimalDigits; model.Fiat = currencyPair.Right; } else { model.RateError = $"{result.EvaluatedRule} ({string.Join(", ", result.Errors.OfType<object>().ToArray())})"; } } catch (Exception ex) { model.RateError = ex.Message; } } model.AdvancedMode = advancedMode; return(View(model)); }