Initial functional code

cleanup + functional import wallet

app state check fix

synchronization wip

cleanup

cleanup + debug menus from the home

debug menus modifier added

fix for the failing tests

transparent -> shielded

comments resolved
This commit is contained in:
Lukas Korba 2022-04-13 15:52:31 +02:00
parent 7b33b632b9
commit c6a90ff96c
20 changed files with 604 additions and 379 deletions

View File

@ -11,7 +11,6 @@
0D0781C9278776D20083ACD7 /* ZcashSymbol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D0781C7278776D20083ACD7 /* ZcashSymbol.swift */; }; 0D0781C9278776D20083ACD7 /* ZcashSymbol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D0781C7278776D20083ACD7 /* ZcashSymbol.swift */; };
0D185819272723FF0046B928 /* ColoredChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D185818272723FF0046B928 /* ColoredChip.swift */; }; 0D185819272723FF0046B928 /* ColoredChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D185818272723FF0046B928 /* ColoredChip.swift */; };
0D18581B272728D60046B928 /* PhraseChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D18581A272728D60046B928 /* PhraseChip.swift */; }; 0D18581B272728D60046B928 /* PhraseChip.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D18581A272728D60046B928 /* PhraseChip.swift */; };
0D1922F226BDE29300052649 /* ZcashSDKStubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1922F126BDE29300052649 /* ZcashSDKStubs.swift */; };
0D1C1AA327611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1C1AA227611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift */; }; 0D1C1AA327611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D1C1AA227611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift */; };
0D2ACE8026C2C67100D62E3C /* Zboto.otf in Resources */ = {isa = PBXBuildFile; fileRef = 0D2ACE7F26C2C67100D62E3C /* Zboto.otf */; }; 0D2ACE8026C2C67100D62E3C /* Zboto.otf in Resources */ = {isa = PBXBuildFile; fileRef = 0D2ACE7F26C2C67100D62E3C /* Zboto.otf */; };
0D354A0926D5A9D000315F45 /* Services.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D354A0626D5A9D000315F45 /* Services.swift */; }; 0D354A0926D5A9D000315F45 /* Services.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D354A0626D5A9D000315F45 /* Services.swift */; };
@ -32,7 +31,6 @@
0D7DF08C271DCC0E00530046 /* ScreenBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D7DF08B271DCC0E00530046 /* ScreenBackground.swift */; }; 0D7DF08C271DCC0E00530046 /* ScreenBackground.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D7DF08B271DCC0E00530046 /* ScreenBackground.swift */; };
0D8A43C4272AEEDE005A6414 /* SecantTextStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D8A43C3272AEEDE005A6414 /* SecantTextStyles.swift */; }; 0D8A43C4272AEEDE005A6414 /* SecantTextStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D8A43C3272AEEDE005A6414 /* SecantTextStyles.swift */; };
0D8A43C6272B129C005A6414 /* WordChipGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D8A43C5272B129C005A6414 /* WordChipGrid.swift */; }; 0D8A43C6272B129C005A6414 /* WordChipGrid.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D8A43C5272B129C005A6414 /* WordChipGrid.swift */; };
0DA13CA526C1963000E3B610 /* Balance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA13CA426C1963000E3B610 /* Balance.swift */; };
0DACFA7F27208CE00039EEA5 /* Clamped.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DACFA7E27208CE00039EEA5 /* Clamped.swift */; }; 0DACFA7F27208CE00039EEA5 /* Clamped.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DACFA7E27208CE00039EEA5 /* Clamped.swift */; };
0DACFA8127208D940039EEA5 /* UInt+SuperscriptText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DACFA8027208D940039EEA5 /* UInt+SuperscriptText.swift */; }; 0DACFA8127208D940039EEA5 /* UInt+SuperscriptText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DACFA8027208D940039EEA5 /* UInt+SuperscriptText.swift */; };
0DACFA9027209FA70039EEA5 /* Roboto-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0DACFA8327209FA60039EEA5 /* Roboto-Bold.ttf */; }; 0DACFA9027209FA70039EEA5 /* Roboto-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 0DACFA8327209FA60039EEA5 /* Roboto-Bold.ttf */; };
@ -96,6 +94,8 @@
9E2DF99C27CF704D00649636 /* ImportWalletStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99827CF704D00649636 /* ImportWalletStore.swift */; }; 9E2DF99C27CF704D00649636 /* ImportWalletStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99827CF704D00649636 /* ImportWalletStore.swift */; };
9E2DF99D27CF704D00649636 /* ImportSeedEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99A27CF704D00649636 /* ImportSeedEditor.swift */; }; 9E2DF99D27CF704D00649636 /* ImportSeedEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99A27CF704D00649636 /* ImportSeedEditor.swift */; };
9E2DF99E27CF704D00649636 /* ImportWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99B27CF704D00649636 /* ImportWalletView.swift */; }; 9E2DF99E27CF704D00649636 /* ImportWalletView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2DF99B27CF704D00649636 /* ImportWalletView.swift */; };
9E2F1C8228095AFE004E65FE /* Int64+Zcash.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2F1C8128095AFE004E65FE /* Int64+Zcash.swift */; };
9E2F1C842809B606004E65FE /* DebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E2F1C832809B606004E65FE /* DebugMenu.swift */; };
9E37A2B827C8F59F00AE57B3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9E37A2B727C8F59F00AE57B3 /* Localizable.strings */; }; 9E37A2B827C8F59F00AE57B3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9E37A2B727C8F59F00AE57B3 /* Localizable.strings */; };
9E4DC6E027C409A100E657F4 /* NeumorphicDesignModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4DC6DF27C409A100E657F4 /* NeumorphicDesignModifier.swift */; }; 9E4DC6E027C409A100E657F4 /* NeumorphicDesignModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4DC6DF27C409A100E657F4 /* NeumorphicDesignModifier.swift */; };
9E4DC6E227C4C6B700E657F4 /* SecantButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4DC6E127C4C6B700E657F4 /* SecantButtonStyles.swift */; }; 9E4DC6E227C4C6B700E657F4 /* SecantButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9E4DC6E127C4C6B700E657F4 /* SecantButtonStyles.swift */; };
@ -104,6 +104,8 @@
9EAFEB822805793200199FC9 /* AppReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAFEB812805793200199FC9 /* AppReducerTests.swift */; }; 9EAFEB822805793200199FC9 /* AppReducerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAFEB812805793200199FC9 /* AppReducerTests.swift */; };
9EAFEB84280597B700199FC9 /* WrappedSecItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAFEB83280597B700199FC9 /* WrappedSecItem.swift */; }; 9EAFEB84280597B700199FC9 /* WrappedSecItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAFEB83280597B700199FC9 /* WrappedSecItem.swift */; };
9EAFEB862805A23100199FC9 /* WrappedSecItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAFEB852805A23100199FC9 /* WrappedSecItemTests.swift */; }; 9EAFEB862805A23100199FC9 /* WrappedSecItemTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAFEB852805A23100199FC9 /* WrappedSecItemTests.swift */; };
9EAFEB882806E5AE00199FC9 /* CombineSynchronizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAFEB872806E5AE00199FC9 /* CombineSynchronizer.swift */; };
9EAFEB8A2806F48100199FC9 /* ZCashSDKEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAFEB892806F48100199FC9 /* ZCashSDKEnvironment.swift */; };
9EAFEB8F2808183D00199FC9 /* SandboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAFEB8D2808183D00199FC9 /* SandboxView.swift */; }; 9EAFEB8F2808183D00199FC9 /* SandboxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAFEB8D2808183D00199FC9 /* SandboxView.swift */; };
9EAFEB902808183D00199FC9 /* SandboxStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAFEB8E2808183D00199FC9 /* SandboxStore.swift */; }; 9EAFEB902808183D00199FC9 /* SandboxStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9EAFEB8E2808183D00199FC9 /* SandboxStore.swift */; };
9EAFEB9128081E9400199FC9 /* HomeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93874ED273C4DE200F0E875 /* HomeStore.swift */; }; 9EAFEB9128081E9400199FC9 /* HomeStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93874ED273C4DE200F0E875 /* HomeStore.swift */; };
@ -165,7 +167,6 @@
0D0781C7278776D20083ACD7 /* ZcashSymbol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZcashSymbol.swift; sourceTree = "<group>"; }; 0D0781C7278776D20083ACD7 /* ZcashSymbol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ZcashSymbol.swift; sourceTree = "<group>"; };
0D185818272723FF0046B928 /* ColoredChip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColoredChip.swift; sourceTree = "<group>"; }; 0D185818272723FF0046B928 /* ColoredChip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColoredChip.swift; sourceTree = "<group>"; };
0D18581A272728D60046B928 /* PhraseChip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhraseChip.swift; sourceTree = "<group>"; }; 0D18581A272728D60046B928 /* PhraseChip.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhraseChip.swift; sourceTree = "<group>"; };
0D1922F126BDE29300052649 /* ZcashSDKStubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZcashSDKStubs.swift; sourceTree = "<group>"; };
0D1C1AA227611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseDisplayReducerTests.swift; sourceTree = "<group>"; }; 0D1C1AA227611EFD0004AF6A /* RecoveryPhraseDisplayReducerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseDisplayReducerTests.swift; sourceTree = "<group>"; };
0D2ACE7F26C2C67100D62E3C /* Zboto.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Zboto.otf; sourceTree = "<group>"; }; 0D2ACE7F26C2C67100D62E3C /* Zboto.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = Zboto.otf; sourceTree = "<group>"; };
0D354A0626D5A9D000315F45 /* Services.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Services.swift; sourceTree = "<group>"; }; 0D354A0626D5A9D000315F45 /* Services.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Services.swift; sourceTree = "<group>"; };
@ -254,6 +255,8 @@
9E2DF99827CF704D00649636 /* ImportWalletStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletStore.swift; sourceTree = "<group>"; }; 9E2DF99827CF704D00649636 /* ImportWalletStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletStore.swift; sourceTree = "<group>"; };
9E2DF99A27CF704D00649636 /* ImportSeedEditor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportSeedEditor.swift; sourceTree = "<group>"; }; 9E2DF99A27CF704D00649636 /* ImportSeedEditor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportSeedEditor.swift; sourceTree = "<group>"; };
9E2DF99B27CF704D00649636 /* ImportWalletView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletView.swift; sourceTree = "<group>"; }; 9E2DF99B27CF704D00649636 /* ImportWalletView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportWalletView.swift; sourceTree = "<group>"; };
9E2F1C8128095AFE004E65FE /* Int64+Zcash.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int64+Zcash.swift"; sourceTree = "<group>"; };
9E2F1C832809B606004E65FE /* DebugMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugMenu.swift; sourceTree = "<group>"; };
9E37A2B727C8F59F00AE57B3 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; }; 9E37A2B727C8F59F00AE57B3 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = "<group>"; };
9E4DC6DF27C409A100E657F4 /* NeumorphicDesignModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NeumorphicDesignModifier.swift; sourceTree = "<group>"; }; 9E4DC6DF27C409A100E657F4 /* NeumorphicDesignModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NeumorphicDesignModifier.swift; sourceTree = "<group>"; };
9E4DC6E127C4C6B700E657F4 /* SecantButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecantButtonStyles.swift; sourceTree = "<group>"; }; 9E4DC6E127C4C6B700E657F4 /* SecantButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecantButtonStyles.swift; sourceTree = "<group>"; };
@ -262,6 +265,8 @@
9EAFEB812805793200199FC9 /* AppReducerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReducerTests.swift; sourceTree = "<group>"; }; 9EAFEB812805793200199FC9 /* AppReducerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppReducerTests.swift; sourceTree = "<group>"; };
9EAFEB83280597B700199FC9 /* WrappedSecItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrappedSecItem.swift; sourceTree = "<group>"; }; 9EAFEB83280597B700199FC9 /* WrappedSecItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrappedSecItem.swift; sourceTree = "<group>"; };
9EAFEB852805A23100199FC9 /* WrappedSecItemTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrappedSecItemTests.swift; sourceTree = "<group>"; }; 9EAFEB852805A23100199FC9 /* WrappedSecItemTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrappedSecItemTests.swift; sourceTree = "<group>"; };
9EAFEB872806E5AE00199FC9 /* CombineSynchronizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombineSynchronizer.swift; sourceTree = "<group>"; };
9EAFEB892806F48100199FC9 /* ZCashSDKEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZCashSDKEnvironment.swift; sourceTree = "<group>"; };
9EAFEB8D2808183D00199FC9 /* SandboxView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SandboxView.swift; sourceTree = "<group>"; }; 9EAFEB8D2808183D00199FC9 /* SandboxView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SandboxView.swift; sourceTree = "<group>"; };
9EAFEB8E2808183D00199FC9 /* SandboxStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SandboxStore.swift; sourceTree = "<group>"; }; 9EAFEB8E2808183D00199FC9 /* SandboxStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SandboxStore.swift; sourceTree = "<group>"; };
9EBEF87927CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseTestPreambleView.swift; sourceTree = "<group>"; }; 9EBEF87927CE369800B4F343 /* RecoveryPhraseTestPreambleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RecoveryPhraseTestPreambleView.swift; sourceTree = "<group>"; };
@ -361,14 +366,6 @@
path = Screens; path = Screens;
sourceTree = "<group>"; sourceTree = "<group>";
}; };
0D1922F026BDE27D00052649 /* Stubs */ = {
isa = PBXGroup;
children = (
0D1922F126BDE29300052649 /* ZcashSDKStubs.swift */,
);
path = Stubs;
sourceTree = "<group>";
};
0D2ACE7E26C2C65E00D62E3C /* Fonts */ = { 0D2ACE7E26C2C65E00D62E3C /* Fonts */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@ -435,7 +432,6 @@
0D2ACE7E26C2C65E00D62E3C /* Fonts */, 0D2ACE7E26C2C65E00D62E3C /* Fonts */,
0DA13CA326C1960A00E3B610 /* Models */, 0DA13CA326C1960A00E3B610 /* Models */,
0DA13C9126C15E1900E3B610 /* UIComponents */, 0DA13C9126C15E1900E3B610 /* UIComponents */,
0D1922F026BDE27D00052649 /* Stubs */,
0D1922EB26BDD9A500052649 /* Screens */, 0D1922EB26BDD9A500052649 /* Screens */,
0D170A7426BC9B7500EB6A46 /* MockedDependencies */, 0D170A7426BC9B7500EB6A46 /* MockedDependencies */,
0D4E7A0826B364170058B01E /* SecantApp.swift */, 0D4E7A0826B364170058B01E /* SecantApp.swift */,
@ -559,6 +555,10 @@
9EF8139B27F47AED0075AF48 /* InitializationState.swift */, 9EF8139B27F47AED0075AF48 /* InitializationState.swift */,
9EF8139027F191BF0075AF48 /* WalletStorageInteractor.swift */, 9EF8139027F191BF0075AF48 /* WalletStorageInteractor.swift */,
9ECAE56727FC713C0089A0EF /* DatabaseFiles.swift */, 9ECAE56727FC713C0089A0EF /* DatabaseFiles.swift */,
9EAFEB872806E5AE00199FC9 /* CombineSynchronizer.swift */,
9EAFEB892806F48100199FC9 /* ZCashSDKEnvironment.swift */,
9E2F1C8128095AFE004E65FE /* Int64+Zcash.swift */,
9E2F1C832809B606004E65FE /* DebugMenu.swift */,
); );
path = Util; path = Util;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1180,12 +1180,13 @@
9EF8136027F043CC0075AF48 /* AppDelegate.swift in Sources */, 9EF8136027F043CC0075AF48 /* AppDelegate.swift in Sources */,
9E80B47227E4B34B008FF493 /* UserPreferencesStorage.swift in Sources */, 9E80B47227E4B34B008FF493 /* UserPreferencesStorage.swift in Sources */,
F96B41E8273B501F0021B49A /* TransactionDetailView.swift in Sources */, F96B41E8273B501F0021B49A /* TransactionDetailView.swift in Sources */,
9E2F1C8228095AFE004E65FE /* Int64+Zcash.swift in Sources */,
9E02B56A27FED43E005B809B /* WrappedFileManager.swift in Sources */, 9E02B56A27FED43E005B809B /* WrappedFileManager.swift in Sources */,
663FABA2271D876C00E495F8 /* SecondaryButton.swift in Sources */, 663FABA2271D876C00E495F8 /* SecondaryButton.swift in Sources */,
0DC487C32772574C00BE6A63 /* ValidationSucceededView.swift in Sources */, 0DC487C32772574C00BE6A63 /* ValidationSucceededView.swift in Sources */,
2EB1C5E827D77F6100BC43D7 /* TextFieldStore.swift in Sources */, 2EB1C5E827D77F6100BC43D7 /* TextFieldStore.swift in Sources */,
9EAFEB8A2806F48100199FC9 /* ZCashSDKEnvironment.swift in Sources */,
0D8A43C4272AEEDE005A6414 /* SecantTextStyles.swift in Sources */, 0D8A43C4272AEEDE005A6414 /* SecantTextStyles.swift in Sources */,
0D1922F226BDE29300052649 /* ZcashSDKStubs.swift in Sources */,
9E4DC6E027C409A100E657F4 /* NeumorphicDesignModifier.swift in Sources */, 9E4DC6E027C409A100E657F4 /* NeumorphicDesignModifier.swift in Sources */,
0DACFA7F27208CE00039EEA5 /* Clamped.swift in Sources */, 0DACFA7F27208CE00039EEA5 /* Clamped.swift in Sources */,
0DFE93E3272CA1AA000FCCA5 /* RecoveryPhraseValidation.swift in Sources */, 0DFE93E3272CA1AA000FCCA5 /* RecoveryPhraseValidation.swift in Sources */,
@ -1208,6 +1209,7 @@
0D18581B272728D60046B928 /* PhraseChip.swift in Sources */, 0D18581B272728D60046B928 /* PhraseChip.swift in Sources */,
0DF482BA2787ADA800EB37D6 /* ConditionalModifier.swift in Sources */, 0DF482BA2787ADA800EB37D6 /* ConditionalModifier.swift in Sources */,
665C963F272C26E600BC04FB /* CircularFrameBackground.swift in Sources */, 665C963F272C26E600BC04FB /* CircularFrameBackground.swift in Sources */,
9EAFEB882806E5AE00199FC9 /* CombineSynchronizer.swift in Sources */,
0DB8AA81271DC7520035BC9D /* DesignGuide.swift in Sources */, 0DB8AA81271DC7520035BC9D /* DesignGuide.swift in Sources */,
F9971A4D27680DC400A2DB75 /* App.swift in Sources */, F9971A4D27680DC400A2DB75 /* App.swift in Sources */,
9EAFEB9228081E9400199FC9 /* HomeView.swift in Sources */, 9EAFEB9228081E9400199FC9 /* HomeView.swift in Sources */,
@ -1217,7 +1219,6 @@
0D3D04082728B3440032ABC1 /* RecoveryPhraseDisplayView.swift in Sources */, 0D3D04082728B3440032ABC1 /* RecoveryPhraseDisplayView.swift in Sources */,
F9971A5F27680DF600A2DB75 /* ScanView.swift in Sources */, F9971A5F27680DF600A2DB75 /* ScanView.swift in Sources */,
F9971A4E27680DC400A2DB75 /* AppView.swift in Sources */, F9971A4E27680DC400A2DB75 /* AppView.swift in Sources */,
0DA13CA526C1963000E3B610 /* Balance.swift in Sources */,
2EA11F5B27467EF800709571 /* OnboardingFooterView.swift in Sources */, 2EA11F5B27467EF800709571 /* OnboardingFooterView.swift in Sources */,
66D50668271D9B6100E51F0D /* NavigationButtonStyle.swift in Sources */, 66D50668271D9B6100E51F0D /* NavigationButtonStyle.swift in Sources */,
2EDA07A427EDE2A900D6F09B /* DebugFrame.swift in Sources */, 2EDA07A427EDE2A900D6F09B /* DebugFrame.swift in Sources */,
@ -1235,6 +1236,7 @@
66DC733F271D88CC0053CBB6 /* StandardButtonStyle.swift in Sources */, 66DC733F271D88CC0053CBB6 /* StandardButtonStyle.swift in Sources */,
663FABA0271D876200E495F8 /* PrimaryButton.swift in Sources */, 663FABA0271D876200E495F8 /* PrimaryButton.swift in Sources */,
663FAB9C271D874D00E495F8 /* ActiveButton.swift in Sources */, 663FAB9C271D874D00E495F8 /* ActiveButton.swift in Sources */,
9E2F1C842809B606004E65FE /* DebugMenu.swift in Sources */,
9E02B5C3280458D2005B809B /* WrappedDerivationTool.swift in Sources */, 9E02B5C3280458D2005B809B /* WrappedDerivationTool.swift in Sources */,
F9C165C02740403600592F76 /* ApproveView.swift in Sources */, F9C165C02740403600592F76 /* ApproveView.swift in Sources */,
0DF2DC5427235E3E00FA31E2 /* View+InnerShadow.swift in Sources */, 0DF2DC5427235E3E00FA31E2 /* View+InnerShadow.swift in Sources */,

