diff --git a/BTCPayServer/BTCPayNetwork.cs b/BTCPayServer/BTCPayNetwork.cs index d399438eb..a432fe45d 100644 --- a/BTCPayServer/BTCPayNetwork.cs +++ b/BTCPayServer/BTCPayNetwork.cs @@ -12,5 +12,16 @@ namespace BTCPayServer public string CryptoCode { get; internal set; } public string BlockExplorerLink { get; internal set; } public string UriScheme { get; internal set; } + + + [Obsolete("Should not be needed")] + public bool IsBTC + { + get + { + return CryptoCode == "BTC"; + } + } + } } diff --git a/BTCPayServer/BTCPayNetworkProvider.cs b/BTCPayServer/BTCPayNetworkProvider.cs index 84e9178ec..162b6bc56 100644 --- a/BTCPayServer/BTCPayNetworkProvider.cs +++ b/BTCPayServer/BTCPayNetworkProvider.cs @@ -11,7 +11,7 @@ namespace BTCPayServer Dictionary _Networks = new Dictionary(); public BTCPayNetworkProvider(Network network) { - if(network == Network.Main) + if (network == Network.Main) { Add(new BTCPayNetwork() { @@ -45,6 +45,15 @@ namespace BTCPayServer } } + [Obsolete("Should not be needed")] + public BTCPayNetwork BTC + { + get + { + return GetNetwork("BTC"); + } + } + public void Add(BTCPayNetwork network) { _Networks.Add(network.CryptoCode, network); diff --git a/BTCPayServer/Controllers/InvoiceController.UI.cs b/BTCPayServer/Controllers/InvoiceController.UI.cs index 5896d773c..3ac0faecf 100644 --- a/BTCPayServer/Controllers/InvoiceController.UI.cs +++ b/BTCPayServer/Controllers/InvoiceController.UI.cs @@ -325,7 +325,7 @@ namespace BTCPayServer.Controllers return View(model); } var store = await _StoreRepository.FindStore(model.StoreId, GetUserId()); - if (string.IsNullOrEmpty(store.DerivationStrategy)) + if (store.GetDerivationStrategies(_NetworkProvider).Count() == 0) { StatusMessage = "Error: You need to configure the derivation scheme in order to create an invoice"; return RedirectToAction(nameof(StoresController.UpdateStore), "Stores", new diff --git a/BTCPayServer/Controllers/InvoiceController.cs b/BTCPayServer/Controllers/InvoiceController.cs index 0b18a743e..920bc61e5 100644 --- a/BTCPayServer/Controllers/InvoiceController.cs +++ b/BTCPayServer/Controllers/InvoiceController.cs @@ -82,12 +82,15 @@ namespace BTCPayServer.Controllers internal async Task> CreateInvoiceCore(Invoice invoice, StoreData store, string serverUrl, double expiryMinutes = 15) { - var derivationStrategy = store.DerivationStrategy; + var derivationStrategies = store.GetDerivationStrategies(_NetworkProvider).ToList(); + if (derivationStrategies.Count == 0) + throw new BitpayHttpException(400, "This store has not configured the derivation strategy"); var entity = new InvoiceEntity { - InvoiceTime = DateTimeOffset.UtcNow, - DerivationStrategy = derivationStrategy ?? throw new BitpayHttpException(400, "This store has not configured the derivation strategy") + 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 ? @@ -113,17 +116,15 @@ namespace BTCPayServer.Controllers 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 => + var queries = derivationStrategies + .Select(derivationStrategy => { return new { - network = network, - getFeeRate = _FeeProviderFactory.CreateFeeProvider(network).GetFeeRateAsync(), + network = derivationStrategy.Network, + getFeeRate = _FeeProviderFactory.CreateFeeProvider(derivationStrategy.Network).GetFeeRateAsync(), getRate = _RateProvider.GetRateAsync(invoice.Currency), - getAddress = _Wallet.ReserveAddressAsync(ParseDerivationStrategy(derivationStrategy, network)) + getAddress = _Wallet.ReserveAddressAsync(derivationStrategy) }; }); @@ -138,7 +139,7 @@ namespace BTCPayServer.Controllers cryptoData.DepositAddress = (await q.getAddress).ToString(); #pragma warning disable CS0618 - if (q.network.CryptoCode == "BTC") + if (q.network.IsBTC) { entity.TxFee = cryptoData.TxFee; entity.Rate = cryptoData.Rate; diff --git a/BTCPayServer/Controllers/StoresController.cs b/BTCPayServer/Controllers/StoresController.cs index af2cd48ad..61d471d64 100644 --- a/BTCPayServer/Controllers/StoresController.cs +++ b/BTCPayServer/Controllers/StoresController.cs @@ -43,10 +43,11 @@ namespace BTCPayServer.Controllers _TokenController = tokenController; _Wallet = wallet; _Env = env; - _Network = networkProvider.GetNetwork("BTC").NBitcoinNetwork; + _NetworkProvider = networkProvider; _CallbackController = callbackController; } - Network _Network; + BTCPayNetworkProvider _NetworkProvider; + CallbackController _CallbackController; BTCPayWallet _Wallet; AccessTokenController _TokenController; @@ -93,8 +94,12 @@ namespace BTCPayServer.Controllers StoresViewModel result = new StoresViewModel(); result.StatusMessage = StatusMessage; var stores = await _Repo.GetStoresByUserId(GetUserId()); - var balances = stores.Select(async s => string.IsNullOrEmpty(s.DerivationStrategy) ? Money.Zero : await _Wallet.GetBalance(ParseDerivationStrategy(s.DerivationStrategy, null))).ToArray(); + var balances = stores + .Select(s => s.GetDerivationStrategies(_NetworkProvider) + .Select(async ss => (await _Wallet.GetBalance(ss)).ToString() + " " + ss.Network.CryptoCode)) + .ToArray(); + await Task.WhenAll(balances.SelectMany(_ => _)); for (int i = 0; i < stores.Length; i++) { var store = stores[i]; @@ -103,7 +108,7 @@ namespace BTCPayServer.Controllers Id = store.Id, Name = store.StoreName, WebSite = store.StoreWebsite, - Balance = await balances[i] + Balances = balances[i].Select(t => t.Result).ToArray() }); } return View(result); @@ -196,9 +201,9 @@ namespace BTCPayServer.Controllers { if (!string.IsNullOrEmpty(model.DerivationScheme)) { - var strategy = ParseDerivationStrategy(model.DerivationScheme, model.DerivationSchemeFormat); + var strategy = ParseDerivationStrategy(model.DerivationScheme, model.DerivationSchemeFormat, _NetworkProvider.BTC); await _Wallet.TrackAsync(strategy); - await _CallbackController.RegisterCallbackUriAsync(strategy); + await _CallbackController.RegisterCallbackUriAsync(strategy.DerivationStrategyBase); model.DerivationScheme = strategy.ToString(); } store.DerivationStrategy = model.DerivationScheme; @@ -236,13 +241,13 @@ namespace BTCPayServer.Controllers { try { - var scheme = ParseDerivationStrategy(model.DerivationScheme, model.DerivationSchemeFormat); - var line = scheme.GetLineFor(DerivationFeature.Deposit); + var scheme = ParseDerivationStrategy(model.DerivationScheme, model.DerivationSchemeFormat, _NetworkProvider.BTC); + var line = scheme.DerivationStrategyBase.GetLineFor(DerivationFeature.Deposit); for (int i = 0; i < 10; i++) { var address = line.Derive((uint)i); - model.AddressSamples.Add((line.Path.Derive((uint)i).ToString(), address.ScriptPubKey.GetDestinationAddress(_Network).ToString())); + model.AddressSamples.Add((line.Path.Derive((uint)i).ToString(), address.ScriptPubKey.GetDestinationAddress(scheme.Network.NBitcoinNetwork).ToString())); } } catch @@ -254,7 +259,7 @@ namespace BTCPayServer.Controllers } } - private DerivationStrategyBase ParseDerivationStrategy(string derivationScheme, string format) + private DerivationStrategy ParseDerivationStrategy(string derivationScheme, string format, BTCPayNetwork network) { if (format == "Electrum") { @@ -276,19 +281,19 @@ namespace BTCPayServer.Controllers var prefix = Utils.ToUInt32(data, false); if (!electrumMapping.TryGetValue(prefix, out string[] labels)) throw new FormatException("!electrumMapping.TryGetValue(prefix, out string[] labels)"); - var standardPrefix = Utils.ToBytes(_Network == Network.Main ? 0x0488b21eU : 0x043587cf, false); + var standardPrefix = Utils.ToBytes(network.NBitcoinNetwork == Network.Main ? 0x0488b21eU : 0x043587cf, false); for (int i = 0; i < 4; i++) data[i] = standardPrefix[i]; - derivationScheme = new BitcoinExtPubKey(Encoders.Base58Check.EncodeData(data), _Network).ToString(); + derivationScheme = new BitcoinExtPubKey(Encoders.Base58Check.EncodeData(data), network.NBitcoinNetwork).ToString(); foreach (var label in labels) { derivationScheme = derivationScheme + $"-[{label}]"; } } - return new DerivationStrategyFactory(_Network).Parse(derivationScheme); + return DerivationStrategy.Parse(new DerivationStrategyFactory(network.NBitcoinNetwork).Parse(derivationScheme).ToString(), network); } [HttpGet] diff --git a/BTCPayServer/Data/StoreData.cs b/BTCPayServer/Data/StoreData.cs index 4e767275b..84a5e4ecc 100644 --- a/BTCPayServer/Data/StoreData.cs +++ b/BTCPayServer/Data/StoreData.cs @@ -10,6 +10,7 @@ using System.Text; using System.Threading.Tasks; using System.ComponentModel; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace BTCPayServer.Data { @@ -26,11 +27,50 @@ namespace BTCPayServer.Data get; set; } + [Obsolete("Use GetDerivationStrategies instead")] public string DerivationStrategy { get; set; } + [Obsolete("Use GetDerivationStrategies instead")] + public string DerivationStrategies + { + get; + set; + } + + public IEnumerable GetDerivationStrategies(BTCPayNetworkProvider networks) + { +#pragma warning disable CS0618 + bool btcReturned = false; + if (!string.IsNullOrEmpty(DerivationStrategy)) + { + if (networks.BTC != null) + { + btcReturned = true; + yield return BTCPayServer.DerivationStrategy.Parse(DerivationStrategy, networks.BTC); + } + } + + + if (!string.IsNullOrEmpty(DerivationStrategies)) + { + JObject strategies = JObject.Parse(DerivationStrategies); + foreach (var strat in strategies.Properties()) + { + var network = networks.GetNetwork(strat.Name); + if (network != null) + { + if (network == networks.BTC && btcReturned) + continue; + yield return BTCPayServer.DerivationStrategy.Parse(strat.Value(), network); + } + } + } +#pragma warning restore CS0618 + } + public string StoreName { get; set; @@ -97,19 +137,5 @@ namespace BTCPayServer.Data get; set; } - - [Obsolete("Use GetSupportedCryptoCurrencies() instead")] - public string[] SupportedCryptoCurrencies { get; set; } - - public string[] GetSupportedCryptoCurrencies() - { -#pragma warning disable CS0618 - if(SupportedCryptoCurrencies == null) - { - return new string[] { "BTC" }; - } - return SupportedCryptoCurrencies; -#pragma warning restore CS0618 - } } } diff --git a/BTCPayServer/DerivationStrategy.cs b/BTCPayServer/DerivationStrategy.cs new file mode 100644 index 000000000..4f2bf101d --- /dev/null +++ b/BTCPayServer/DerivationStrategy.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using NBitcoin; +using NBXplorer.DerivationStrategy; + +namespace BTCPayServer +{ + public class DerivationStrategy + { + private DerivationStrategyBase _DerivationStrategy; + private BTCPayNetwork _Network; + + DerivationStrategy(DerivationStrategyBase result, BTCPayNetwork network) + { + this._DerivationStrategy = result; + this._Network = network; + } + + public static DerivationStrategy Parse(string derivationStrategy, BTCPayNetwork network) + { + if (network == null) + throw new ArgumentNullException(nameof(network)); + if (derivationStrategy == null) + throw new ArgumentNullException(nameof(derivationStrategy)); + var result = new NBXplorer.DerivationStrategy.DerivationStrategyFactory(network.NBitcoinNetwork).Parse(derivationStrategy); + return new DerivationStrategy(result, network); + } + + public BTCPayNetwork Network { get { return this._Network; } } + + public DerivationStrategyBase DerivationStrategyBase { get { return this._DerivationStrategy; } } + + public override string ToString() + { + return _DerivationStrategy.ToString(); + } + } +} diff --git a/BTCPayServer/Extensions.cs b/BTCPayServer/Extensions.cs index 2678a8368..81be23217 100644 --- a/BTCPayServer/Extensions.cs +++ b/BTCPayServer/Extensions.cs @@ -22,6 +22,11 @@ namespace BTCPayServer { public static class Extensions { + public static bool SupportDropColumn(this Microsoft.EntityFrameworkCore.Migrations.Migration migration, string activeProvider) + { + return activeProvider != "Microsoft.EntityFrameworkCore.Sqlite"; + } + public static async Task> GetTransactions(this ExplorerClient client, uint256[] hashes, CancellationToken cts = default(CancellationToken)) { hashes = hashes.Distinct().ToArray(); diff --git a/BTCPayServer/Migrations/20171012020112_PendingInvoices.cs b/BTCPayServer/Migrations/20171012020112_PendingInvoices.cs index 4f66f788a..5ac527ac3 100644 --- a/BTCPayServer/Migrations/20171012020112_PendingInvoices.cs +++ b/BTCPayServer/Migrations/20171012020112_PendingInvoices.cs @@ -8,7 +8,7 @@ namespace BTCPayServer.Migrations { protected override void Up(MigrationBuilder migrationBuilder) { - if (SupportDropColumn(migrationBuilder.ActiveProvider)) + if (this.SupportDropColumn(migrationBuilder.ActiveProvider)) { migrationBuilder.DropColumn( name: "Name", @@ -30,11 +30,6 @@ namespace BTCPayServer.Migrations }); } - private bool SupportDropColumn(string activeProvider) - { - return activeProvider != "Microsoft.EntityFrameworkCore.Sqlite"; - } - protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( diff --git a/BTCPayServer/Migrations/20180106095215_DerivationStrategies.Designer.cs b/BTCPayServer/Migrations/20180106095215_DerivationStrategies.Designer.cs new file mode 100644 index 000000000..24ac8efcf --- /dev/null +++ b/BTCPayServer/Migrations/20180106095215_DerivationStrategies.Designer.cs @@ -0,0 +1,483 @@ +// +using BTCPayServer.Data; +using BTCPayServer.Services.Invoices; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.Storage.Internal; +using System; + +namespace BTCPayServer.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20180106095215_DerivationStrategies")] + partial class DerivationStrategies + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.1-rtm-125"); + + modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b => + { + b.Property("Address") + .ValueGeneratedOnAdd(); + + b.Property("CreatedTime"); + + b.Property("InvoiceDataId"); + + b.HasKey("Address"); + + b.HasIndex("InvoiceDataId"); + + b.ToTable("AddressInvoices"); + }); + + modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b => + { + b.Property("InvoiceDataId"); + + b.Property("Address"); + + b.Property("Assigned"); + + b.Property("CryptoCode"); + + b.Property("UnAssigned"); + + b.HasKey("InvoiceDataId", "Address"); + + b.ToTable("HistoricalAddressInvoices"); + }); + + modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Blob"); + + b.Property("Created"); + + b.Property("CustomerEmail"); + + b.Property("ExceptionStatus"); + + b.Property("ItemCode"); + + b.Property("OrderId"); + + b.Property("Status"); + + b.Property("StoreDataId"); + + b.HasKey("Id"); + + b.HasIndex("StoreDataId"); + + b.ToTable("Invoices"); + }); + + modelBuilder.Entity("BTCPayServer.Data.PairedSINData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Facade"); + + b.Property("Label"); + + b.Property("PairingTime"); + + b.Property("SIN"); + + b.Property("StoreDataId"); + + b.HasKey("Id"); + + b.HasIndex("SIN"); + + b.HasIndex("StoreDataId"); + + b.ToTable("PairedSINData"); + }); + + modelBuilder.Entity("BTCPayServer.Data.PairingCodeData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DateCreated"); + + b.Property("Expiration"); + + b.Property("Facade"); + + b.Property("Label"); + + b.Property("SIN"); + + b.Property("StoreDataId"); + + b.Property("TokenValue"); + + b.HasKey("Id"); + + b.ToTable("PairingCodes"); + }); + + modelBuilder.Entity("BTCPayServer.Data.PaymentData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Accounted"); + + b.Property("Blob"); + + b.Property("InvoiceDataId"); + + b.HasKey("Id"); + + b.HasIndex("InvoiceDataId"); + + b.ToTable("Payments"); + }); + + modelBuilder.Entity("BTCPayServer.Data.PendingInvoiceData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.HasKey("Id"); + + b.ToTable("PendingInvoices"); + }); + + modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Blob"); + + b.Property("InvoiceDataId"); + + b.HasKey("Id"); + + b.HasIndex("InvoiceDataId"); + + b.ToTable("RefundAddresses"); + }); + + modelBuilder.Entity("BTCPayServer.Data.SettingData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Value"); + + b.HasKey("Id"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("BTCPayServer.Data.StoreData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("DerivationStrategies"); + + b.Property("DerivationStrategy"); + + b.Property("SpeedPolicy"); + + b.Property("StoreBlob"); + + b.Property("StoreCertificate"); + + b.Property("StoreName"); + + b.Property("StoreWebsite"); + + b.HasKey("Id"); + + b.ToTable("Stores"); + }); + + modelBuilder.Entity("BTCPayServer.Data.UserStore", b => + { + b.Property("ApplicationUserId"); + + b.Property("StoreDataId"); + + b.Property("Role"); + + b.HasKey("ApplicationUserId", "StoreDataId"); + + b.HasIndex("StoreDataId"); + + b.ToTable("UserStore"); + }); + + modelBuilder.Entity("BTCPayServer.Models.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessFailedCount"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Email") + .HasMaxLength(256); + + b.Property("EmailConfirmed"); + + b.Property("LockoutEnabled"); + + b.Property("LockoutEnd"); + + b.Property("NormalizedEmail") + .HasMaxLength(256); + + b.Property("NormalizedUserName") + .HasMaxLength(256); + + b.Property("PasswordHash"); + + b.Property("PhoneNumber"); + + b.Property("PhoneNumberConfirmed"); + + b.Property("RequiresEmailConfirmation"); + + b.Property("SecurityStamp"); + + b.Property("TwoFactorEnabled"); + + b.Property("UserName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken(); + + b.Property("Name") + .HasMaxLength(256); + + b.Property("NormalizedName") + .HasMaxLength(256); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("RoleId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClaimType"); + + b.Property("ClaimValue"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider"); + + b.Property("ProviderKey"); + + b.Property("ProviderDisplayName"); + + b.Property("UserId") + .IsRequired(); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId"); + + b.Property("RoleId"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId"); + + b.Property("LoginProvider"); + + b.Property("Name"); + + b.Property("Value"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b => + { + b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData") + .WithMany("AddressInvoices") + .HasForeignKey("InvoiceDataId"); + }); + + modelBuilder.Entity("BTCPayServer.Data.HistoricalAddressInvoiceData", b => + { + b.HasOne("BTCPayServer.Data.InvoiceData") + .WithMany("HistoricalAddressInvoices") + .HasForeignKey("InvoiceDataId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("BTCPayServer.Data.InvoiceData", b => + { + b.HasOne("BTCPayServer.Data.StoreData", "StoreData") + .WithMany() + .HasForeignKey("StoreDataId"); + }); + + modelBuilder.Entity("BTCPayServer.Data.PaymentData", b => + { + b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData") + .WithMany("Payments") + .HasForeignKey("InvoiceDataId"); + }); + + modelBuilder.Entity("BTCPayServer.Data.RefundAddressesData", b => + { + b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData") + .WithMany("RefundAddresses") + .HasForeignKey("InvoiceDataId"); + }); + + modelBuilder.Entity("BTCPayServer.Data.UserStore", b => + { + b.HasOne("BTCPayServer.Models.ApplicationUser", "ApplicationUser") + .WithMany("UserStores") + .HasForeignKey("ApplicationUserId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("BTCPayServer.Data.StoreData", "StoreData") + .WithMany("UserStores") + .HasForeignKey("StoreDataId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("BTCPayServer.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("BTCPayServer.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole") + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("BTCPayServer.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("BTCPayServer.Models.ApplicationUser") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/BTCPayServer/Migrations/20180106095215_DerivationStrategies.cs b/BTCPayServer/Migrations/20180106095215_DerivationStrategies.cs new file mode 100644 index 000000000..e2d22edc3 --- /dev/null +++ b/BTCPayServer/Migrations/20180106095215_DerivationStrategies.cs @@ -0,0 +1,24 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace BTCPayServer.Migrations +{ + public partial class DerivationStrategies : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "DerivationStrategies", + table: "Stores", + nullable: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "DerivationStrategies", + table: "Stores"); + } + } +} diff --git a/BTCPayServer/Migrations/ApplicationDbContextModelSnapshot.cs b/BTCPayServer/Migrations/ApplicationDbContextModelSnapshot.cs index bb586afe1..182698abe 100644 --- a/BTCPayServer/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/BTCPayServer/Migrations/ApplicationDbContextModelSnapshot.cs @@ -190,6 +190,8 @@ namespace BTCPayServer.Migrations b.Property("Id") .ValueGeneratedOnAdd(); + b.Property("DerivationStrategies"); + b.Property("DerivationStrategy"); b.Property("SpeedPolicy"); diff --git a/BTCPayServer/Models/StoreViewModels/StoresViewModel.cs b/BTCPayServer/Models/StoreViewModels/StoresViewModel.cs index 67b25c472..25e07b7ca 100644 --- a/BTCPayServer/Models/StoreViewModels/StoresViewModel.cs +++ b/BTCPayServer/Models/StoreViewModels/StoresViewModel.cs @@ -32,7 +32,7 @@ namespace BTCPayServer.Models.StoreViewModels { get; set; } - public Money Balance + public string[] Balances { get; set; } diff --git a/BTCPayServer/Services/Invoices/InvoiceEntity.cs b/BTCPayServer/Services/Invoices/InvoiceEntity.cs index 30d9ab5d7..54cd7ae7c 100644 --- a/BTCPayServer/Services/Invoices/InvoiceEntity.cs +++ b/BTCPayServer/Services/Invoices/InvoiceEntity.cs @@ -153,11 +153,64 @@ namespace BTCPayServer.Services.Invoices get; set; } + + [Obsolete("Use GetDerivationStrategies instead")] public string DerivationStrategy { get; set; } + + [Obsolete("Use GetDerivationStrategies instead")] + public string DerivationStrategies + { + get; + set; + } + + public IEnumerable GetDerivationStrategies(BTCPayNetworkProvider networks) + { +#pragma warning disable CS0618 + bool btcReturned = false; + if (!string.IsNullOrEmpty(DerivationStrategies)) + { + JObject strategies = JObject.Parse(DerivationStrategies); + foreach (var strat in strategies.Properties()) + { + var network = networks.GetNetwork(strat.Name); + if (network != null) + { + if (network == networks.BTC && btcReturned) + btcReturned = true; + yield return BTCPayServer.DerivationStrategy.Parse(strat.Value.Value(), network); + } + } + } + + if (!btcReturned && !string.IsNullOrEmpty(DerivationStrategy)) + { + if (networks.BTC != null) + { + yield return BTCPayServer.DerivationStrategy.Parse(DerivationStrategy, networks.BTC); + } + } +#pragma warning restore CS0618 + } + + internal void SetDerivationStrategies(IEnumerable derivationStrategies) + { + JObject obj = new JObject(); + foreach(var strat in derivationStrategies) + { + obj.Add(strat.Network.CryptoCode, new JValue(strat.DerivationStrategyBase.ToString())); +#pragma warning disable CS0618 + if (strat.Network.IsBTC) + DerivationStrategy = strat.DerivationStrategyBase.ToString(); + } + DerivationStrategies = JsonConvert.SerializeObject(obj); +#pragma warning restore CS0618 + } + public string Status { get; @@ -424,7 +477,7 @@ namespace BTCPayServer.Services.Invoices public Money TxFee { get; set; } [JsonProperty(PropertyName = "depositAddress")] public string DepositAddress { get; set; } - + public CryptoDataAccounting Calculate() { var cryptoData = ParentEntity.GetCryptoData(); @@ -464,7 +517,7 @@ namespace BTCPayServer.Services.Invoices accounting.NetworkFee = TxFee * txCount; return accounting; } - + } public class AccountedPaymentEntity diff --git a/BTCPayServer/Services/Invoices/InvoiceRepository.cs b/BTCPayServer/Services/Invoices/InvoiceRepository.cs index c9c64c836..b8572383b 100644 --- a/BTCPayServer/Services/Invoices/InvoiceRepository.cs +++ b/BTCPayServer/Services/Invoices/InvoiceRepository.cs @@ -185,7 +185,7 @@ namespace BTCPayServer.Services.Invoices currencyData.DepositAddress = bitcoinAddress.ToString(); #pragma warning disable CS0618 - if (network.CryptoCode == "BTC") + if (network.IsBTC) { invoiceEntity.DepositAddress = currencyData.DepositAddress; } diff --git a/BTCPayServer/Services/Invoices/InvoiceWatcher.cs b/BTCPayServer/Services/Invoices/InvoiceWatcher.cs index 7672bf0e5..ed375a7a9 100644 --- a/BTCPayServer/Services/Invoices/InvoiceWatcher.cs +++ b/BTCPayServer/Services/Invoices/InvoiceWatcher.cs @@ -26,7 +26,6 @@ namespace BTCPayServer.Services.Invoices { InvoiceRepository _InvoiceRepository; ExplorerClient _ExplorerClient; - DerivationStrategyFactory _DerivationFactory; EventAggregator _EventAggregator; BTCPayWallet _Wallet; BTCPayNetworkProvider _NetworkProvider; @@ -42,7 +41,6 @@ namespace BTCPayServer.Services.Invoices PollInterval = explorerClient.Network == Network.RegTest ? TimeSpan.FromSeconds(10.0) : TimeSpan.FromMinutes(1.0); _Wallet = wallet ?? throw new ArgumentNullException(nameof(wallet)); _ExplorerClient = explorerClient ?? throw new ArgumentNullException(nameof(explorerClient)); - _DerivationFactory = new DerivationStrategyFactory(_ExplorerClient.Network); _InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository)); _EventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator)); _NetworkProvider = networkProvider; @@ -125,8 +123,8 @@ namespace BTCPayServer.Services.Invoices { bool needSave = false; //Fetch unknown payments - var strategy = _DerivationFactory.Parse(invoice.DerivationStrategy); - changes = await _ExplorerClient.SyncAsync(strategy, changes, !LongPollingMode, _Cts.Token).ConfigureAwait(false); + var strategy = invoice.GetDerivationStrategies(_NetworkProvider).First(s => s.Network.IsBTC); + changes = await _ExplorerClient.SyncAsync(strategy.DerivationStrategyBase, changes, !LongPollingMode, _Cts.Token).ConfigureAwait(false); var utxos = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).ToArray(); @@ -192,7 +190,7 @@ namespace BTCPayServer.Services.Invoices needSave = true; if (dirtyAddress) { - var address = await _Wallet.ReserveAddressAsync(_DerivationFactory.Parse(invoice.DerivationStrategy)); + var address = await _Wallet.ReserveAddressAsync(strategy); Logs.PayServer.LogInformation("Generate new " + address); await _InvoiceRepository.NewAddress(invoice.Id, address, network); } diff --git a/BTCPayServer/Services/Wallets/BTCPayWallet.cs b/BTCPayServer/Services/Wallets/BTCPayWallet.cs index 8c0736c7e..d9ed20bb8 100644 --- a/BTCPayServer/Services/Wallets/BTCPayWallet.cs +++ b/BTCPayServer/Services/Wallets/BTCPayWallet.cs @@ -28,15 +28,15 @@ namespace BTCPayServer.Services.Wallets } - public async Task ReserveAddressAsync(DerivationStrategyBase derivationStrategy) + public async Task ReserveAddressAsync(DerivationStrategy derivationStrategy) { - var pathInfo = await _Client.GetUnusedAsync(derivationStrategy, DerivationFeature.Deposit, 0, true).ConfigureAwait(false); + var pathInfo = await _Client.GetUnusedAsync(derivationStrategy.DerivationStrategyBase, DerivationFeature.Deposit, 0, true).ConfigureAwait(false); return pathInfo.ScriptPubKey.GetDestinationAddress(_Client.Network); } - public async Task TrackAsync(DerivationStrategyBase derivationStrategy) + public async Task TrackAsync(DerivationStrategy derivationStrategy) { - await _Client.TrackAsync(derivationStrategy); + await _Client.TrackAsync(derivationStrategy.DerivationStrategyBase); } private byte[] ToBytes(T obj) @@ -50,9 +50,9 @@ namespace BTCPayServer.Services.Wallets return Task.WhenAll(tasks); } - public async Task GetBalance(DerivationStrategyBase derivationStrategy) + public async Task GetBalance(DerivationStrategy derivationStrategy) { - var result = await _Client.SyncAsync(derivationStrategy, null, true); + var result = await _Client.SyncAsync(derivationStrategy.DerivationStrategyBase, null, true); return result.Confirmed.UTXOs.Select(u => u.Value) .Concat(result.Unconfirmed.UTXOs.Select(u => u.Value)) .Sum(); diff --git a/BTCPayServer/Views/Stores/ListStores.cshtml b/BTCPayServer/Views/Stores/ListStores.cshtml index 02dfe2227..cbaa87c70 100644 --- a/BTCPayServer/Views/Stores/ListStores.cshtml +++ b/BTCPayServer/Views/Stores/ListStores.cshtml @@ -27,7 +27,7 @@ Name Website - Balance + Balances Actions @@ -42,7 +42,16 @@ @store.WebSite } - @store.Balance + + @for(int i = 0; i < store.Balances.Length; i++) + { + @store.Balances[i] + if(i != store.Balances.Length - 1) + { +
+ } + } + Settings - Remove }