From bd703122023b044283eac8227ab09ef28f655922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Manuel=20Nieto=20=28SuperJMN=29?= Date: Thu, 26 Dec 2024 23:24:40 +0100 Subject: [PATCH] Ability to create Wallet --- src/Angor.Avalonia.sln | 10 +- .../AngorApp.Model/AngorApp.Model.csproj | 2 +- .../AngorApp.Model/BitcoinAddressValidator.cs | 269 ++++++++++++++++++ .../AngorApp.Model/BitcoinNetwork.cs | 0 .../Avalonia}/AngorApp.Model/Destination.cs | 4 +- .../AngorApp.Model/IBroadcastedTransaction.cs | 2 +- .../Avalonia}/AngorApp.Model/IProject.cs | 0 .../Avalonia}/AngorApp.Model/IStage.cs | 2 +- .../AngorApp.Model/IUnsignedTransaction.cs | 2 +- .../Avalonia}/AngorApp.Model/IWallet.cs | 4 +- .../Avalonia/AngorApp.Model/IWalletFactory.cs | 11 + .../AngorApp.Model/IWalletProvider.cs | 10 + src/Angor/Avalonia/AngorApp/AngorApp.csproj | 2 +- src/Angor/Avalonia/AngorApp/App.axaml | 2 + .../AngorApp/Common/Success/SuccessView.axaml | 5 +- .../Common/Success/SuccessView.axaml.cs | 6 +- .../Common/Success/SuccessViewModel.cs | 2 +- ...iew.cs => ITransactionPreviewViewModel.cs} | 2 +- .../Common/TransactionPreview/Summary.axaml | 56 ++++ .../TransactionPreview/Summary.axaml.cs} | 6 +- .../TransactionPreviewView.axaml | 50 +--- .../TransactionPreviewViewModel.cs | 2 +- ...s => TransactionPreviewViewModelDesign.cs} | 22 +- .../Avalonia/AngorApp/CompositionRoot.cs | 14 +- .../AngorApp/Controls/AmountControl.axaml | 69 +++++ .../AngorApp/Controls/AmountControl.axaml.cs | 136 +++++++++ .../Avalonia/AngorApp/Controls/Dialog.axaml | 14 +- .../AngorApp/Controls/IconButton.axaml | 22 +- .../AngorApp/Controls/MiscConverters.cs | 6 + ...owseView.axaml => BrowseSectionView.axaml} | 6 +- .../Browse/BrowseSectionView.axaml.cs | 9 + ...ViewModel.cs => BrowseSectionViewModel.cs} | 9 +- .../Browse/BrowseSectionViewModelDesign.cs | 18 ++ .../Sections/Browse/BrowseView.axaml.cs | 9 - .../Sections/Browse/BrowseViewModelDesign.cs | 28 -- .../Sections/Browse/Details/INostrRelay.cs | 6 + .../Details/IProjectDetailsViewModel.cs | 8 - .../Details/Invest/Amount/AmountView.axaml | 6 +- .../Details/Invest/Amount/AmountViewModel.cs | 3 +- ...odelDesign.cs => AmountViewModelDesign.cs} | 3 +- .../Details/Invest/Amount/IAmountViewModel.cs | 2 +- .../Browse/Details/NostProperties.axaml | 11 +- .../Browse/Details/NostrRelayDesign.cs | 6 + .../Browse/Details/ProjectDetailsViewModel.cs | 6 +- .../Details/ProjectDetailsViewModelDesign.cs | 22 -- .../AngorApp/Sections/Browse/Details/Stage.cs | 9 + ...iewModel.cs => IBrowseSectionViewModel.cs} | 2 +- .../Browse/{IProject.cs => ProjectDesign.cs} | 8 +- .../Sections/Browse/ProjectViewModel.cs | 5 +- .../AngorApp/Sections/Browse/StageDesign.cs | 2 +- .../Browse/TestNotificationService.cs | 13 + ...derView.axaml => FounderSectionView.axaml} | 6 +- .../Founder/FounderSectionView.axaml.cs | 9 + ...iewModel.cs => FounderSectionViewModel.cs} | 4 +- .../Sections/Founder/FounderView.axaml.cs | 9 - .../Founder/IFounderSectionViewModel.cs | 3 + .../Sections/Founder/IFounderViewModel.cs | 3 - .../{HomeView.axaml => HomeSectionView.axaml} | 2 +- ...View.axaml.cs => HomeSectionView.axaml.cs} | 4 +- .../Sections/Home/HomeSectionViewModel.cs | 3 + .../AngorApp/Sections/Home/HomeViewModel.cs | 6 - ...Model.cs => IPortfolioSectionViewModel.cs} | 2 +- ...oView.axaml => PortfolioSectionView.axaml} | 6 +- .../Portfolio/PortfolioSectionView.axaml.cs | 9 + ...Design.cs => PortfolioSectionViewModel.cs} | 4 +- ....cs => PortfolioSectionViewModelDesign.cs} | 4 +- .../Sections/Portfolio/PortfolioView.cs | 9 - .../Sections/Shell/MainViewModelDesign.cs | 2 +- .../Wallet/BroadcastedTransactionDesign.cs | 2 +- .../Wallet/IWalletSectionViewModel.cs | 11 + .../Wallet/{ => Operate}/Balance.axaml | 9 +- .../Wallet/{ => Operate}/Balance.axaml.cs | 2 +- .../Wallet/{ => Operate}/HistoryView.axaml | 8 +- .../Wallet/{ => Operate}/HistoryView.axaml.cs | 2 +- .../Wallet/Operate/IWalletViewModel.cs | 10 + .../Wallet/{ => Operate}/Receive.axaml | 6 +- .../Sections/Wallet/Operate/Receive.axaml.cs | 9 + .../Operate/Send/AddressAndAmountView.axaml | 35 +++ .../Send/AddressAndAmountView.axaml.cs | 6 +- .../Send/AddressAndAmountViewModel.cs | 15 +- .../Send/IAddressAndAmountViewModel.cs | 9 + .../UnsignedTransactionDesign.cs} | 8 +- .../Wallet/{ => Operate}/WalletDesign.cs | 16 +- .../Wallet/{ => Operate}/WalletView.axaml | 13 +- .../Wallet/{ => Operate}/WalletView.axaml.cs | 2 +- .../Wallet/{ => Operate}/WalletViewModel.cs | 21 +- .../Wallet/Operate/WalletViewModelDesign.cs | 10 + .../Wallet/Send/AddressAndAmountView.axaml | 42 --- .../Sections/Wallet/WalletFactoryDesign.cs | 30 ++ .../Sections/Wallet/WalletProviderDesign.cs | 20 ++ .../Sections/Wallet/WalletSectionView.axaml | 29 ++ .../Wallet/WalletSectionView.axaml.cs | 9 + .../Sections/Wallet/WalletSectionViewModel.cs | 27 ++ src/Angor/Avalonia/AngorApp/Styles.axaml | 16 +- src/Angor/Avalonia/Directory.Packages.props | 7 +- src/AngorApp.Model/BitcoinAddressValidator.cs | 178 ------------ 96 files changed, 1028 insertions(+), 532 deletions(-) rename src/{ => Angor/Avalonia}/AngorApp.Model/AngorApp.Model.csproj (76%) create mode 100644 src/Angor/Avalonia/AngorApp.Model/BitcoinAddressValidator.cs rename src/{ => Angor/Avalonia}/AngorApp.Model/BitcoinNetwork.cs (100%) rename src/{ => Angor/Avalonia}/AngorApp.Model/Destination.cs (67%) rename src/{ => Angor/Avalonia}/AngorApp.Model/IBroadcastedTransaction.cs (90%) rename src/{ => Angor/Avalonia}/AngorApp.Model/IProject.cs (100%) rename src/{ => Angor/Avalonia}/AngorApp.Model/IStage.cs (83%) rename src/{ => Angor/Avalonia}/AngorApp.Model/IUnsignedTransaction.cs (79%) rename src/{ => Angor/Avalonia}/AngorApp.Model/IWallet.cs (66%) create mode 100644 src/Angor/Avalonia/AngorApp.Model/IWalletFactory.cs create mode 100644 src/Angor/Avalonia/AngorApp.Model/IWalletProvider.cs rename src/Angor/Avalonia/AngorApp/Common/TransactionPreview/{ITransactionPreview.cs => ITransactionPreviewViewModel.cs} (92%) create mode 100644 src/Angor/Avalonia/AngorApp/Common/TransactionPreview/Summary.axaml rename src/Angor/Avalonia/AngorApp/{Sections/Wallet/Receive.axaml.cs => Common/TransactionPreview/Summary.axaml.cs} (51%) rename src/Angor/Avalonia/AngorApp/Common/TransactionPreview/{ITransactionViewModelPreviewDesign.cs => TransactionPreviewViewModelDesign.cs} (50%) create mode 100644 src/Angor/Avalonia/AngorApp/Controls/AmountControl.axaml create mode 100644 src/Angor/Avalonia/AngorApp/Controls/AmountControl.axaml.cs rename src/Angor/Avalonia/AngorApp/Sections/Browse/{BrowseView.axaml => BrowseSectionView.axaml} (94%) create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseSectionView.axaml.cs rename src/Angor/Avalonia/AngorApp/Sections/Browse/{BrowseViewModel.cs => BrowseSectionViewModel.cs} (60%) create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseSectionViewModelDesign.cs delete mode 100644 src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseView.axaml.cs delete mode 100644 src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseViewModelDesign.cs create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Browse/Details/INostrRelay.cs rename src/Angor/Avalonia/AngorApp/Sections/Browse/Details/Invest/Amount/{InvestViewModelDesign.cs => AmountViewModelDesign.cs} (84%) create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Browse/Details/NostrRelayDesign.cs create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Browse/Details/Stage.cs rename src/Angor/Avalonia/AngorApp/Sections/Browse/{IBrowseViewModel.cs => IBrowseSectionViewModel.cs} (80%) rename src/Angor/Avalonia/AngorApp/Sections/Browse/{IProject.cs => ProjectDesign.cs} (82%) create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Browse/TestNotificationService.cs rename src/Angor/Avalonia/AngorApp/Sections/Founder/{FounderView.axaml => FounderSectionView.axaml} (92%) create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Founder/FounderSectionView.axaml.cs rename src/Angor/Avalonia/AngorApp/Sections/Founder/{FounderViewModel.cs => FounderSectionViewModel.cs} (73%) delete mode 100644 src/Angor/Avalonia/AngorApp/Sections/Founder/FounderView.axaml.cs create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Founder/IFounderSectionViewModel.cs delete mode 100644 src/Angor/Avalonia/AngorApp/Sections/Founder/IFounderViewModel.cs rename src/Angor/Avalonia/AngorApp/Sections/Home/{HomeView.axaml => HomeSectionView.axaml} (94%) rename src/Angor/Avalonia/AngorApp/Sections/Home/{HomeView.axaml.cs => HomeSectionView.axaml.cs} (61%) create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Home/HomeSectionViewModel.cs delete mode 100644 src/Angor/Avalonia/AngorApp/Sections/Home/HomeViewModel.cs rename src/Angor/Avalonia/AngorApp/Sections/Portfolio/{IPortfolioViewModel.cs => IPortfolioSectionViewModel.cs} (68%) rename src/Angor/Avalonia/AngorApp/Sections/Portfolio/{PorfolioView.axaml => PortfolioSectionView.axaml} (93%) create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Portfolio/PortfolioSectionView.axaml.cs rename src/Angor/Avalonia/AngorApp/Sections/Portfolio/{PortfolioViewModelDesign.cs => PortfolioSectionViewModel.cs} (74%) rename src/Angor/Avalonia/AngorApp/Sections/Portfolio/{PortfolioViewModel.cs => PortfolioSectionViewModelDesign.cs} (75%) delete mode 100644 src/Angor/Avalonia/AngorApp/Sections/Portfolio/PortfolioView.cs create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Wallet/IWalletSectionViewModel.cs rename src/Angor/Avalonia/AngorApp/Sections/Wallet/{ => Operate}/Balance.axaml (73%) rename src/Angor/Avalonia/AngorApp/Sections/Wallet/{ => Operate}/Balance.axaml.cs (71%) rename src/Angor/Avalonia/AngorApp/Sections/Wallet/{ => Operate}/HistoryView.axaml (87%) rename src/Angor/Avalonia/AngorApp/Sections/Wallet/{ => Operate}/HistoryView.axaml.cs (73%) create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Wallet/Operate/IWalletViewModel.cs rename src/Angor/Avalonia/AngorApp/Sections/Wallet/{ => Operate}/Receive.axaml (87%) create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Wallet/Operate/Receive.axaml.cs create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Wallet/Operate/Send/AddressAndAmountView.axaml rename src/Angor/Avalonia/AngorApp/Sections/Wallet/{ => Operate}/Send/AddressAndAmountView.axaml.cs (55%) rename src/Angor/Avalonia/AngorApp/Sections/Wallet/{ => Operate}/Send/AddressAndAmountViewModel.cs (64%) create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Wallet/Operate/Send/IAddressAndAmountViewModel.cs rename src/Angor/Avalonia/AngorApp/Sections/Wallet/{TransactionDesign.cs => Operate/UnsignedTransactionDesign.cs} (75%) rename src/Angor/Avalonia/AngorApp/Sections/Wallet/{ => Operate}/WalletDesign.cs (70%) rename src/Angor/Avalonia/AngorApp/Sections/Wallet/{ => Operate}/WalletView.axaml (82%) rename src/Angor/Avalonia/AngorApp/Sections/Wallet/{ => Operate}/WalletView.axaml.cs (72%) rename src/Angor/Avalonia/AngorApp/Sections/Wallet/{ => Operate}/WalletViewModel.cs (65%) create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Wallet/Operate/WalletViewModelDesign.cs delete mode 100644 src/Angor/Avalonia/AngorApp/Sections/Wallet/Send/AddressAndAmountView.axaml create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Wallet/WalletFactoryDesign.cs create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Wallet/WalletProviderDesign.cs create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Wallet/WalletSectionView.axaml create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Wallet/WalletSectionView.axaml.cs create mode 100644 src/Angor/Avalonia/AngorApp/Sections/Wallet/WalletSectionViewModel.cs delete mode 100644 src/AngorApp.Model/BitcoinAddressValidator.cs diff --git a/src/Angor.Avalonia.sln b/src/Angor.Avalonia.sln index f8cb6c45..e5ea45fe 100644 --- a/src/Angor.Avalonia.sln +++ b/src/Angor.Avalonia.sln @@ -12,7 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Angor\Avalonia\Directory.Packages.props = Angor\Avalonia\Directory.Packages.props EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AngorApp.Model", "AngorApp.Model\AngorApp.Model.csproj", "{5FEB5D7F-A9B3-47C7-B343-0126B90888E6}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AngorApp.Model", "Angor\Avalonia\AngorApp.Model\AngorApp.Model.csproj", "{AD095B93-61C9-46AF-9EB5-403D1C9EB29E}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -28,10 +28,10 @@ Global {B0BFAC0F-9F74-4770-9B12-A3A478DB0A71}.Debug|Any CPU.Build.0 = Debug|Any CPU {B0BFAC0F-9F74-4770-9B12-A3A478DB0A71}.Release|Any CPU.ActiveCfg = Release|Any CPU {B0BFAC0F-9F74-4770-9B12-A3A478DB0A71}.Release|Any CPU.Build.0 = Release|Any CPU - {5FEB5D7F-A9B3-47C7-B343-0126B90888E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5FEB5D7F-A9B3-47C7-B343-0126B90888E6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5FEB5D7F-A9B3-47C7-B343-0126B90888E6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5FEB5D7F-A9B3-47C7-B343-0126B90888E6}.Release|Any CPU.Build.0 = Release|Any CPU + {AD095B93-61C9-46AF-9EB5-403D1C9EB29E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD095B93-61C9-46AF-9EB5-403D1C9EB29E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD095B93-61C9-46AF-9EB5-403D1C9EB29E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD095B93-61C9-46AF-9EB5-403D1C9EB29E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/AngorApp.Model/AngorApp.Model.csproj b/src/Angor/Avalonia/AngorApp.Model/AngorApp.Model.csproj similarity index 76% rename from src/AngorApp.Model/AngorApp.Model.csproj rename to src/Angor/Avalonia/AngorApp.Model/AngorApp.Model.csproj index c30299db..3c992aa0 100644 --- a/src/AngorApp.Model/AngorApp.Model.csproj +++ b/src/Angor/Avalonia/AngorApp.Model/AngorApp.Model.csproj @@ -7,7 +7,7 @@ - + diff --git a/src/Angor/Avalonia/AngorApp.Model/BitcoinAddressValidator.cs b/src/Angor/Avalonia/AngorApp.Model/BitcoinAddressValidator.cs new file mode 100644 index 00000000..fb8da982 --- /dev/null +++ b/src/Angor/Avalonia/AngorApp.Model/BitcoinAddressValidator.cs @@ -0,0 +1,269 @@ +namespace AngorApp.Model; + +using System.Numerics; +using System.Security.Cryptography; + +public class BitcoinAddressValidator +{ + public enum BitcoinAddressType + { + Unknown, + P2PKH, // Pay to Public Key Hash (Legacy) + P2SH, // Pay to Script Hash (Legacy) + P2WPKH, // Pay to Witness Public Key Hash (Native SegWit) + P2WSH // Pay to Witness Script Hash (Native SegWit) + } + + private static readonly string Base58Alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + private static readonly string Bech32Alphabet = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; + + public static ValidationResult ValidateBitcoinAddress(string address, BitcoinNetwork expectedNetwork) + { + if (string.IsNullOrEmpty(address)) + { + return new ValidationResult(false, "Address cannot be empty"); + } + + // Check if it's a Bech32 address + if (address.StartsWith("bc1") || address.StartsWith("tb1")) + { + return ValidateBech32Address(address, expectedNetwork); + } + + // Legacy or P2SH address validation + return ValidateLegacyAddress(address, expectedNetwork); + } + + private static ValidationResult ValidateLegacyAddress(string address, BitcoinNetwork expectedNetwork) + { + // Validate length for legacy addresses + if (address.Length < 26 || address.Length > 35) + { + return new ValidationResult(false, "Invalid legacy address length"); + } + + // Validate characters + var invalidChars = address.Where(c => !Base58Alphabet.Contains(c)).ToList(); + if (invalidChars.Any()) + { + return new ValidationResult(false, + $"Invalid characters in address: {string.Join(", ", invalidChars)}"); + } + + try + { + // Decode Base58 + var decoded = Base58Decode(address); + + // Validate decoded length + if (decoded.Length != 25) + { + return new ValidationResult(false, + $"Invalid decoded length: {decoded.Length} bytes (expected 25 bytes)"); + } + + // Separate components + var withoutChecksum = decoded.Take(21).ToArray(); + var checksum = decoded.Skip(21).Take(4).ToArray(); + + // Calculate checksum + var calculatedChecksum = CalculateChecksum(withoutChecksum); + + // Compare checksums + if (!checksum.SequenceEqual(calculatedChecksum)) + { + return new ValidationResult(false, "Invalid checksum"); + } + + // Identify version and network + var version = decoded[0]; + var (detectedNetwork, addressType) = DetermineNetworkAndType(version); + + if (detectedNetwork == BitcoinNetwork.Unknown) + { + return new ValidationResult(false, + $"Unrecognized version byte: 0x{version:X2}", + detectedNetwork, + addressType); + } + + // Validate network + if (expectedNetwork != detectedNetwork) + { + return new ValidationResult(false, + $"Address belongs to network {detectedNetwork} but expected {expectedNetwork}", + detectedNetwork, + addressType); + } + + return new ValidationResult(true, "Valid address", detectedNetwork, addressType); + } + catch (Exception ex) + { + return new ValidationResult(false, $"Error processing address: {ex.Message}"); + } + } + + private static ValidationResult ValidateBech32Address(string address, BitcoinNetwork expectedNetwork) + { + try + { + // Basic Bech32 validation + if (address.Length < 14 || address.Length > 74) + { + return new ValidationResult(false, "Invalid Bech32 address length"); + } + + // Validate characters + var invalidChars = address.ToLower() + .Skip(4) // Skip the prefix (bc1 or tb1) and separator (1) + .Where(c => !Bech32Alphabet.Contains(c)) + .ToList(); + + if (invalidChars.Any()) + { + return new ValidationResult(false, + $"Invalid characters in Bech32 address: {string.Join(", ", invalidChars)}"); + } + + // Determine network from prefix + var detectedNetwork = address.ToLower().StartsWith("bc1") + ? BitcoinNetwork.Mainnet + : address.ToLower().StartsWith("tb1") + ? BitcoinNetwork.Testnet + : BitcoinNetwork.Unknown; + + if (detectedNetwork == BitcoinNetwork.Unknown) + { + return new ValidationResult(false, + "Invalid Bech32 address prefix", + BitcoinNetwork.Unknown, + BitcoinAddressType.Unknown); + } + + // Validate network + if (expectedNetwork != detectedNetwork) + { + return new ValidationResult(false, + $"Address belongs to network {detectedNetwork} but expected {expectedNetwork}", + detectedNetwork, + BitcoinAddressType.Unknown); + } + + // Determine SegWit version and program length + var program = DecodeBech32(address); + var witnessVersion = program[0]; + var dataLength = program.Length - 1; + + // Validate according to BIP141 + var addressType = DetermineSegWitType(witnessVersion, dataLength); + if (addressType == BitcoinAddressType.Unknown) + { + return new ValidationResult(false, + "Invalid SegWit program length", + detectedNetwork, + BitcoinAddressType.Unknown); + } + + return new ValidationResult(true, "Valid SegWit address", detectedNetwork, addressType); + } + catch (Exception ex) + { + return new ValidationResult(false, $"Error processing Bech32 address: {ex.Message}"); + } + } + + private static BitcoinAddressType DetermineSegWitType(int witnessVersion, int programLength) + { + if (witnessVersion != 0) return BitcoinAddressType.Unknown; + + return programLength switch + { + 20 => BitcoinAddressType.P2WPKH, + 32 => BitcoinAddressType.P2WSH, + _ => BitcoinAddressType.Unknown + }; + } + + private static (BitcoinNetwork network, BitcoinAddressType type) DetermineNetworkAndType(byte version) + { + return version switch + { + 0x00 => (BitcoinNetwork.Mainnet, BitcoinAddressType.P2PKH), // Mainnet P2PKH (1...) + 0x05 => (BitcoinNetwork.Mainnet, BitcoinAddressType.P2SH), // Mainnet P2SH (3...) + 0x6F => (BitcoinNetwork.Testnet, BitcoinAddressType.P2PKH), // Testnet P2PKH (m... or n...) + 0xC4 => (BitcoinNetwork.Testnet, BitcoinAddressType.P2SH), // Testnet P2SH (2...) + 0x3C => (BitcoinNetwork.Regtest, BitcoinAddressType.P2PKH), // Regtest P2PKH + 0x26 => (BitcoinNetwork.Regtest, BitcoinAddressType.P2SH), // Regtest P2SH + _ => (BitcoinNetwork.Unknown, BitcoinAddressType.Unknown) + }; + } + + private static byte[] Base58Decode(string base58) + { + var result = new BigInteger(0); + var multiplier = new BigInteger(1); + + // Convert from Base58 to number + for (var i = base58.Length - 1; i >= 0; i--) + { + var digit = Base58Alphabet.IndexOf(base58[i]); + if (digit == -1) + { + throw new FormatException($"Invalid character '{base58[i]}' at position {i}"); + } + + result += multiplier * digit; + multiplier *= Base58Alphabet.Length; + } + + // Convert to bytes + var bytes = result.ToByteArray().Reverse().SkipWhile(b => b == 0).ToArray(); + + // Add leading zeros as needed + var leadingZeros = base58.TakeWhile(c => c == '1').Count(); + var leadingZeroBytes = Enumerable.Repeat((byte)0, leadingZeros).ToArray(); + + return leadingZeroBytes.Concat(bytes).ToArray(); + } + + private static byte[] DecodeBech32(string address) + { + // Note: This is a simplified Bech32 decoder + // In a production environment, you should implement full Bech32 decoding + // including checksum verification according to BIP173 + var data = address.ToLower() + .Skip(4) // Skip prefix and separator + .Select(c => (byte)Bech32Alphabet.IndexOf(c)) + .ToArray(); + + return data; + } + + private static byte[] CalculateChecksum(byte[] data) + { + using (var sha256 = SHA256.Create()) + { + var hash1 = sha256.ComputeHash(data); + var hash2 = sha256.ComputeHash(hash1); + return hash2.Take(4).ToArray(); + } + } + + public class ValidationResult + { + public ValidationResult(bool isValid, string message, BitcoinNetwork network = BitcoinNetwork.Unknown, + BitcoinAddressType addressType = BitcoinAddressType.Unknown) + { + IsValid = isValid; + Message = message; + Network = network; + AddressType = addressType; + } + + public bool IsValid { get; } + public string Message { get; } + public BitcoinNetwork Network { get; init; } + public BitcoinAddressType AddressType { get; init; } + } +} \ No newline at end of file diff --git a/src/AngorApp.Model/BitcoinNetwork.cs b/src/Angor/Avalonia/AngorApp.Model/BitcoinNetwork.cs similarity index 100% rename from src/AngorApp.Model/BitcoinNetwork.cs rename to src/Angor/Avalonia/AngorApp.Model/BitcoinNetwork.cs diff --git a/src/AngorApp.Model/Destination.cs b/src/Angor/Avalonia/AngorApp.Model/Destination.cs similarity index 67% rename from src/AngorApp.Model/Destination.cs rename to src/Angor/Avalonia/AngorApp.Model/Destination.cs index a3c8a1de..671d7f98 100644 --- a/src/AngorApp.Model/Destination.cs +++ b/src/Angor/Avalonia/AngorApp.Model/Destination.cs @@ -2,7 +2,7 @@ namespace AngorApp.Model; public class Destination { - public Destination(string name, decimal amount, string bitcoinAddress) + public Destination(string name, ulong amount, string bitcoinAddress) { Name = name; Amount = amount; @@ -10,6 +10,6 @@ public Destination(string name, decimal amount, string bitcoinAddress) } public string Name { get; } - public decimal Amount { get; } + public ulong Amount { get; } public string BitcoinAddress { get; } } \ No newline at end of file diff --git a/src/AngorApp.Model/IBroadcastedTransaction.cs b/src/Angor/Avalonia/AngorApp.Model/IBroadcastedTransaction.cs similarity index 90% rename from src/AngorApp.Model/IBroadcastedTransaction.cs rename to src/Angor/Avalonia/AngorApp.Model/IBroadcastedTransaction.cs index 28015d12..7883e8f8 100644 --- a/src/AngorApp.Model/IBroadcastedTransaction.cs +++ b/src/Angor/Avalonia/AngorApp.Model/IBroadcastedTransaction.cs @@ -7,7 +7,7 @@ public interface IBroadcastedTransaction public string Address { get; } public decimal FeeRate { get; set; } public decimal TotalFee { get; set; } - public decimal Amount { get; } + public uint Amount { get; } public string Path { get; } public int UtxoCount { get; } public string ViewRawJson { get; } diff --git a/src/AngorApp.Model/IProject.cs b/src/Angor/Avalonia/AngorApp.Model/IProject.cs similarity index 100% rename from src/AngorApp.Model/IProject.cs rename to src/Angor/Avalonia/AngorApp.Model/IProject.cs diff --git a/src/AngorApp.Model/IStage.cs b/src/Angor/Avalonia/AngorApp.Model/IStage.cs similarity index 83% rename from src/AngorApp.Model/IStage.cs rename to src/Angor/Avalonia/AngorApp.Model/IStage.cs index a48c0cb9..225d7495 100644 --- a/src/AngorApp.Model/IStage.cs +++ b/src/Angor/Avalonia/AngorApp.Model/IStage.cs @@ -3,7 +3,7 @@ namespace AngorApp.Model; public interface IStage { DateOnly ReleaseDate { get; } - decimal Amount { get; } + uint Amount { get; } int Index { get; } double Weight { get; } } \ No newline at end of file diff --git a/src/AngorApp.Model/IUnsignedTransaction.cs b/src/Angor/Avalonia/AngorApp.Model/IUnsignedTransaction.cs similarity index 79% rename from src/AngorApp.Model/IUnsignedTransaction.cs rename to src/Angor/Avalonia/AngorApp.Model/IUnsignedTransaction.cs index 58734027..1092847d 100644 --- a/src/AngorApp.Model/IUnsignedTransaction.cs +++ b/src/Angor/Avalonia/AngorApp.Model/IUnsignedTransaction.cs @@ -4,6 +4,6 @@ namespace AngorApp.Model; public interface IUnsignedTransaction { - public decimal TotalFee { get; set; } + public ulong TotalFee { get; set; } Task> Broadcast(); } \ No newline at end of file diff --git a/src/AngorApp.Model/IWallet.cs b/src/Angor/Avalonia/AngorApp.Model/IWallet.cs similarity index 66% rename from src/AngorApp.Model/IWallet.cs rename to src/Angor/Avalonia/AngorApp.Model/IWallet.cs index c02d9696..1d556cb3 100644 --- a/src/AngorApp.Model/IWallet.cs +++ b/src/Angor/Avalonia/AngorApp.Model/IWallet.cs @@ -5,9 +5,9 @@ namespace AngorApp.Model; public interface IWallet { public IEnumerable History { get; } - decimal? Balance { get; set; } + ulong? Balance { get; set; } public BitcoinNetwork Network { get; } public string ReceiveAddress { get; } - Task> CreateTransaction(decimal amount, string address, decimal feerate); + Task> CreateTransaction(ulong amount, string address, ulong feerate); Result IsAddressValid(string address); } \ No newline at end of file diff --git a/src/Angor/Avalonia/AngorApp.Model/IWalletFactory.cs b/src/Angor/Avalonia/AngorApp.Model/IWalletFactory.cs new file mode 100644 index 00000000..31c24abd --- /dev/null +++ b/src/Angor/Avalonia/AngorApp.Model/IWalletFactory.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using AngorApp.Model; +using CSharpFunctionalExtensions; + +namespace AngorApp.Sections.Wallet.NoWallet; + +public interface IWalletFactory +{ + public Task> Recover(); + public Task> Create(); +} \ No newline at end of file diff --git a/src/Angor/Avalonia/AngorApp.Model/IWalletProvider.cs b/src/Angor/Avalonia/AngorApp.Model/IWalletProvider.cs new file mode 100644 index 00000000..58d4c2b2 --- /dev/null +++ b/src/Angor/Avalonia/AngorApp.Model/IWalletProvider.cs @@ -0,0 +1,10 @@ +using AngorApp.Model; +using CSharpFunctionalExtensions; + +namespace AngorApp.Sections.Wallet.NoWallet; + +public interface IWalletProvider +{ + Maybe GetWallet(); + void SetWallet(IWallet wallet); +} \ No newline at end of file diff --git a/src/Angor/Avalonia/AngorApp/AngorApp.csproj b/src/Angor/Avalonia/AngorApp/AngorApp.csproj index fc018732..2517be8b 100644 --- a/src/Angor/Avalonia/AngorApp/AngorApp.csproj +++ b/src/Angor/Avalonia/AngorApp/AngorApp.csproj @@ -37,7 +37,7 @@ - + diff --git a/src/Angor/Avalonia/AngorApp/App.axaml b/src/Angor/Avalonia/AngorApp/App.axaml index a405b6fe..f517af97 100644 --- a/src/Angor/Avalonia/AngorApp/App.axaml +++ b/src/Angor/Avalonia/AngorApp/App.axaml @@ -20,6 +20,7 @@ + @@ -31,6 +32,7 @@ + diff --git a/src/Angor/Avalonia/AngorApp/Common/Success/SuccessView.axaml b/src/Angor/Avalonia/AngorApp/Common/Success/SuccessView.axaml index dc3b5045..b2d62f7f 100644 --- a/src/Angor/Avalonia/AngorApp/Common/Success/SuccessView.axaml +++ b/src/Angor/Avalonia/AngorApp/Common/Success/SuccessView.axaml @@ -4,9 +4,10 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:avalonia="https://github.com/projektanker/icons.avalonia" xmlns:common="clr-namespace:AngorApp.Common" + xmlns:success="clr-namespace:AngorApp.Common.Success" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" - x:Class="AngorApp.Common.SuccessView" x:DataType="common:SuccessViewModel"> - + x:Class="AngorApp.Common.Success.SuccessView" x:DataType="success:SuccessViewModel"> + diff --git a/src/Angor/Avalonia/AngorApp/Common/Success/SuccessView.axaml.cs b/src/Angor/Avalonia/AngorApp/Common/Success/SuccessView.axaml.cs index 0dcf5931..3b2349de 100644 --- a/src/Angor/Avalonia/AngorApp/Common/Success/SuccessView.axaml.cs +++ b/src/Angor/Avalonia/AngorApp/Common/Success/SuccessView.axaml.cs @@ -1,8 +1,4 @@ -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; - -namespace AngorApp.Common; +namespace AngorApp.Common.Success; public partial class SuccessView : UserControl { diff --git a/src/Angor/Avalonia/AngorApp/Common/Success/SuccessViewModel.cs b/src/Angor/Avalonia/AngorApp/Common/Success/SuccessViewModel.cs index d2369906..d99007d6 100644 --- a/src/Angor/Avalonia/AngorApp/Common/Success/SuccessViewModel.cs +++ b/src/Angor/Avalonia/AngorApp/Common/Success/SuccessViewModel.cs @@ -1,7 +1,7 @@ using System.Reactive.Linq; using Zafiro.Avalonia.Controls.Wizards.Builder; -namespace AngorApp.Common; +namespace AngorApp.Common.Success; public class SuccessViewModel : IStep { diff --git a/src/Angor/Avalonia/AngorApp/Common/TransactionPreview/ITransactionPreview.cs b/src/Angor/Avalonia/AngorApp/Common/TransactionPreview/ITransactionPreviewViewModel.cs similarity index 92% rename from src/Angor/Avalonia/AngorApp/Common/TransactionPreview/ITransactionPreview.cs rename to src/Angor/Avalonia/AngorApp/Common/TransactionPreview/ITransactionPreviewViewModel.cs index 3fb579bd..8a49e1a7 100644 --- a/src/Angor/Avalonia/AngorApp/Common/TransactionPreview/ITransactionPreview.cs +++ b/src/Angor/Avalonia/AngorApp/Common/TransactionPreview/ITransactionPreviewViewModel.cs @@ -11,5 +11,5 @@ public interface ITransactionPreviewViewModel : IStep ReactiveCommand> CreateTransaction { get; } public IObservable TransactionConfirmed { get; } public Destination Destination { get; } - public decimal Feerate { get; set; } + public ulong Feerate { get; set; } } \ No newline at end of file diff --git a/src/Angor/Avalonia/AngorApp/Common/TransactionPreview/Summary.axaml b/src/Angor/Avalonia/AngorApp/Common/TransactionPreview/Summary.axaml new file mode 100644 index 00000000..af4bc451 --- /dev/null +++ b/src/Angor/Avalonia/AngorApp/Common/TransactionPreview/Summary.axaml @@ -0,0 +1,56 @@ + + + + + + + + + This is a preview of the transaction you will send. + If everything is correct, you can confirm it with the button below. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Angor/Avalonia/AngorApp/Sections/Wallet/Receive.axaml.cs b/src/Angor/Avalonia/AngorApp/Common/TransactionPreview/Summary.axaml.cs similarity index 51% rename from src/Angor/Avalonia/AngorApp/Sections/Wallet/Receive.axaml.cs rename to src/Angor/Avalonia/AngorApp/Common/TransactionPreview/Summary.axaml.cs index 64c647cf..d22767d4 100644 --- a/src/Angor/Avalonia/AngorApp/Sections/Wallet/Receive.axaml.cs +++ b/src/Angor/Avalonia/AngorApp/Common/TransactionPreview/Summary.axaml.cs @@ -2,11 +2,11 @@ using Avalonia.Controls; using Avalonia.Markup.Xaml; -namespace AngorApp.Sections.Wallet; +namespace AngorApp.Common.TransactionPreview; -public partial class Receive : UserControl +public partial class Summary : UserControl { - public Receive() + public Summary() { InitializeComponent(); } diff --git a/src/Angor/Avalonia/AngorApp/Common/TransactionPreview/TransactionPreviewView.axaml b/src/Angor/Avalonia/AngorApp/Common/TransactionPreview/TransactionPreviewView.axaml index 983a9f61..756bd17c 100644 --- a/src/Angor/Avalonia/AngorApp/Common/TransactionPreview/TransactionPreviewView.axaml +++ b/src/Angor/Avalonia/AngorApp/Common/TransactionPreview/TransactionPreviewView.axaml @@ -2,25 +2,21 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" - xmlns:wallet="clr-namespace:AngorApp.Sections.Wallet" - xmlns:designTime="clr-namespace:Zafiro.Avalonia.DesignTime;assembly=Zafiro.Avalonia" xmlns:controls="clr-namespace:AngorApp.Controls" xmlns:dp="clr-namespace:AvaloniaProgressRing;assembly=AvaloniaProgressRing" - xmlns:transactionPreview1="clr-namespace:AngorApp.Common.TransactionPreview" - xmlns:f="clr-namespace:Zafiro.Avalonia.Converters;assembly=Zafiro.Avalonia" + xmlns:tp="clr-namespace:AngorApp.Common.TransactionPreview" mc:Ignorable="d" + d:DesignWidth="600" + d:DesignHeight="400" x:Class="AngorApp.Common.TransactionPreview.TransactionPreviewView" - x:DataType="transactionPreview1:ITransactionPreviewViewModel"> + x:DataType="tp:ITransactionPreviewViewModel"> - - - - - + + @@ -29,36 +25,10 @@ - - - This is a preview of the transaction you will send. - If everything is correct you can confirm it with the button below. - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Angor/Avalonia/AngorApp/Controls/MiscConverters.cs b/src/Angor/Avalonia/AngorApp/Controls/MiscConverters.cs index a4a285d8..8fb14eed 100644 --- a/src/Angor/Avalonia/AngorApp/Controls/MiscConverters.cs +++ b/src/Angor/Avalonia/AngorApp/Controls/MiscConverters.cs @@ -61,4 +61,10 @@ public static class MiscConverters Debug.Assert(s != null, nameof(s) + " != null"); return QRGenerator.SvgImageFrom(s); }); + + public static FuncValueConverter SatsToBtc { get; } = new(satoshis => + { + var btc = satoshis / 10000_0000; + return $"{btc:0.0000 0000}" + " BTC"; + }); } \ No newline at end of file diff --git a/src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseView.axaml b/src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseSectionView.axaml similarity index 94% rename from src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseView.axaml rename to src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseSectionView.axaml index 4ed2339d..6687b65e 100644 --- a/src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseView.axaml +++ b/src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseSectionView.axaml @@ -6,11 +6,11 @@ xmlns:i="https://github.com/projektanker/icons.avalonia" xmlns:browse="clr-namespace:AngorApp.Sections.Browse" mc:Ignorable="d" d:DesignWidth="800" - x:Class="AngorApp.Sections.Browse.BrowseView" - x:DataType="browse:IBrowseViewModel" ClipToBounds="False"> + x:Class="AngorApp.Sections.Browse.BrowseSectionView" + x:DataType="browse:IBrowseSectionViewModel" ClipToBounds="False"> - + diff --git a/src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseSectionView.axaml.cs b/src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseSectionView.axaml.cs new file mode 100644 index 00000000..cca67b30 --- /dev/null +++ b/src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseSectionView.axaml.cs @@ -0,0 +1,9 @@ +namespace AngorApp.Sections.Browse; + +public partial class BrowseSectionView : UserControl +{ + public BrowseSectionView() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseViewModel.cs b/src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseSectionViewModel.cs similarity index 60% rename from src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseViewModel.cs rename to src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseSectionViewModel.cs index 6fea95d9..3f90b767 100644 --- a/src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseViewModel.cs +++ b/src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseSectionViewModel.cs @@ -1,16 +1,15 @@ using System.Linq; -using AngorApp.Model; +using AngorApp.Sections.Wallet.NoWallet; using AngorApp.Services; -using CSharpFunctionalExtensions; using Zafiro.Avalonia.Controls.Navigation; namespace AngorApp.Sections.Browse; -public class BrowseViewModel : ReactiveObject, IBrowseViewModel +public class BrowseSectionViewModel : ReactiveObject, IBrowseSectionViewModel { - public BrowseViewModel(Func> getWallet, INavigator navigator, UIServices uiServices) + public BrowseSectionViewModel(IWalletProvider walletProvider, INavigator navigator, UIServices uiServices) { - Projects = SampleData.GetProjects().Select(project => new ProjectViewModel(getWallet, project, navigator, uiServices)).ToList(); + Projects = SampleData.GetProjects().Select(project => new ProjectViewModel(walletProvider, project, navigator, uiServices)).ToList(); OpenHub = ReactiveCommand.CreateFromTask(() => uiServices.LauncherService.LaunchUri(new Uri("https://www.angor.io"))); } diff --git a/src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseSectionViewModelDesign.cs b/src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseSectionViewModelDesign.cs new file mode 100644 index 00000000..6b1bffcc --- /dev/null +++ b/src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseSectionViewModelDesign.cs @@ -0,0 +1,18 @@ +using System.Linq; +using AngorApp.Sections.Shell; +using AngorApp.Sections.Wallet; +using AngorApp.Sections.Wallet.NoWallet; +using AngorApp.Services; + +namespace AngorApp.Sections.Browse; + +public class BrowseSectionViewModelDesign : IBrowseSectionViewModel +{ + public BrowseSectionViewModelDesign() + { + Projects = SampleData.GetProjects().Select(project => new ProjectViewModel(new WalletProviderDesign(), project, null, new UIServices(new NoopLauncherService(), new TestDialog(), new TestNotificationService()))).ToList(); + } + + public IReadOnlyCollection Projects { get; set; } + public ReactiveCommand OpenHub { get; set; } +} \ No newline at end of file diff --git a/src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseView.axaml.cs b/src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseView.axaml.cs deleted file mode 100644 index c82472fc..00000000 --- a/src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseView.axaml.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace AngorApp.Sections.Browse; - -public partial class BrowseView : UserControl -{ - public BrowseView() - { - InitializeComponent(); - } -} \ No newline at end of file diff --git a/src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseViewModelDesign.cs b/src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseViewModelDesign.cs deleted file mode 100644 index a253085a..00000000 --- a/src/Angor/Avalonia/AngorApp/Sections/Browse/BrowseViewModelDesign.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Linq; -using System.Threading.Tasks; -using AngorApp.Sections.Shell; -using AngorApp.Sections.Wallet; -using AngorApp.Services; -using CSharpFunctionalExtensions; -using Zafiro.UI; - -namespace AngorApp.Sections.Browse; - -public class BrowseViewModelDesign : IBrowseViewModel -{ - public BrowseViewModelDesign() - { - Projects = SampleData.GetProjects().Select(project => new ProjectViewModel(() => new WalletDesign(), project, null, new UIServices(new NoopLauncherService(), new TestDialog(), new TestNotificationService()))).ToList(); - } - - public IReadOnlyCollection Projects { get; set; } - public ReactiveCommand OpenHub { get; set; } -} - -public class TestNotificationService : INotificationService -{ - public Task Show(string message, Maybe title) - { - return Task.CompletedTask; - } -} diff --git a/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/INostrRelay.cs b/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/INostrRelay.cs new file mode 100644 index 00000000..44ee0d17 --- /dev/null +++ b/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/INostrRelay.cs @@ -0,0 +1,6 @@ +namespace AngorApp.Sections.Browse.Details; + +public interface INostrRelay +{ + public Uri Uri { get; } +} \ No newline at end of file diff --git a/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/IProjectDetailsViewModel.cs b/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/IProjectDetailsViewModel.cs index 12fe15bd..d85d6478 100644 --- a/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/IProjectDetailsViewModel.cs +++ b/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/IProjectDetailsViewModel.cs @@ -14,12 +14,4 @@ public interface IProjectDetailsViewModel public double CurrentDays { get; } public double CurrentInvestment { get; } public IProject Project { get; } -} - -public class Stage -{ - public int Index { get; set; } - public double Weight { get; set; } - public DateTimeOffset ReleaseDate { get; set; } - public decimal Amount { get; set; } } \ No newline at end of file diff --git a/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/Invest/Amount/AmountView.axaml b/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/Invest/Amount/AmountView.axaml index 59ae5937..50c26c16 100644 --- a/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/Invest/Amount/AmountView.axaml +++ b/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/Invest/Amount/AmountView.axaml @@ -37,15 +37,17 @@ This agreement ensures that in the event the project does not succeed, you will be able to recover your funds. This provides a safety net for your investment, giving you peace of mind that your financial contribution is protected. + - + + - + diff --git a/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/Invest/Amount/AmountViewModel.cs b/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/Invest/Amount/AmountViewModel.cs index c683140f..aeb56a65 100644 --- a/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/Invest/Amount/AmountViewModel.cs +++ b/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/Invest/Amount/AmountViewModel.cs @@ -8,7 +8,7 @@ namespace AngorApp.Sections.Browse.Details.Invest.Amount; public partial class AmountViewModel : ReactiveValidationObject, IAmountViewModel { - [Reactive] private decimal? amount; + [Reactive] private ulong? amount; public AmountViewModel(IWallet wallet, IProject project) { @@ -19,7 +19,6 @@ public AmountViewModel(IWallet wallet, IProject project) } public IProject Project { get; } - public IObservable IsValid => this.IsValid(); public IObservable IsBusy => Observable.Return(false); public bool AutoAdvance => false; diff --git a/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/Invest/Amount/InvestViewModelDesign.cs b/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/Invest/Amount/AmountViewModelDesign.cs similarity index 84% rename from src/Angor/Avalonia/AngorApp/Sections/Browse/Details/Invest/Amount/InvestViewModelDesign.cs rename to src/Angor/Avalonia/AngorApp/Sections/Browse/Details/Invest/Amount/AmountViewModelDesign.cs index b54ebe53..e19b9fde 100644 --- a/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/Invest/Amount/InvestViewModelDesign.cs +++ b/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/Invest/Amount/AmountViewModelDesign.cs @@ -7,7 +7,8 @@ namespace AngorApp.Sections.Browse.Details.Invest.Amount; public class AmountViewModelDesign : IAmountViewModel { public ICommand Next { get; } = ReactiveCommand.Create(() => { }); - public decimal? Amount { get; set; } = 2.00m; + public ulong? Amount { get; set; } = 20000; + public decimal? AmountInBtc { get; set; } public IProject Project { get; } = new ProjectDesign(); public IObservable IsValid => Observable.Return(true); public IObservable IsBusy => Observable.Return(false); diff --git a/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/Invest/Amount/IAmountViewModel.cs b/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/Invest/Amount/IAmountViewModel.cs index 0b9a85f9..01711417 100644 --- a/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/Invest/Amount/IAmountViewModel.cs +++ b/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/Invest/Amount/IAmountViewModel.cs @@ -5,6 +5,6 @@ namespace AngorApp.Sections.Browse.Details.Invest.Amount; public interface IAmountViewModel : IStep { - public decimal? Amount { get; set; } + public ulong? Amount { get; set; } IProject Project { get; } } \ No newline at end of file diff --git a/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/NostProperties.axaml b/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/NostProperties.axaml index f275e127..f602e803 100644 --- a/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/NostProperties.axaml +++ b/src/Angor/Avalonia/AngorApp/Sections/Browse/Details/NostProperties.axaml @@ -4,11 +4,10 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:c="clr-namespace:AngorApp.Controls" xmlns:p="https://github.com/projektanker/icons.avalonia" - xmlns:a="clr-namespace:AngorApp" xmlns:conv="clr-namespace:Zafiro.Avalonia.Converters;assembly=Zafiro.Avalonia" xmlns:misc="clr-namespace:Zafiro.Avalonia.Misc;assembly=Zafiro.Avalonia" xmlns:dt="clr-namespace:AngorApp.Sections.Browse.Details" - mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" + mc:Ignorable="d" d:DesignWidth="650" d:DesignHeight="450" x:Class="AngorApp.Sections.Browse.Details.NostProperties" x:DataType="dt:IProjectDetailsViewModel"> @@ -31,9 +30,7 @@ - - - +