View File

@ -25,10 +25,11 @@ struct AppState: Equatable {
enum AppAction: Equatable { enum AppAction: Equatable {
case appDelegate(AppDelegateAction) case appDelegate(AppDelegateAction)
case checkBackupPhraseValidation
case checkWalletInitialization case checkWalletInitialization
case createNewWallet case createNewWallet
case home(HomeAction) case home(HomeAction)
case initializeApp case initializeSDK
case nukeWallet case nukeWallet
case onboarding(OnboardingAction) case onboarding(OnboardingAction)
case phraseDisplay(RecoveryPhraseDisplayAction) case phraseDisplay(RecoveryPhraseDisplayAction)
@ -40,28 +41,34 @@ enum AppAction: Equatable {
} }
struct AppEnvironment { struct AppEnvironment {
let combineSynchronizer: CombineSynchronizer
let databaseFiles: DatabaseFilesInteractor let databaseFiles: DatabaseFilesInteractor
let scheduler: AnySchedulerOf<DispatchQueue>
let mnemonicSeedPhraseProvider: MnemonicSeedPhraseProvider let mnemonicSeedPhraseProvider: MnemonicSeedPhraseProvider
let scheduler: AnySchedulerOf<DispatchQueue>
let walletStorage: WalletStorageInteractor let walletStorage: WalletStorageInteractor
let wrappedDerivationTool: WrappedDerivationTool let wrappedDerivationTool: WrappedDerivationTool
let zcashSDKEnvironment: ZCashSDKEnvironment
} }
extension AppEnvironment { extension AppEnvironment {
static let live = AppEnvironment( static let live = AppEnvironment(
combineSynchronizer: LiveCombineSynchronizer(),
databaseFiles: .live(), databaseFiles: .live(),
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
mnemonicSeedPhraseProvider: .live, mnemonicSeedPhraseProvider: .live,
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live(), walletStorage: .live(),
wrappedDerivationTool: .live() wrappedDerivationTool: .live(),
zcashSDKEnvironment: .mainnet
) )
static let mock = AppEnvironment( static let mock = AppEnvironment(
combineSynchronizer: LiveCombineSynchronizer(),
databaseFiles: .live(), databaseFiles: .live(),
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
mnemonicSeedPhraseProvider: .mock, mnemonicSeedPhraseProvider: .mock,
scheduler: DispatchQueue.main.eraseToAnyScheduler(),
walletStorage: .live(), walletStorage: .live(),
wrappedDerivationTool: .live(derivationTool: DerivationTool(networkType: .testnet)) wrappedDerivationTool: .live(derivationTool: DerivationTool(networkType: .mainnet)),
zcashSDKEnvironment: .mainnet
) )
} }
@ -104,14 +111,17 @@ extension AppReducer {
case .failed: case .failed:
// TODO: error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221) // TODO: error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221)
state.appInitializationState = .failed state.appInitializationState = .failed
case .initialized:
return Effect(value: .initializeApp)
case .keysMissing: case .keysMissing:
// TODO: error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221) // TODO: error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221)
state.appInitializationState = .keysMissing state.appInitializationState = .keysMissing
case .filesMissing: case .initialized, .filesMissing:
state.appInitializationState = .filesMissing if walletState == .filesMissing {
return Effect(value: .initializeApp) state.appInitializationState = .filesMissing
}
return .concatenate(
Effect(value: .initializeSDK),
Effect(value: .checkBackupPhraseValidation)
)
case .uninitialized: case .uninitialized:
state.appInitializationState = .uninitialized state.appInitializationState = .uninitialized
return Effect(value: .updateRoute(.onboarding)) return Effect(value: .updateRoute(.onboarding))
@ -124,36 +134,54 @@ extension AppReducer {
/// Stored wallet is present, database files may or may not be present, trying to initialize app state variables and environments. /// Stored wallet is present, database files may or may not be present, trying to initialize app state variables and environments.
/// When initialization succeeds user is taken to the home screen. /// When initialization succeeds user is taken to the home screen.
case .initializeApp: case .initializeSDK:
do { do {
state.storedWallet = try environment.walletStorage.exportWallet() state.storedWallet = try environment.walletStorage.exportWallet()
guard let storedWallet = state.storedWallet else {
state.appInitializationState = .failed
// TODO: fatal error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221)
return .none
}
try environment.mnemonicSeedPhraseProvider.isValid(storedWallet.seedPhrase)
let birthday = state.storedWallet?.birthday ?? environment.zcashSDKEnvironment.defaultBirthday
let initializer = try prepareInitializer(
for: storedWallet.seedPhrase,
birthday: birthday,
with: environment
)
try environment.combineSynchronizer.prepareWith(initializer: initializer)
try environment.combineSynchronizer.start()
} catch { } catch {
state.appInitializationState = .failed state.appInitializationState = .failed
// TODO: error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221) // TODO: error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221)
return .none
} }
return .none
case .checkBackupPhraseValidation:
guard let storedWallet = state.storedWallet else { guard let storedWallet = state.storedWallet else {
return Effect(value: .updateRoute(.onboarding)) state.appInitializationState = .failed
.delay(for: 3, scheduler: environment.scheduler) // TODO: fatal error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221)
.eraseToEffect() return .none
.cancellable(id: ListenerId(), cancelInFlight: true)
} }
var landingRoute: AppState.Route = .home var landingRoute: AppState.Route = .home
if !storedWallet.hasUserPassedPhraseBackupTest { if !storedWallet.hasUserPassedPhraseBackupTest {
let phraseWords: [String]
do { do {
phraseWords = try environment.mnemonicSeedPhraseProvider.asWords(storedWallet.seedPhrase) let phraseWords = try environment.mnemonicSeedPhraseProvider.asWords(storedWallet.seedPhrase)
let recoveryPhrase = RecoveryPhrase(words: phraseWords)
state.phraseDisplayState.phrase = recoveryPhrase
state.phraseValidationState = RecoveryPhraseValidationState.random(phrase: recoveryPhrase)
landingRoute = .phraseDisplay
} catch { } catch {
// TODO: - merge with issue 201 (https://github.com/zcash/secant-ios-wallet/issues/201) and its Error States // TODO: - merge with issue 201 (https://github.com/zcash/secant-ios-wallet/issues/201) and its Error States
return .none return .none
} }
let recoveryPhrase = RecoveryPhrase(words: phraseWords)
state.phraseDisplayState.phrase = recoveryPhrase
state.phraseValidationState = RecoveryPhraseValidationState.random(phrase: recoveryPhrase)
landingRoute = .phraseDisplay
} }
state.appInitializationState = .initialized state.appInitializationState = .initialized
@ -161,25 +189,26 @@ extension AppReducer {
.delay(for: 3, scheduler: environment.scheduler) .delay(for: 3, scheduler: environment.scheduler)
.eraseToEffect() .eraseToEffect()
.cancellable(id: ListenerId(), cancelInFlight: true) .cancellable(id: ListenerId(), cancelInFlight: true)
case .createNewWallet: case .createNewWallet:
do { do {
// get the random english mnemonic // get the random english mnemonic
let randomPhrase = try environment.mnemonicSeedPhraseProvider.randomMnemonic() let randomPhrase = try environment.mnemonicSeedPhraseProvider.randomMnemonic()
let randomPhraseWords = try environment.mnemonicSeedPhraseProvider.asWords(randomPhrase) let birthday = try environment.zcashSDKEnvironment.lightWalletService.latestBlockHeight()
// TODO: - Get birthday from the integrated SDK, issue 228 (https://github.com/zcash/secant-ios-wallet/issues/228)
// get the latest block height
let birthday = BlockHeight(12345678)
// store the wallet to the keychain // store the wallet to the keychain
try environment.walletStorage.importWallet(randomPhrase, birthday, .english, false) try environment.walletStorage.importWallet(randomPhrase, birthday, .english, false)
// start the backup phrase validation test // start the backup phrase validation test
let randomPhraseWords = try environment.mnemonicSeedPhraseProvider.asWords(randomPhrase)
let recoveryPhrase = RecoveryPhrase(words: randomPhraseWords) let recoveryPhrase = RecoveryPhrase(words: randomPhraseWords)
state.phraseDisplayState.phrase = recoveryPhrase state.phraseDisplayState.phrase = recoveryPhrase
state.phraseValidationState = RecoveryPhraseValidationState.random(phrase: recoveryPhrase) state.phraseValidationState = RecoveryPhraseValidationState.random(phrase: recoveryPhrase)
return Effect(value: .phraseValidation(.displayBackedUpPhrase)) return .concatenate(
Effect(value: .initializeSDK),
Effect(value: .phraseValidation(.displayBackedUpPhrase))
)
} catch { } catch {
// TODO: - merge with issue 201 (https://github.com/zcash/secant-ios-wallet/issues/201) and its Error States // TODO: - merge with issue 201 (https://github.com/zcash/secant-ios-wallet/issues/201) and its Error States
} }
@ -193,21 +222,28 @@ extension AppReducer {
// TODO: error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221) // TODO: error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221)
} }
return .none return .none
case .nukeWallet: case .nukeWallet:
environment.walletStorage.nukeWallet() environment.walletStorage.nukeWallet()
// TODO: - when DatabaseFiles dependency is merged, nukeFiles as well, issue #220 (https://github.com/zcash/secant-ios-wallet/issues/220) do {
try environment.databaseFiles.nukeDbFilesFor(environment.zcashSDKEnvironment.network)
} catch {
// TODO: error we need to handle, issue #221 (https://github.com/zcash/secant-ios-wallet/issues/221)
}
return .none return .none
case .welcome(.debugMenuStartup): case .welcome(.debugMenuStartup), .home(.debugMenuStartup):
return .concatenate( return .concatenate(
Effect.cancel(id: ListenerId()), Effect.cancel(id: ListenerId()),
Effect(value: .updateRoute(.startup)) Effect(value: .updateRoute(.startup))
) )
case .onboarding(.importWallet(.successfullyRecovered)): case .onboarding(.importWallet(.successfullyRecovered)):
return Effect(value: .updateRoute(.sandbox)) return Effect(value: .updateRoute(.home))
case .onboarding(.importWallet(.initializeSDK)):
return Effect(value: .initializeSDK)
/// Default is meaningful here because there's `routeReducer` handling routes and this reducer is handling only actions. We don't here plenty of unused cases. /// Default is meaningful here because there's `routeReducer` handling routes and this reducer is handling only actions. We don't here plenty of unused cases.
default: default:
return .none return .none
@ -246,7 +282,11 @@ extension AppReducer {
private static let homeReducer: AppReducer = HomeReducer.default.pullback( private static let homeReducer: AppReducer = HomeReducer.default.pullback(
state: \AppState.homeState, state: \AppState.homeState,
action: /AppAction.home, action: /AppAction.home,
environment: { _ in } environment: { environment in
HomeEnvironment(
combineSynchronizer: environment.combineSynchronizer
)
}
) )
private static let onboardingReducer: AppReducer = OnboardingReducer.default.pullback( private static let onboardingReducer: AppReducer = OnboardingReducer.default.pullback(
@ -255,7 +295,8 @@ extension AppReducer {
environment: { environment in environment: { environment in
OnboardingEnvironment( OnboardingEnvironment(
mnemonicSeedPhraseProvider: environment.mnemonicSeedPhraseProvider, mnemonicSeedPhraseProvider: environment.mnemonicSeedPhraseProvider,
walletStorage: environment.walletStorage walletStorage: environment.walletStorage,
zcashSDKEnvironment: environment.zcashSDKEnvironment
) )
} }
) )
@ -292,8 +333,9 @@ extension AppReducer {
var keysPresent = false var keysPresent = false
do { do {
keysPresent = try environment.walletStorage.areKeysPresent() keysPresent = try environment.walletStorage.areKeysPresent()
// TODO: replace the hardcoded network with the environmental value, issue 239 (https://github.com/zcash/secant-ios-wallet/issues/239) let databaseFilesPresent = try environment.databaseFiles.areDbFilesPresentFor(
let databaseFilesPresent = try environment.databaseFiles.areDbFilesPresentFor("mainnet") environment.zcashSDKEnvironment.network
)
switch (keysPresent, databaseFilesPresent) { switch (keysPresent, databaseFilesPresent) {
case (false, false): case (false, false):
@ -311,8 +353,9 @@ extension AppReducer {
} }
} catch WalletStorage.WalletStorageError.uninitializedWallet { } catch WalletStorage.WalletStorageError.uninitializedWallet {
do { do {
// TODO: replace the hardcoded network with the environmental value, issue 239 (https://github.com/zcash/secant-ios-wallet/issues/239) if try environment.databaseFiles.areDbFilesPresentFor(
if try environment.databaseFiles.areDbFilesPresentFor("mainnet") { environment.zcashSDKEnvironment.network
) {
return .keysMissing return .keysMissing
} }
} catch { } catch {
@ -324,6 +367,35 @@ extension AppReducer {
return .uninitialized return .uninitialized
} }
static func prepareInitializer(
for seedPhrase: String,
birthday: BlockHeight,
with environment: AppEnvironment
) throws -> Initializer {
do {
let seedBytes = try environment.mnemonicSeedPhraseProvider.toSeed(seedPhrase)
let viewingKeys = try environment.wrappedDerivationTool.deriveUnifiedViewingKeysFromSeed(seedBytes, 1)
let network = environment.zcashSDKEnvironment.network
let initializer = Initializer(
cacheDbURL: try environment.databaseFiles.cacheDbURLFor(network),
dataDbURL: try environment.databaseFiles.dataDbURLFor(network),
pendingDbURL: try environment.databaseFiles.pendingDbURLFor(network),
endpoint: environment.zcashSDKEnvironment.endpoint,
network: environment.zcashSDKEnvironment.network,
spendParamsURL: try environment.databaseFiles.spendParamsURLFor(network),
outputParamsURL: try environment.databaseFiles.outputParamsURLFor(network),
viewingKeys: viewingKeys,
walletBirthday: birthday
)
return initializer
} catch {
throw SDKInitializationError.failed
}
}
} }
// MARK: - AppStore // MARK: - AppStore
@ -335,7 +407,7 @@ extension AppStore {
AppStore( AppStore(
initialState: .placeholder, initialState: .placeholder,
reducer: .default, reducer: .default,
environment: .mock environment: .live
) )
} }
} }

View File

@ -2,19 +2,48 @@ import ComposableArchitecture
import SwiftUI import SwiftUI
struct HomeState: Equatable { struct HomeState: Equatable {
var balance: Double var totalBalance: Double
var verifiedBalance: Double
var arePublishersPrepared = false
} }
enum HomeAction: Equatable { enum HomeAction: Equatable {
case debugMenuStartup
case preparePublishers
case updateBalance(Balance)
}
struct HomeEnvironment {
let combineSynchronizer: CombineSynchronizer
} }
// MARK: - HomeReducer // MARK: - HomeReducer
typealias HomeReducer = Reducer<HomeState, HomeAction, Void> typealias HomeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
extension HomeReducer { extension HomeReducer {
static let `default` = HomeReducer { _, _, _ in static let `default` = HomeReducer { state, action, environment in
return .none switch action {
case .preparePublishers:
if !state.arePublishersPrepared {
state.arePublishersPrepared = true
return environment.combineSynchronizer.shieldedBalance
.receive(on: DispatchQueue.main)
.map({ Balance(verified: $0.verified, total: $0.total) })
.map(HomeAction.updateBalance)
.eraseToEffect()
}
return .none
case .updateBalance(let balance):
state.totalBalance = balance.total.asHumanReadableZecBalance()
state.verifiedBalance = balance.verified.asHumanReadableZecBalance()
return .none
case .debugMenuStartup:
return .none
}
} }
} }
@ -26,7 +55,8 @@ typealias HomeStore = Store<HomeState, HomeAction>
extension HomeState { extension HomeState {
static var placeholder: Self { static var placeholder: Self {
.init( .init(
balance: 1.2 totalBalance: 0.0,
verifiedBalance: 0.0
) )
} }
} }

View File

@ -6,9 +6,14 @@ struct HomeView: View {
var body: some View { var body: some View {
WithViewStore(store) { viewStore in WithViewStore(store) { viewStore in
VStack { VStack(alignment: .center, spacing: 30.0) {
Text("balance \(viewStore.balance)") Text("totalBalance \(viewStore.totalBalance)")
Text("verifiedBalance \(viewStore.verifiedBalance)")
.accessDebugMenuWithHiddenGesture {
viewStore.send(.debugMenuStartup)
}
} }
.onAppear(perform: { viewStore.send(.preparePublishers) })
} }
} }
} }
@ -20,7 +25,9 @@ extension HomeStore {
HomeStore( HomeStore(
initialState: .placeholder, initialState: .placeholder,
reducer: .default.debug(), reducer: .default.debug(),
environment: () environment: HomeEnvironment(
combineSynchronizer: LiveCombineSynchronizer()
)
) )
} }
} }

