diff --git a/src/Angor.Avalonia.sln b/src/Angor.Avalonia.sln index 99fd8e77..e191f7ae 100644 --- a/src/Angor.Avalonia.sln +++ b/src/Angor.Avalonia.sln @@ -22,6 +22,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Angor.UI.Model.Implementati EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{5B7EE527-242E-4C87-9BB7-AD4E05EABC4C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Angor.Wallet", "Angor\Avalonia\Angor.Wallet\Angor.Wallet.csproj", "{287F370D-D680-4BE5-8577-7A180BD0A0E3}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -52,6 +54,10 @@ Global {B84751C6-1B82-4DE8-9FBE-F0A67D981C43}.Debug|Any CPU.Build.0 = Debug|Any CPU {B84751C6-1B82-4DE8-9FBE-F0A67D981C43}.Release|Any CPU.ActiveCfg = Release|Any CPU {B84751C6-1B82-4DE8-9FBE-F0A67D981C43}.Release|Any CPU.Build.0 = Release|Any CPU + {287F370D-D680-4BE5-8577-7A180BD0A0E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {287F370D-D680-4BE5-8577-7A180BD0A0E3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {287F370D-D680-4BE5-8577-7A180BD0A0E3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {287F370D-D680-4BE5-8577-7A180BD0A0E3}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -64,5 +70,6 @@ Global {539B4D21-F2A9-46A6-84F1-9A51884A62E9} = {5B7EE527-242E-4C87-9BB7-AD4E05EABC4C} {B84751C6-1B82-4DE8-9FBE-F0A67D981C43} = {5B7EE527-242E-4C87-9BB7-AD4E05EABC4C} {700DF0C4-C965-4662-A91F-CD3BE043AC9E} = {5B7EE527-242E-4C87-9BB7-AD4E05EABC4C} + {287F370D-D680-4BE5-8577-7A180BD0A0E3} = {5B7EE527-242E-4C87-9BB7-AD4E05EABC4C} EndGlobalSection EndGlobal diff --git a/src/Angor/Avalonia/Angor.Wallet/Angor.Wallet.csproj b/src/Angor/Avalonia/Angor.Wallet/Angor.Wallet.csproj new file mode 100644 index 00000000..b66defdc --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Angor.Wallet.csproj @@ -0,0 +1,13 @@ + + + + net9.0 + enable + enable + + + + + + + diff --git a/src/Angor/Avalonia/Angor.Wallet/Application/ITransactionWatcher.cs b/src/Angor/Avalonia/Angor.Wallet/Application/ITransactionWatcher.cs new file mode 100644 index 00000000..cca5b2d2 --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Application/ITransactionWatcher.cs @@ -0,0 +1,8 @@ +using Angor.Wallet.Domain; + +namespace Angor.Wallet.Application; + +public interface ITransactionWatcher +{ + IObservable Watch(WalletId id); +} \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Application/IWalletAppService.cs b/src/Angor/Avalonia/Angor.Wallet/Application/IWalletAppService.cs new file mode 100644 index 00000000..cf8a045a --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Application/IWalletAppService.cs @@ -0,0 +1,15 @@ +using Angor.Wallet.Domain; +using CSharpFunctionalExtensions; + +namespace Angor.Wallet.Application; + +public interface IWalletAppService +{ + Task> SendAmount(WalletId walletId, Amount amount, Address address, DomainFeeRate feeRate); + Task>> GetWallets(); + Task>> GetTransactions(WalletId walletId); + Task> GetBalance(WalletId walletId); + Task> EstimateFee(WalletId walletId, Amount amount, Address address, DomainFeeRate feeRate); + Task> GetNextReceiveAddress(WalletId id); + Task> ImportWallet(string name, string seedwords, Maybe passphrase, string encryptionKey, BitcoinNetwork network); +} \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/Address.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/Address.cs new file mode 100644 index 00000000..0105c13a --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/Address.cs @@ -0,0 +1,3 @@ +namespace Angor.Wallet.Domain; + +public record Address(string Value); \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/Amount.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/Amount.cs new file mode 100644 index 00000000..4649ca41 --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/Amount.cs @@ -0,0 +1,3 @@ +namespace Angor.Wallet.Domain; + +public record Amount(long Value); \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/Balance.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/Balance.cs new file mode 100644 index 00000000..c5f0caab --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/Balance.cs @@ -0,0 +1,3 @@ +namespace Angor.Wallet.Domain; + +public record Balance(long Value); \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/BitcoinNetwork.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/BitcoinNetwork.cs new file mode 100644 index 00000000..33321984 --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/BitcoinNetwork.cs @@ -0,0 +1,7 @@ +namespace Angor.Wallet.Domain; + +public enum BitcoinNetwork +{ + Mainnet, + Testnet +} \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/BroadcastedTransaction.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/BroadcastedTransaction.cs new file mode 100644 index 00000000..2ab67297 --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/BroadcastedTransaction.cs @@ -0,0 +1,15 @@ +namespace Angor.Wallet.Domain; + +public record BroadcastedTransaction( + Balance Balance, + string Id, + IEnumerable WalletInputs, + IEnumerable WalletOutputs, + IEnumerable AllInputs, + IEnumerable AllOutputs, + long Fee, + bool IsConfirmed, + int? BlockHeight, + DateTimeOffset? BlockTime, + string RawJson +); \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/DerivationPath.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/DerivationPath.cs new file mode 100644 index 00000000..2ab5e2b5 --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/DerivationPath.cs @@ -0,0 +1,20 @@ +namespace Angor.Wallet.Domain; + +public record DerivationPath +{ + public uint Purpose { get; } + public uint CoinType { get; } + public uint Account { get; } + + private DerivationPath(uint purpose, uint coinType, uint account) + { + Purpose = purpose; + CoinType = coinType; + Account = account; + } + + public static DerivationPath Create(uint purpose, uint coinType, uint account) => + new(purpose, coinType, account); + + public override string ToString() => $"{Purpose}'{CoinType}'{Account}'"; +} \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/DerivationType.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/DerivationType.cs new file mode 100644 index 00000000..09a9893d --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/DerivationType.cs @@ -0,0 +1,7 @@ +namespace Angor.Wallet.Domain; + +public enum DerivationType +{ + Receive = 0, + Change = 1 +} \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/DomainException.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/DomainException.cs new file mode 100644 index 00000000..ade57682 --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/DomainException.cs @@ -0,0 +1,3 @@ +namespace Angor.Wallet.Domain; + +public class DomainException(string message) : Exception(message); \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/DomainFeeRate.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/DomainFeeRate.cs new file mode 100644 index 00000000..b904aea1 --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/DomainFeeRate.cs @@ -0,0 +1,3 @@ +namespace Angor.Wallet.Domain; + +public record DomainFeeRate(long Value); \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/DomainScriptType.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/DomainScriptType.cs new file mode 100644 index 00000000..e79b11dd --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/DomainScriptType.cs @@ -0,0 +1,11 @@ +namespace Angor.Wallet.Domain; + +public enum DomainScriptType +{ + Invalid, + SegWit, + Taproot, + Legacy, + P2SH, + P2WSH +} \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/Fee.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/Fee.cs new file mode 100644 index 00000000..4f799c62 --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/Fee.cs @@ -0,0 +1,3 @@ +namespace Angor.Wallet.Domain; + +public record Fee(long Value); \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/IWalletRepository.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/IWalletRepository.cs new file mode 100644 index 00000000..e4123ea8 --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/IWalletRepository.cs @@ -0,0 +1,9 @@ +using CSharpFunctionalExtensions; + +namespace Angor.Wallet.Domain; + +public interface IWalletRepository +{ + Task>> ListWallets(); + Task> Get(WalletId id); +} \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/TransactionAddressInfo.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/TransactionAddressInfo.cs new file mode 100644 index 00000000..a2b152e6 --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/TransactionAddressInfo.cs @@ -0,0 +1,6 @@ +namespace Angor.Wallet.Domain; + +public record TransactionAddressInfo( + string Address, + ulong TotalAmount +); \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/TransactionInputInfo.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/TransactionInputInfo.cs new file mode 100644 index 00000000..6a99fe9f --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/TransactionInputInfo.cs @@ -0,0 +1,3 @@ +namespace Angor.Wallet.Domain; + +public record TransactionInputInfo(TransactionAddressInfo Address); \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/TransactionOutputInfo.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/TransactionOutputInfo.cs new file mode 100644 index 00000000..b67b0413 --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/TransactionOutputInfo.cs @@ -0,0 +1,3 @@ +namespace Angor.Wallet.Domain; + +public record TransactionOutputInfo(TransactionAddressInfo Address); \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/TxId.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/TxId.cs new file mode 100644 index 00000000..d4e57bea --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/TxId.cs @@ -0,0 +1,3 @@ +namespace Angor.Wallet.Domain; + +public record TxId(string Value); \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/Wallet.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/Wallet.cs new file mode 100644 index 00000000..7583dc60 --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/Wallet.cs @@ -0,0 +1,13 @@ +namespace Angor.Wallet.Domain; + +public class Wallet +{ + public Wallet(WalletId id, WalletDescriptor descriptor) + { + Id = id; + Descriptor = descriptor; + } + + public WalletId Id { get; } + public WalletDescriptor Descriptor { get; } +} \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/WalletDescriptor.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/WalletDescriptor.cs new file mode 100644 index 00000000..a7b918ab --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/WalletDescriptor.cs @@ -0,0 +1,3 @@ +namespace Angor.Wallet.Domain; + +public sealed record WalletDescriptor(BitcoinNetwork Network, XPubCollection Xpubs); \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/WalletId.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/WalletId.cs new file mode 100644 index 00000000..ae7fceba --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/WalletId.cs @@ -0,0 +1,9 @@ +namespace Angor.Wallet.Domain; + +public record WalletId(Guid Id) +{ + public static WalletId New() + { + return new WalletId(Guid.NewGuid()); + } +} \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/XPub.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/XPub.cs new file mode 100644 index 00000000..10099af9 --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/XPub.cs @@ -0,0 +1,23 @@ +namespace Angor.Wallet.Domain; + +public sealed record XPub +{ + public string Value { get; } + public DomainScriptType ScriptType { get; } + public DerivationPath Path { get; } + + private XPub(string value, DomainScriptType scriptType, DerivationPath path) + { + Value = value; + ScriptType = scriptType; + Path = path; + } + + public static XPub Create(string value, DomainScriptType scriptType, DerivationPath path) + { + if (string.IsNullOrWhiteSpace(value)) + throw new DomainException("XPub value cannot be empty"); + + return new XPub(value, scriptType, path); + } +} \ No newline at end of file diff --git a/src/Angor/Avalonia/Angor.Wallet/Domain/XPubCollection.cs b/src/Angor/Avalonia/Angor.Wallet/Domain/XPubCollection.cs new file mode 100644 index 00000000..aea001d9 --- /dev/null +++ b/src/Angor/Avalonia/Angor.Wallet/Domain/XPubCollection.cs @@ -0,0 +1,5 @@ +using System.Collections.ObjectModel; + +namespace Angor.Wallet.Domain; + +public class XPubCollection : Collection; \ No newline at end of file