View File

@ -6,6 +6,7 @@
// //
import ComposableArchitecture import ComposableArchitecture
import ZcashLightClientKit
typealias ImportWalletStore = Store<ImportWalletState, ImportWalletAction> typealias ImportWalletStore = Store<ImportWalletState, ImportWalletAction>
@ -19,23 +20,27 @@ enum ImportWalletAction: Equatable, BindableAction {
case dismissAlert case dismissAlert
case importRecoveryPhrase case importRecoveryPhrase
case importPrivateOrViewingKey case importPrivateOrViewingKey
case initializeSDK
case successfullyRecovered case successfullyRecovered
} }
struct ImportWalletEnvironment { struct ImportWalletEnvironment {
let mnemonicSeedPhraseProvider: MnemonicSeedPhraseProvider let mnemonicSeedPhraseProvider: MnemonicSeedPhraseProvider
let walletStorage: WalletStorageInteractor let walletStorage: WalletStorageInteractor
let zcashSDKEnvironment: ZCashSDKEnvironment
} }
extension ImportWalletEnvironment { extension ImportWalletEnvironment {
static let live = ImportWalletEnvironment( static let live = ImportWalletEnvironment(
mnemonicSeedPhraseProvider: .live, mnemonicSeedPhraseProvider: .live,
walletStorage: .live() walletStorage: .live(),
zcashSDKEnvironment: .mainnet
) )
static let demo = ImportWalletEnvironment( static let demo = ImportWalletEnvironment(
mnemonicSeedPhraseProvider: .mock, mnemonicSeedPhraseProvider: .mock,
walletStorage: .live() walletStorage: .live(),
zcashSDKEnvironment: .testnet
) )
} }
@ -57,15 +62,12 @@ extension ImportWalletReducer {
try environment.mnemonicSeedPhraseProvider.isValid(state.importedSeedPhrase) try environment.mnemonicSeedPhraseProvider.isValid(state.importedSeedPhrase)
// store it to the keychain // store it to the keychain
// TODO: - Get the latest block number, initialization of the SDK = Issue #239 (https://github.com/zcash/secant-ios-wallet/issues/239) let birthday = environment.zcashSDKEnvironment.defaultBirthday
let birthday = BlockHeight(1386000)
try environment.walletStorage.importWallet(state.importedSeedPhrase, birthday, .english, false) try environment.walletStorage.importWallet(state.importedSeedPhrase, birthday, .english, false)
// update the backup phrase validation flag // update the backup phrase validation flag
try environment.walletStorage.markUserPassedPhraseBackupTest() try environment.walletStorage.markUserPassedPhraseBackupTest()
// TODO: - Initialize the SDK with the new seed, initialization of the SDK = Issue #239 (https://github.com/zcash/secant-ios-wallet/issues/239)
// notify user // notify user
// TODO: Proper Error/Success handling, issue 221 (https://github.com/zcash/secant-ios-wallet/issues/221) // TODO: Proper Error/Success handling, issue 221 (https://github.com/zcash/secant-ios-wallet/issues/221)
state.alert = AlertState( state.alert = AlertState(
@ -76,6 +78,8 @@ extension ImportWalletReducer {
action: .send(.successfullyRecovered) action: .send(.successfullyRecovered)
) )
) )
return Effect(value: .initializeSDK)
} catch { } catch {
// TODO: Proper Error/Success handling, issue 221 (https://github.com/zcash/secant-ios-wallet/issues/221) // TODO: Proper Error/Success handling, issue 221 (https://github.com/zcash/secant-ios-wallet/issues/221)
state.alert = AlertState( state.alert = AlertState(
@ -95,6 +99,9 @@ extension ImportWalletReducer {
case .successfullyRecovered: case .successfullyRecovered:
return Effect(value: .dismissAlert) return Effect(value: .dismissAlert)
case .initializeSDK:
return .none
} }
} }
.binding() .binding()

View File

@ -69,17 +69,20 @@ enum OnboardingAction: Equatable {
struct OnboardingEnvironment { struct OnboardingEnvironment {
let mnemonicSeedPhraseProvider: MnemonicSeedPhraseProvider let mnemonicSeedPhraseProvider: MnemonicSeedPhraseProvider
let walletStorage: WalletStorageInteractor let walletStorage: WalletStorageInteractor
let zcashSDKEnvironment: ZCashSDKEnvironment
} }
extension OnboardingEnvironment { extension OnboardingEnvironment {
static let live = OnboardingEnvironment( static let live = OnboardingEnvironment(
mnemonicSeedPhraseProvider: .live, mnemonicSeedPhraseProvider: .live,
walletStorage: .live() walletStorage: .live(),
zcashSDKEnvironment: .mainnet
) )
static let demo = OnboardingEnvironment( static let demo = OnboardingEnvironment(
mnemonicSeedPhraseProvider: .mock, mnemonicSeedPhraseProvider: .mock,
walletStorage: .live() walletStorage: .live(),
zcashSDKEnvironment: .testnet
) )
} }
@ -139,7 +142,8 @@ extension OnboardingReducer {
environment: { environment in environment: { environment in
ImportWalletEnvironment( ImportWalletEnvironment(
mnemonicSeedPhraseProvider: environment.mnemonicSeedPhraseProvider, mnemonicSeedPhraseProvider: environment.mnemonicSeedPhraseProvider,
walletStorage: environment.walletStorage walletStorage: environment.walletStorage,
zcashSDKEnvironment: environment.zcashSDKEnvironment
) )
} }
) )

View File

@ -10,43 +10,9 @@ import ComposableArchitecture
struct WelcomeView: View { struct WelcomeView: View {
var store: WelcomeStore var store: WelcomeStore
enum DragState {
case inactive
case pressing
case dragging(translation: CGSize)
}
let topPaddingRatio: Double = 0.18
let horizontalPaddingRatio: Double = 0.07
@GestureState var dragState = DragState.inactive
var body: some View {
let longPressDrag = LongPressGesture(minimumDuration: 0.75)
.sequenced(before: DragGesture())
.updating($dragState) { value, state, _ in
switch value {
// Long press begins.
case .first(true):
state = .pressing
// Long press confirmed, dragging may begin.
case .second(true, let drag):
state = .dragging(translation: drag?.translation ?? .zero)
// Dragging ended or the long press cancelled.
default:
state = .inactive
}
}
.onEnded { value in
guard case .second(true, let drag?) = value else { return }
if drag.translation.height > 0 {
ViewStore(store).send(.debugMenuStartup)
}
}
return GeometryReader { proxy in var body: some View {
GeometryReader { proxy in
ZStack(alignment: .top) { ZStack(alignment: .top) {
VStack(alignment: .center, spacing: 80) { VStack(alignment: .center, spacing: 80) {
let diameter = proxy.size.width - 40 let diameter = proxy.size.width - 40
@ -55,7 +21,9 @@ struct WelcomeView: View {
width: diameter, width: diameter,
height: diameter height: diameter
) )
.gesture(longPressDrag) .accessDebugMenuWithHiddenGesture {
ViewStore(store).send(.debugMenuStartup)
}
VStack { VStack {
Text("welcomeScreen.title") Text("welcomeScreen.title")

View File

@ -6,6 +6,7 @@
// //
import Foundation import Foundation
import ZcashLightClientKit
protocol Services { protocol Services {
var networkProvider: ZcashNetworkProvider { get } var networkProvider: ZcashNetworkProvider { get }

View File

@ -1,237 +0,0 @@
//
// ZcashSDKStubs.swift
// secant
//
// Created by Francisco Gindre on 8/6/21.
//
import Foundation
public typealias BlockHeight = Int
public protocol ZcashNetwork {
var networkType: NetworkType { get }
var constants: NetworkConstants.Type { get }
}
public enum NetworkType {
case mainnet
case testnet
var networkId: UInt32 {
switch self {
case .mainnet: return 1
case .testnet: return 0
}
}
}
extension NetworkType {
static func forChainName(_ chainame: String) -> NetworkType? {
switch chainame {
case "test": return .testnet
case "main": return .mainnet
default: return nil
}
}
}
public enum ZcashNetworkBuilder {
public static func network(for networkType: NetworkType) -> ZcashNetwork {
switch networkType {
case .mainnet: return ZcashMainnet()
case .testnet: return ZcashTestnet()
}
}
}
class ZcashTestnet: ZcashNetwork {
var networkType: NetworkType = .testnet
var constants: NetworkConstants.Type = ZcashSDKTestnetConstants.self
}
class ZcashMainnet: ZcashNetwork {
var networkType: NetworkType = .mainnet
var constants: NetworkConstants.Type = ZcashSDKMainnetConstants.self
}
/**
Constants of ZcashLightClientKit.
*/
public enum ZcashSDK {
/**
The number of zatoshi that equal 1 ZEC.
*/
public static var zatoshiPerZEC: BlockHeight = 100_000_000
/**
The theoretical maximum number of blocks in a reorg, due to other bottlenecks in the protocol design.
*/
public static var maxReorgSize = 100
/**
The amount of blocks ahead of the current height where new transactions are set to expire. This value is controlled by the rust backend but it is helpful to know what it is set to and should be kept in sync.
*/
public static var expiryOffset = 20
//
// Defaults
//
/**
Default size of batches of blocks to request from the compact block service.
*/
public static var defaultBatchSize = 100
/**
Default amount of time, in in seconds, to poll for new blocks. Typically, this should be about half the average block time.
*/
public static var defaultPollInterval: TimeInterval = 20
/**
Default attempts at retrying.
*/
public static var defaultRetrie: Int = 5
/**
The default maximum amount of time to wait during retry backoff intervals. Failed loops will never wait longer than this before retrying.
*/
public static var defaultMaxBackoffInterval: TimeInterval = 600
/**
Default number of blocks to rewind when a chain reorg is detected. This should be large enough to recover from the reorg but smaller than the theoretical max reorg size of 100.
*/
public static var defaultRewindDistance: Int = 10
/**
The number of blocks to allow before considering our data to be stale. This usually helps with what to do when returning from the background and is exposed via the Synchronizer's isStale function.
*/
public static var defaultStaleTolerance: Int = 10
/**
Default Name for LibRustZcash data.db
*/
public static var defaultDataDbName = "data.db"
/**
Default Name for Compact Block caches db
*/
public static var defaultCachesDbName = "caches.db"
/**
Default name for pending transactions db
*/
public static var defaultPendingDbName = "pending.db"
/**
File name for the sapling spend params
*/
public static var spendParamFileName = "sapling-spend.params"
/**
File name for the sapling output params
*/
public static var outputParamFileName = "sapling-output.params"
/**
The Url that is used by default in zcashd. We'll want to make this externally configurable, rather than baking it into the SDK but this will do for now, since we're using a cloudfront URL that already redirects.
*/
public static var cloudParamDirURL = "https://z.cash/downloads/"
}
public protocol NetworkConstants {
/**
The height of the first sapling block. When it comes to shielded transactions, we do not need to consider any block prior to this height, at all.
*/
static var saplingActivationHeight: BlockHeight { get }
/**
Default Name for LibRustZcash data.db
*/
static var defaultDataDbName: String { get }
/**
Default Name for Compact Block caches db
*/
static var defaultCachesDbName: String { get }
/**
Default name for pending transactions db
*/
static var defaultPendingDbName: String { get }
static var defaultDbNamePrefix: String { get }
/**
Fixed height where the SDK considers that the ZIP-321 was deployed. This is a workaround for librustzcash not figuring out the tx fee from the tx itself.
*/
static var feeChangeHeight: BlockHeight { get }
static func defaultFee(for height: BlockHeight) -> Int64
}
public extension NetworkConstants {
static func defaultFee(for height: BlockHeight = BlockHeight.max) -> Int64 {
guard height >= feeChangeHeight else { return 10_000 }
return 1_000
}
}
public class ZcashSDKMainnetConstants: NetworkConstants {
/**
The height of the first sapling block. When it comes to shielded transactions, we do not need to consider any blocks prior to this height, at all.
*/
public static var saplingActivationHeight: BlockHeight = 419_200
/**
Default Name for LibRustZcash data.db
*/
public static var defaultDataDbName = "data.db"
/**
Default Name for Compact Block caches db
*/
public static var defaultCachesDbName = "caches.db"
/**
Default name for pending transactions db
*/
public static var defaultPendingDbName = "pending.db"
public static var defaultDbNamePrefix = "ZcashSdk_mainnet_"
public static var feeChangeHeight: BlockHeight = 1_077_550
private init() {}
}
public class ZcashSDKTestnetConstants: NetworkConstants {
/**
The height of the first sapling block. When it comes to shielded transactions, we do not need to consider any blocks prior to this height, at all.
*/
public static var saplingActivationHeight: BlockHeight = 280_000
/**
Default Name for LibRustZcash data.db
*/
public static var defaultDataDbName = "data.db"
/**
Default Name for Compact Block caches db
*/
public static var defaultCachesDbName = "caches.db"
/**
Default name for pending transactions db
*/
public static var defaultPendingDbName = "pending.db"
public static var defaultDbNamePrefix = "ZcashSdk_testnet_"
/**
Estimated height where wallets are supposed to change the fee
*/
public static var feeChangeHeight: BlockHeight = 1_028_500
private init() {}
}

View File

@ -0,0 +1,126 @@
//
// CombineSynchronizer.swift
// secant-testnet
//
// Created by Lukáš Korba on 13.04.2022.
//
import Foundation
import ZcashLightClientKit
import Combine
struct Balance: WalletBalance, Equatable {
var verified: Int64
var total: Int64
}
protocol CombineSynchronizer {
var synchronizer: SDKSynchronizer? { get }
var shieldedBalance: CurrentValueSubject<WalletBalance, Never> { get }
func prepareWith(initializer: Initializer) throws
func start(retry: Bool) throws
func stop()
func updatePublishers()
func getTransparentAddress(account: Int) -> TransparentAddress?
func getShieldedAddress(account: Int) -> SaplingShieldedAddress?
}
extension CombineSynchronizer {
func start() throws {
try start(retry: false)
}
func getTransparentAddress() -> TransparentAddress? {
getTransparentAddress(account: 0)
}
func getShieldedAddress() -> SaplingShieldedAddress? {
getShieldedAddress(account: 0)
}
}
class LiveCombineSynchronizer: CombineSynchronizer {
private var cancellables: [AnyCancellable] = []
private(set) var synchronizer: SDKSynchronizer?
private(set) var shieldedBalance: CurrentValueSubject<WalletBalance, Never>
init() {
self.shieldedBalance = CurrentValueSubject<WalletBalance, Never>(Balance(verified: 0, total: 0))
}
func prepareWith(initializer: Initializer) throws {
synchronizer = try SDKSynchronizer(initializer: initializer)
NotificationCenter.default.publisher(for: .synchronizerSynced)
.receive(on: DispatchQueue.main)
.sink(receiveValue: { [weak self] _ in
self?.updatePublishers()
})
.store(in: &cancellables)
try synchronizer?.prepare()
}
func start(retry: Bool) throws {
try synchronizer?.start(retry: retry)
}
func stop() {
synchronizer?.stop()
}
func updatePublishers() {
if let shieldedVerifiedBalance = synchronizer?.getShieldedVerifiedBalance(),
let shieldedTotalBalance = synchronizer?.getShieldedBalance(accountIndex: 0) {
shieldedBalance.send(Balance(verified: shieldedVerifiedBalance, total: shieldedTotalBalance))
} else {
shieldedBalance.send(Balance(verified: 0, total: 0))
}
}
func getTransparentAddress(account: Int) -> TransparentAddress? {
synchronizer?.getTransparentAddress(accountIndex: account)
}
func getShieldedAddress(account: Int) -> SaplingShieldedAddress? {
synchronizer?.getShieldedAddress(accountIndex: account)
}
}
class MockCombineSynchronizer: CombineSynchronizer {
private(set) var synchronizer: SDKSynchronizer?
private(set) var shieldedBalance: CurrentValueSubject<WalletBalance, Never>
init() {
self.shieldedBalance = CurrentValueSubject<WalletBalance, Never>(Balance(verified: 0, total: 0))
}
func prepareWith(initializer: Initializer) throws {
synchronizer = try SDKSynchronizer(initializer: initializer)
shieldedBalance = CurrentValueSubject<WalletBalance, Never>(
Balance(verified: 0, total: 0)
)
try synchronizer?.prepare()
}
func start(retry: Bool) throws {
try synchronizer?.start(retry: retry)
}
func stop() {
synchronizer?.stop()
}
func updatePublishers() {
}
func getTransparentAddress(account: Int) -> TransparentAddress? {
synchronizer?.getTransparentAddress(accountIndex: account)
}
func getShieldedAddress(account: Int) -> SaplingShieldedAddress? {
synchronizer?.getShieldedAddress(accountIndex: account)
}
}

View File

@ -6,11 +6,16 @@
// //
import Foundation import Foundation
import ZcashLightClientKit
struct DatabaseFiles { struct DatabaseFiles {
enum DatabaseFilesError: Error { enum DatabaseFilesError: Error {
case getDocumentsURL case getDocumentsURL
case getCacheURL
case getDataURL case getDataURL
case getOutputParamsURL
case getPendingURL
case getSpendParamsURL
case nukeFiles case nukeFiles
case filesPresentCheck case filesPresentCheck
} }
@ -29,27 +34,83 @@ struct DatabaseFiles {
} }
} }
func dataDbURL(for network: String) throws -> URL { func cacheDbURL(for network: ZcashNetwork) throws -> URL {
do { do {
return try documentsDirectory().appendingPathComponent("zcash.\(network).data.db", isDirectory: false) return try documentsDirectory()
.appendingPathComponent(
"\(network.constants.defaultDbNamePrefix)cache.db",
isDirectory: false
)
} catch {
throw DatabaseFilesError.getCacheURL
}
}
func dataDbURL(for network: ZcashNetwork) throws -> URL {
do {
return try documentsDirectory()
.appendingPathComponent(
"\(network.constants.defaultDbNamePrefix)data.db",
isDirectory: false
)
} catch { } catch {
throw DatabaseFilesError.getDataURL throw DatabaseFilesError.getDataURL
} }
} }
func areDbFilesPresent(for network: String) throws -> Bool { func outputParamsURL(for network: ZcashNetwork) throws -> URL {
do { do {
let dataDatabaseURL = try dataDbURL(for: network) return try documentsDirectory()
return fileManager.fileExists(dataDatabaseURL.path) .appendingPathComponent(
"\(network.constants.defaultDbNamePrefix)sapling-output.params",
isDirectory: false
)
} catch {
throw DatabaseFilesError.getOutputParamsURL
}
}
func pendingDbURL(for network: ZcashNetwork) throws -> URL {
do {
return try documentsDirectory()
.appendingPathComponent(
"\(network.constants.defaultDbNamePrefix)pending.db",
isDirectory: false
)
} catch {
throw DatabaseFilesError.getPendingURL
}
}
func spendParamsURL(for network: ZcashNetwork) throws -> URL {
do {
return try documentsDirectory()
.appendingPathComponent(
"\(network.constants.defaultDbNamePrefix)sapling-spend.params",
isDirectory: false
)
} catch {
throw DatabaseFilesError.getSpendParamsURL
}
}
func areDbFilesPresent(for network: ZcashNetwork) throws -> Bool {
do {
let dataDbURL = try dataDbURL(for: network)
return fileManager.fileExists(dataDbURL.path)
} catch { } catch {
throw DatabaseFilesError.filesPresentCheck throw DatabaseFilesError.filesPresentCheck
} }
} }
func nukeDbFiles(for network: String) throws { func nukeDbFiles(for network: ZcashNetwork) throws {
do { do {
let dataDatabaseURL = try dataDbURL(for: network) let cacheDbURL = try cacheDbURL(for: network)
try fileManager.removeItem(dataDatabaseURL) let dataDbURL = try dataDbURL(for: network)
let pendingDbURL = try pendingDbURL(for: network)
try fileManager.removeItem(cacheDbURL)
try fileManager.removeItem(dataDbURL)
try fileManager.removeItem(pendingDbURL)
} catch { } catch {
throw DatabaseFilesError.nukeFiles throw DatabaseFilesError.nukeFiles
} }
@ -58,9 +119,13 @@ struct DatabaseFiles {
struct DatabaseFilesInteractor { struct DatabaseFilesInteractor {
let documentsDirectory: () throws -> URL let documentsDirectory: () throws -> URL
let dataDbURLFor: (String) throws -> URL let cacheDbURLFor: (ZcashNetwork) throws -> URL
let areDbFilesPresentFor: (String) throws -> Bool let dataDbURLFor: (ZcashNetwork) throws -> URL
let nukeDbFilesFor: (String) throws -> Void let outputParamsURLFor: (ZcashNetwork) throws -> URL
let pendingDbURLFor: (ZcashNetwork) throws -> URL
let spendParamsURLFor: (ZcashNetwork) throws -> URL
let areDbFilesPresentFor: (ZcashNetwork) throws -> Bool
let nukeDbFilesFor: (ZcashNetwork) throws -> Void
} }
extension DatabaseFilesInteractor { extension DatabaseFilesInteractor {
@ -69,9 +134,21 @@ extension DatabaseFilesInteractor {
documentsDirectory: { documentsDirectory: {
try databaseFiles.documentsDirectory() try databaseFiles.documentsDirectory()
}, },
cacheDbURLFor: { network in
try databaseFiles.cacheDbURL(for: network)
},
dataDbURLFor: { network in dataDbURLFor: { network in
try databaseFiles.dataDbURL(for: network) try databaseFiles.dataDbURL(for: network)
}, },
outputParamsURLFor: { network in
try databaseFiles.outputParamsURL(for: network)
},
pendingDbURLFor: { network in
try databaseFiles.pendingDbURL(for: network)
},
spendParamsURLFor: { network in
try databaseFiles.spendParamsURL(for: network)
},
areDbFilesPresentFor: { network in areDbFilesPresentFor: { network in
try databaseFiles.areDbFilesPresent(for: network) try databaseFiles.areDbFilesPresent(for: network)
}, },
@ -85,9 +162,21 @@ extension DatabaseFilesInteractor {
documentsDirectory: { documentsDirectory: {
throw DatabaseFiles.DatabaseFilesError.getDocumentsURL throw DatabaseFiles.DatabaseFilesError.getDocumentsURL
}, },
cacheDbURLFor: { _ in
throw DatabaseFiles.DatabaseFilesError.getCacheURL
},
dataDbURLFor: { _ in dataDbURLFor: { _ in
throw DatabaseFiles.DatabaseFilesError.getDataURL throw DatabaseFiles.DatabaseFilesError.getDataURL
}, },
outputParamsURLFor: { _ in
throw DatabaseFiles.DatabaseFilesError.getOutputParamsURL
},
pendingDbURLFor: { _ in
throw DatabaseFiles.DatabaseFilesError.getPendingURL
},
spendParamsURLFor: { _ in
throw DatabaseFiles.DatabaseFilesError.getSpendParamsURL
},
areDbFilesPresentFor: { _ in areDbFilesPresentFor: { _ in
throw DatabaseFiles.DatabaseFilesError.filesPresentCheck throw DatabaseFiles.DatabaseFilesError.filesPresentCheck
}, },

View File

@ -0,0 +1,60 @@
//
// DebugMenu.swift
// secant-testnet
//
// Created by Lukáš Korba on 15.04.2022.
//
import SwiftUI
// TODO: Make sure this code will never be in the production (app store) build, issue 273 (https://github.com/zcash/secant-ios-wallet/issues/273)
// swiftlint:disable:next private_over_fileprivate strict_fileprivate
fileprivate struct DebugMenuModifier: ViewModifier {
enum DragState {
case inactive
case pressing
case dragging(translation: CGSize)
}
@GestureState var dragState = DragState.inactive
var minimumDuration: Double
let action: () -> Void
func body(content: Content) -> some View {
let longPressDrag = LongPressGesture(minimumDuration: minimumDuration)
.sequenced(before: DragGesture())
.updating($dragState) { value, state, _ in
switch value {
// Long press begins.
case .first(true):
state = .pressing
// Long press confirmed, dragging may begin.
case .second(true, let drag):
state = .dragging(translation: drag?.translation ?? .zero)
// Dragging ended or the long press cancelled.
default:
state = .inactive
}
}
.onEnded { value in
guard case .second(true, let drag?) = value else { return }
if drag.translation.height > 0 {
action()
}
}
return content.gesture(longPressDrag)
}
}
extension View {
func accessDebugMenuWithHiddenGesture(minimumDuration: Double = 0.75, action: @escaping () -> Void ) -> some View {
self.modifier(
DebugMenuModifier(minimumDuration: minimumDuration) {
action()
}
)
}
}

View File

@ -14,3 +14,7 @@ enum InitializationState: Equatable {
case filesMissing case filesMissing
case uninitialized case uninitialized
} }
enum SDKInitializationError: Error {
case failed
}

View File

@ -0,0 +1,15 @@
//
// Int64+Zcash.swift
// secant-testnet
//
// Created by Lukáš Korba on 15.04.2022.
//
import Foundation
// TODO: Improve with decimals and zatoshi type, issue #272 (https://github.com/zcash/secant-ios-wallet/issues/272)
extension Int64 {
func asHumanReadableZecBalance() -> Double {
Double(self) / Double(100_000_000)
}
}

View File

@ -7,6 +7,7 @@
import Foundation import Foundation
import MnemonicSwift import MnemonicSwift
import ZcashLightClientKit
/// Zcash implementation of the keychain that is not universal but designed to deliver functionality needed by the wallet itself. /// Zcash implementation of the keychain that is not universal but designed to deliver functionality needed by the wallet itself.
/// All the APIs should be thread safe according to official doc: /// All the APIs should be thread safe according to official doc:
@ -35,6 +36,7 @@ struct WalletStorage {
} }
private let secItem: WrappedSecItem private let secItem: WrappedSecItem
var zcashStoredWalletPrefix = ""
init(secItem: WrappedSecItem) { init(secItem: WrappedSecItem) {
self.secItem = secItem self.secItem = secItem
@ -123,7 +125,7 @@ struct WalletStorage {
throw KeychainError.encoding throw KeychainError.encoding
} }
try updateData(data, forKey: WalletStorage.Constants.zcashStoredWallet) try updateData(data, forKey: Constants.zcashStoredWallet)
} catch { } catch {
throw error throw error
} }
@ -158,7 +160,7 @@ struct WalletStorage {
func baseQuery(forAccount account: String = "", andKey forKey: String) -> [String: Any] { func baseQuery(forAccount account: String = "", andKey forKey: String) -> [String: Any] {
let query:[ String: AnyObject ] = [ let query:[ String: AnyObject ] = [
/// Uniquely identify this keychain accessor /// Uniquely identify this keychain accessor
kSecAttrService as String: forKey as AnyObject, kSecAttrService as String: (zcashStoredWalletPrefix + forKey) as AnyObject,
kSecAttrAccount as String: account as AnyObject, kSecAttrAccount as String: account as AnyObject,
kSecClass as String: kSecClassGenericPassword, kSecClass as String: kSecClassGenericPassword,
/// The data in the keychain item can be accessed only while the device is unlocked by the user. /// The data in the keychain item can be accessed only while the device is unlocked by the user.

View File

@ -7,6 +7,7 @@
import Foundation import Foundation
import MnemonicSwift import MnemonicSwift
import ZcashLightClientKit
/// Representation of the wallet stored in the persistent storage (typically keychain, handled by `WalletStorage`). /// Representation of the wallet stored in the persistent storage (typically keychain, handled by `WalletStorage`).
struct StoredWallet: Codable, Equatable { struct StoredWallet: Codable, Equatable {

View File

@ -0,0 +1,47 @@
//
// ZCashSDKEnvironment.swift
// secant-testnet
//
// Created by Lukáš Korba on 13.04.2022.
//
import Foundation
import ZcashLightClientKit
// swiftlint:disable:next private_over_fileprivate strict_fileprivate
fileprivate enum ZcashSDKConstants {
static let endpointMainnetAddress = "lightwalletd.electriccoin.co"
static let endpointTestnetAddress = "lightwalletd.testnet.electriccoin.co"
static let endpointPort = 9067
static let defaultBlockHeight = 1_629_724
}
struct ZCashSDKEnvironment {
let defaultBirthday: BlockHeight
let endpoint: LightWalletEndpoint
let lightWalletService: LightWalletService
let network: ZcashNetwork
let isMainnet: () -> Bool
}
extension ZCashSDKEnvironment {
static let mainnet = ZCashSDKEnvironment(
defaultBirthday: BlockHeight(ZcashSDKConstants.defaultBlockHeight),
endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointMainnetAddress, port: ZcashSDKConstants.endpointPort),
lightWalletService: LightWalletGRPCService(
endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointMainnetAddress, port: ZcashSDKConstants.endpointPort)
),
network: ZcashNetworkBuilder.network(for: .mainnet),
isMainnet: { true }
)
static let testnet = ZCashSDKEnvironment(
defaultBirthday: BlockHeight(ZcashSDKConstants.defaultBlockHeight),
endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointTestnetAddress, port: ZcashSDKConstants.endpointPort),
lightWalletService: LightWalletGRPCService(
endpoint: LightWalletEndpoint(address: ZcashSDKConstants.endpointTestnetAddress, port: ZcashSDKConstants.endpointPort)
),
network: ZcashNetworkBuilder.network(for: .testnet),
isMainnet: { false }
)
}

View File

@ -13,20 +13,24 @@ class AppReducerTests: XCTestCase {
static let testScheduler = DispatchQueue.test static let testScheduler = DispatchQueue.test
let testEnvironment = AppEnvironment( let testEnvironment = AppEnvironment(
combineSynchronizer: MockCombineSynchronizer(),
databaseFiles: .throwing, databaseFiles: .throwing,
scheduler: testScheduler.eraseToAnyScheduler(),
mnemonicSeedPhraseProvider: .mock, mnemonicSeedPhraseProvider: .mock,
scheduler: testScheduler.eraseToAnyScheduler(),
walletStorage: .throwing, walletStorage: .throwing,
wrappedDerivationTool: .live() wrappedDerivationTool: .live(),
zcashSDKEnvironment: .mainnet
) )
func testWalletInitializationState_Uninitialized() throws { func testWalletInitializationState_Uninitialized() throws {
let uninitializedEnvironment = AppEnvironment( let uninitializedEnvironment = AppEnvironment(
combineSynchronizer: MockCombineSynchronizer(),
databaseFiles: .throwing, databaseFiles: .throwing,
scheduler: DispatchQueue.test.eraseToAnyScheduler(),
mnemonicSeedPhraseProvider: .mock, mnemonicSeedPhraseProvider: .mock,
scheduler: DispatchQueue.test.eraseToAnyScheduler(),
walletStorage: .throwing, walletStorage: .throwing,
wrappedDerivationTool: .live() wrappedDerivationTool: .live(),
zcashSDKEnvironment: .mainnet
) )
let walletState = AppReducer.walletInitializationState(uninitializedEnvironment) let walletState = AppReducer.walletInitializationState(uninitializedEnvironment)
@ -42,11 +46,13 @@ class AppReducerTests: XCTestCase {
) )
let keysMissingEnvironment = AppEnvironment( let keysMissingEnvironment = AppEnvironment(
combineSynchronizer: MockCombineSynchronizer(),
databaseFiles: .live(databaseFiles: DatabaseFiles(fileManager: wfmMock)), databaseFiles: .live(databaseFiles: DatabaseFiles(fileManager: wfmMock)),
scheduler: Self.testScheduler.eraseToAnyScheduler(),
mnemonicSeedPhraseProvider: .mock, mnemonicSeedPhraseProvider: .mock,
scheduler: Self.testScheduler.eraseToAnyScheduler(),
walletStorage: .throwing, walletStorage: .throwing,
wrappedDerivationTool: .live() wrappedDerivationTool: .live(),
zcashSDKEnvironment: .mainnet
) )
let walletState = AppReducer.walletInitializationState(keysMissingEnvironment) let walletState = AppReducer.walletInitializationState(keysMissingEnvironment)
@ -62,11 +68,13 @@ class AppReducerTests: XCTestCase {
) )
let keysMissingEnvironment = AppEnvironment( let keysMissingEnvironment = AppEnvironment(
combineSynchronizer: MockCombineSynchronizer(),
databaseFiles: .live(databaseFiles: DatabaseFiles(fileManager: wfmMock)), databaseFiles: .live(databaseFiles: DatabaseFiles(fileManager: wfmMock)),
scheduler: Self.testScheduler.eraseToAnyScheduler(),
mnemonicSeedPhraseProvider: .mock, mnemonicSeedPhraseProvider: .mock,
scheduler: Self.testScheduler.eraseToAnyScheduler(),
walletStorage: .throwing, walletStorage: .throwing,
wrappedDerivationTool: .live() wrappedDerivationTool: .live(),
zcashSDKEnvironment: .testnet
) )
let walletState = AppReducer.walletInitializationState(keysMissingEnvironment) let walletState = AppReducer.walletInitializationState(keysMissingEnvironment)
@ -118,7 +126,12 @@ class AppReducerTests: XCTestCase {
state.appInitializationState = .filesMissing state.appInitializationState = .filesMissing
} }
store.receive(.initializeApp) { state in store.receive(.initializeSDK) { state in
// failed is expected because environment is throwing errors
state.appInitializationState = .failed
}
store.receive(.checkBackupPhraseValidation) { state in
// failed is expected because environment is throwing errors // failed is expected because environment is throwing errors
state.appInitializationState = .failed state.appInitializationState = .failed
} }
@ -133,7 +146,12 @@ class AppReducerTests: XCTestCase {
store.send(.respondToWalletInitializationState(.initialized)) store.send(.respondToWalletInitializationState(.initialized))
store.receive(.initializeApp) { state in store.receive(.initializeSDK) { state in
// failed is expected because environment is throwing errors
state.appInitializationState = .failed
}
store.receive(.checkBackupPhraseValidation) { state in
// failed is expected because environment is throwing errors // failed is expected because environment is throwing errors
state.appInitializationState = .failed state.appInitializationState = .failed
} }

View File

@ -6,6 +6,7 @@
// //
import XCTest import XCTest
import ZcashLightClientKit
@testable import secant_testnet @testable import secant_testnet
extension String: Error {} extension String: Error {}
@ -14,7 +15,11 @@ extension DatabaseFiles.DatabaseFilesError {
var debugValue: String { var debugValue: String {
switch self { switch self {
case .getDocumentsURL: return "getDocumentsURL" case .getDocumentsURL: return "getDocumentsURL"
case .getCacheURL: return "getCacheURL"
case .getDataURL: return "getDataURL" case .getDataURL: return "getDataURL"
case .getOutputParamsURL: return "getOutputParamsURL"
case .getPendingURL: return "getPendingURL"
case .getSpendParamsURL: return "getSpendParamsURL"
case .nukeFiles: return "nukeFiles" case .nukeFiles: return "nukeFiles"
case .filesPresentCheck: return "filesPresentCheck" case .filesPresentCheck: return "filesPresentCheck"
} }
@ -22,6 +27,8 @@ extension DatabaseFiles.DatabaseFilesError {
} }
class DatabaseFilesTests: XCTestCase { class DatabaseFilesTests: XCTestCase {
let network = ZcashNetworkBuilder.network(for: .testnet)
func testFailingDocumentsDirectory() throws { func testFailingDocumentsDirectory() throws {
let mockedFileManager = WrappedFileManager( let mockedFileManager = WrappedFileManager(
url: { _, _, _, _ in throw "some error" }, url: { _, _, _, _ in throw "some error" },
@ -60,7 +67,7 @@ class DatabaseFilesTests: XCTestCase {
let dfInteractor = DatabaseFilesInteractor.live(databaseFiles: DatabaseFiles(fileManager: mockedFileManager)) let dfInteractor = DatabaseFilesInteractor.live(databaseFiles: DatabaseFiles(fileManager: mockedFileManager))
do { do {
_ = try dfInteractor.dataDbURLFor("") _ = try dfInteractor.dataDbURLFor(network)
XCTFail("DatabaseFiles: `testFailingDataDbURL` expected to fail but passed with no error.") XCTFail("DatabaseFiles: `testFailingDataDbURL` expected to fail but passed with no error.")
} catch { } catch {
@ -88,7 +95,7 @@ class DatabaseFilesTests: XCTestCase {
let dfInteractor = DatabaseFilesInteractor.live(databaseFiles: DatabaseFiles(fileManager: mockedFileManager)) let dfInteractor = DatabaseFilesInteractor.live(databaseFiles: DatabaseFiles(fileManager: mockedFileManager))
do { do {
let areFilesPresent = try dfInteractor.areDbFilesPresentFor("") let areFilesPresent = try dfInteractor.areDbFilesPresentFor(network)
XCTAssertTrue(areFilesPresent, "DatabaseFiles: `testDatabaseFilesPresent` is expected to be true but it's \(areFilesPresent)") XCTAssertTrue(areFilesPresent, "DatabaseFiles: `testDatabaseFilesPresent` is expected to be true but it's \(areFilesPresent)")
} catch { } catch {
@ -106,7 +113,7 @@ class DatabaseFilesTests: XCTestCase {
let dfInteractor = DatabaseFilesInteractor.live(databaseFiles: DatabaseFiles(fileManager: mockedFileManager)) let dfInteractor = DatabaseFilesInteractor.live(databaseFiles: DatabaseFiles(fileManager: mockedFileManager))
do { do {
let areFilesPresent = try dfInteractor.areDbFilesPresentFor("") let areFilesPresent = try dfInteractor.areDbFilesPresentFor(network)
XCTAssertFalse(areFilesPresent, "DatabaseFiles: `testDatabaseFilesNotPresent` is expected to be false but it's \(areFilesPresent)") XCTAssertFalse(areFilesPresent, "DatabaseFiles: `testDatabaseFilesNotPresent` is expected to be false but it's \(areFilesPresent)")
} catch { } catch {
@ -124,7 +131,7 @@ class DatabaseFilesTests: XCTestCase {
let dfInteractor = DatabaseFilesInteractor.live(databaseFiles: DatabaseFiles(fileManager: mockedFileManager)) let dfInteractor = DatabaseFilesInteractor.live(databaseFiles: DatabaseFiles(fileManager: mockedFileManager))
do { do {
_ = try dfInteractor.areDbFilesPresentFor("") _ = try dfInteractor.areDbFilesPresentFor(network)
XCTFail("DatabaseFiles: `testDatabaseFilesPresentFailure` expected to fail but passed with no error.") XCTFail("DatabaseFiles: `testDatabaseFilesPresentFailure` expected to fail but passed with no error.")
} catch { } catch {
@ -152,7 +159,7 @@ class DatabaseFilesTests: XCTestCase {
let dfInteractor = DatabaseFilesInteractor.live(databaseFiles: DatabaseFiles(fileManager: mockedFileManager)) let dfInteractor = DatabaseFilesInteractor.live(databaseFiles: DatabaseFiles(fileManager: mockedFileManager))
do { do {
_ = try dfInteractor.nukeDbFilesFor("") _ = try dfInteractor.nukeDbFilesFor(network)
XCTFail("DatabaseFiles: `testNukeFiles_RemoveFileFailure` expected to fail but passed with no error.") XCTFail("DatabaseFiles: `testNukeFiles_RemoveFileFailure` expected to fail but passed with no error.")
} catch { } catch {
@ -180,7 +187,7 @@ class DatabaseFilesTests: XCTestCase {
let dfInteractor = DatabaseFilesInteractor.live(databaseFiles: DatabaseFiles(fileManager: mockedFileManager)) let dfInteractor = DatabaseFilesInteractor.live(databaseFiles: DatabaseFiles(fileManager: mockedFileManager))
do { do {
_ = try dfInteractor.nukeDbFilesFor("") _ = try dfInteractor.nukeDbFilesFor(network)
XCTFail("DatabaseFiles: `testNukeFiles_URLFailure` expected to fail but passed with no error.") XCTFail("DatabaseFiles: `testNukeFiles_URLFailure` expected to fail but passed with no error.")
} catch { } catch {

View File

@ -7,6 +7,7 @@
import XCTest import XCTest
import MnemonicSwift import MnemonicSwift
import ZcashLightClientKit
@testable import secant_testnet @testable import secant_testnet
extension WalletStorage.WalletStorageError { extension WalletStorage.WalletStorageError {
@ -26,9 +27,10 @@ class WalletStorageTests: XCTestCase {
let seedPhrase = "one two three" let seedPhrase = "one two three"
let language = MnemonicLanguageType.english let language = MnemonicLanguageType.english
var storage = WalletStorage(secItem: .live) var storage = WalletStorage(secItem: .live)
override func setUp() { override func setUp() {
super.setUp() super.setUp()
storage.zcashStoredWalletPrefix = "test_"
deleteData(forKey: WalletStorage.Constants.zcashStoredWallet) deleteData(forKey: WalletStorage.Constants.zcashStoredWallet)
} }