From 3e6afc69a8650c55b85a0f642dd4cf385640965c Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Tue, 12 Mar 2024 15:34:00 +0100 Subject: [PATCH] [#1024] KeysMissing when restored to a new iPhone - validation flow done for the 2 out of 3 flows - 1. when the restore path is selected and the seed doesn't match the DB - 2. when create new wallet is selected - (missing) 3. seed is validated (waiting on API) [#1024] KeysMissing when restored to a new iPhone - preparations for SDK 2.1.0 [#1024] KeysMissing when restored to a new iPhone - Seed validation API used and integrated - There's an error in rust that must be resolved first [#1024] KeysMissing when restored to a new iPhone - rebased [#1024] KeysMissing when restored to a new iPhone - updated to use latest SDK's isSeedRelevantToAnyDerivedAccount [#1024] KeysMissing when restored to a new iPhone - unstable SDK branch test [#1024] KeysMissing when restored to a new iPhone - version bump [#1024] KeysMissing when restored to a new iPhone - bugfix - packae redirected to ECCs repo [#1024] KeysMissing when restored to a new iPhone - tests fixed --- CHANGELOG.md | 2 + modules/Package.swift | 51 +++---- .../SDKSynchronizerInterface.swift | 14 +- .../SDKSynchronizer/SDKSynchronizerLive.swift | 3 + .../SDKSynchronizer/SDKSynchronizerTest.swift | 14 +- .../BalanceBreakdownStore.swift | 9 +- .../BalanceBreakdownView.swift | 1 + modules/Sources/Features/Home/HomeStore.swift | 5 +- .../ImportWallet/ImportWalletStore.swift | 6 +- .../ImportWallet/ImportWalletView.swift | 2 +- .../Features/Root/RootInitialization.swift | 136 +++++++++++++----- modules/Sources/Features/Root/RootStore.swift | 28 +++- .../SecurityWarningStore.swift | 4 + .../Features/SendFlow/SendFlowStore.swift | 6 +- .../Sources/Models/SyncStatusSnapshot.swift | 3 +- secant.xcodeproj/project.pbxproj | 18 +-- .../xcshareddata/swiftpm/Package.resolved | 12 +- .../RootTests/AppInitializationTests.swift | 6 +- secantTests/RootTests/RootTests.swift | 6 +- 19 files changed, 224 insertions(+), 102 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f1385b..2c72437 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ directly impact users rather than highlighting other crucial architectural updat ### Added - Proposal API integrated with error handling for multi-transaction Proposals. - Privacy info manifest. +- Orchard support. +- Seed validation for case when Zashi is migrated to another device. ### Fixed - White area above the keyboard has been removed. diff --git a/modules/Package.swift b/modules/Package.swift index 3ca7b3e..4a2a6ee 100644 --- a/modules/Package.swift +++ b/modules/Package.swift @@ -68,7 +68,7 @@ let package = Package( .package(url: "https://github.com/pointfreeco/swift-case-paths", from: "1.1.0"), .package(url: "https://github.com/pointfreeco/swift-url-routing", from: "0.6.0"), .package(url: "https://github.com/zcash-hackworks/MnemonicSwift", from: "2.2.4"), - .package(url: "https://github.com/zcash/ZcashLightClientKit", from: "2.0.11"), + .package(url: "https://github.com/Electric-Coin-Company/zcash-swift-wallet-sdk", from: "2.1.0"), .package(url: "https://github.com/firebase/firebase-ios-sdk", from: "10.17.0") ], targets: [ @@ -80,7 +80,7 @@ let package = Package( "UIComponents", "Utils", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Features/AddressDetails" ), @@ -116,7 +116,7 @@ let package = Package( "WalletStorage", "ZcashSDKEnvironment", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Features/BalanceBreakdown" ), @@ -126,7 +126,7 @@ let package = Package( "Generated", "Utils", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Dependencies/BalanceFormatter" ), @@ -151,7 +151,7 @@ let package = Package( "FileManager", "Utils", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Dependencies/DatabaseFiles" ), @@ -168,7 +168,7 @@ let package = Package( "DerivationTool", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "URLRouting", package: "swift-url-routing"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Dependencies/Deeplink" ), @@ -177,7 +177,7 @@ let package = Package( dependencies: [ "Utils", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Dependencies/DerivationTool" ), @@ -195,7 +195,7 @@ let package = Package( "LogsHandler", "Utils", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Features/ExportLogs" ), @@ -235,7 +235,7 @@ let package = Package( "TransactionList", "ZcashSDKEnvironment", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Features/Home" ), @@ -249,7 +249,7 @@ let package = Package( "WalletStorage", "ZcashSDKEnvironment", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Features/ImportWallet" ), @@ -303,7 +303,7 @@ let package = Package( "SecurityWarning", "UIComponents", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Features/OnboardingFlow" ), @@ -352,7 +352,7 @@ let package = Package( "Utils", "WalletStorage", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Features/RecoveryPhraseDisplay" ), @@ -401,7 +401,7 @@ let package = Package( "Welcome", "ZcashSDKEnvironment", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Features/Root" ), @@ -413,7 +413,7 @@ let package = Package( "SendFlow", "TransactionList", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Features/Sandbox" ), @@ -427,7 +427,7 @@ let package = Package( "Utils", "ZcashSDKEnvironment", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Features/Scan" ), @@ -438,7 +438,7 @@ let package = Package( "Models", "ZcashSDKEnvironment", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Dependencies/SDKSynchronizer" ), @@ -483,7 +483,7 @@ let package = Package( "WalletStorage", "ZcashSDKEnvironment", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Features/SendFlow" ), @@ -496,7 +496,7 @@ let package = Package( "UserDefaults", "ZcashSDKEnvironment", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Features/ServerSetup" ), @@ -515,7 +515,7 @@ let package = Package( "SupportDataGenerator", "UIComponents", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Features/Settings" ), @@ -560,7 +560,7 @@ let package = Package( "Settings", "UIComponents", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Features/Tabs" ), @@ -576,7 +576,7 @@ let package = Package( "Utils", "ZcashSDKEnvironment", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Features/TransactionList" ), @@ -587,6 +587,7 @@ let package = Package( "DerivationTool", "Generated", "NumberFormatter", + "SupportDataGenerator", "Utils", "ZcashSDKEnvironment" ], @@ -597,7 +598,7 @@ let package = Package( dependencies: [ "DerivationTool", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Dependencies/URIParser" ), @@ -619,7 +620,7 @@ let package = Package( .target( name: "Utils", dependencies: [ - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit"), + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk"), .product(name: "CasePaths", package: "swift-case-paths"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture") ], @@ -642,7 +643,7 @@ let package = Package( "MnemonicClient", "Models", .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit") + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk") ], path: "Sources/Dependencies/WalletStorage" ), @@ -660,7 +661,7 @@ let package = Package( name: "ZcashSDKEnvironment", dependencies: [ "UserDefaults", - .product(name: "ZcashLightClientKit", package: "ZcashLightClientKit"), + .product(name: "ZcashLightClientKit", package: "zcash-swift-wallet-sdk"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture") ], path: "Sources/Dependencies/ZcashSDKEnvironment" diff --git a/modules/Sources/Dependencies/SDKSynchronizer/SDKSynchronizerInterface.swift b/modules/Sources/Dependencies/SDKSynchronizer/SDKSynchronizerInterface.swift index 2e12fc5..1f5e296 100644 --- a/modules/Sources/Dependencies/SDKSynchronizer/SDKSynchronizerInterface.swift +++ b/modules/Sources/Dependencies/SDKSynchronizer/SDKSynchronizerInterface.swift @@ -28,24 +28,24 @@ public struct SDKSynchronizerClient { public let stateStream: () -> AnyPublisher public let eventStream: () -> AnyPublisher public let latestState: () -> SynchronizerState - + public let prepareWith: ([UInt8], BlockHeight, WalletInitMode) async throws -> Void public let start: (_ retry: Bool) async throws -> Void public let stop: () -> Void public let isSyncing: () -> Bool public let isInitialized: () -> Bool - + public let rewind: (RewindPolicy) -> AnyPublisher - + public var getAllTransactions: () async throws -> [TransactionState] - + public let getUnifiedAddress: (_ account: Int) async throws -> UnifiedAddress? public let getTransparentAddress: (_ account: Int) async throws -> TransparentAddress? public let getSaplingAddress: (_ accountIndex: Int) async throws -> SaplingAddress? - + public var sendTransaction: (UnifiedSpendingKey, Zatoshi, Recipient, Memo?) async throws -> TransactionState public let shieldFunds: (UnifiedSpendingKey, Memo, Zatoshi) async throws -> TransactionState - + public var wipe: () -> AnyPublisher? public var switchToEndpoint: (LightWalletEndpoint) async throws -> Void @@ -54,4 +54,6 @@ public struct SDKSynchronizerClient { public var proposeTransfer: (Int, Recipient, Zatoshi, Memo?) async throws -> Proposal public var createProposedTransactions: (Proposal, UnifiedSpendingKey) async throws -> CreateProposedTransactionsResult public var proposeShielding: (Int, Zatoshi, Memo, TransparentAddress?) async throws -> Proposal? + + public var isSeedRelevantToAnyDerivedAccount: ([UInt8]) async throws -> Bool } diff --git a/modules/Sources/Dependencies/SDKSynchronizer/SDKSynchronizerLive.swift b/modules/Sources/Dependencies/SDKSynchronizer/SDKSynchronizerLive.swift index a24dde7..85fa9aa 100644 --- a/modules/Sources/Dependencies/SDKSynchronizer/SDKSynchronizerLive.swift +++ b/modules/Sources/Dependencies/SDKSynchronizer/SDKSynchronizerLive.swift @@ -173,6 +173,9 @@ extension SDKSynchronizerClient: DependencyKey { memo: memo, transparentReceiver: transparentReceiver ) + }, + isSeedRelevantToAnyDerivedAccount: { seed in + try await synchronizer.isSeedRelevantToAnyDerivedAccount(seed: seed) } ) } diff --git a/modules/Sources/Dependencies/SDKSynchronizer/SDKSynchronizerTest.swift b/modules/Sources/Dependencies/SDKSynchronizer/SDKSynchronizerTest.swift index f7b53f7..54d7011 100644 --- a/modules/Sources/Dependencies/SDKSynchronizer/SDKSynchronizerTest.swift +++ b/modules/Sources/Dependencies/SDKSynchronizer/SDKSynchronizerTest.swift @@ -33,7 +33,8 @@ extension SDKSynchronizerClient: TestDependencyKey { switchToEndpoint: XCTUnimplemented("\(Self.self).switchToEndpoint"), proposeTransfer: XCTUnimplemented("\(Self.self).proposeTransfer", placeholder: .testOnlyFakeProposal(totalFee: 0)), createProposedTransactions: XCTUnimplemented("\(Self.self).createProposedTransactions", placeholder: .success), - proposeShielding: XCTUnimplemented("\(Self.self).proposeShielding", placeholder: nil) + proposeShielding: XCTUnimplemented("\(Self.self).proposeShielding", placeholder: nil), + isSeedRelevantToAnyDerivedAccount: XCTUnimplemented("\(Self.self).isSeedRelevantToAnyDerivedAccount") ) } @@ -58,7 +59,8 @@ extension SDKSynchronizerClient { switchToEndpoint: { _ in }, proposeTransfer: { _, _, _, _ in .testOnlyFakeProposal(totalFee: 0) }, createProposedTransactions: { _, _ in .success }, - proposeShielding: { _, _, _, _ in nil } + proposeShielding: { _, _, _, _ in nil }, + isSeedRelevantToAnyDerivedAccount: { _ in false } ) public static let mock = Self.mocked() @@ -179,12 +181,13 @@ extension SDKSynchronizerClient { }, wipe: @escaping () -> AnyPublisher? = { Fail(error: "Error").eraseToAnyPublisher() }, switchToEndpoint: @escaping (LightWalletEndpoint) async throws -> Void = { _ in }, - proposeTransfer: + proposeTransfer: @escaping (Int, Recipient, Zatoshi, Memo?) async throws -> Proposal = { _, _, _, _ in .testOnlyFakeProposal(totalFee: 0) }, createProposedTransactions: @escaping (Proposal, UnifiedSpendingKey) async throws -> CreateProposedTransactionsResult = { _, _ in .success }, proposeShielding: - @escaping (Int, Zatoshi, Memo, TransparentAddress?) async throws -> Proposal? = { _, _, _, _ in nil } + @escaping (Int, Zatoshi, Memo, TransparentAddress?) async throws -> Proposal? = { _, _, _, _ in nil }, + isSeedRelevantToAnyDerivedAccount: @escaping ([UInt8]) async throws -> Bool = { _ in false } ) -> SDKSynchronizerClient { SDKSynchronizerClient( stateStream: stateStream, @@ -206,7 +209,8 @@ extension SDKSynchronizerClient { switchToEndpoint: switchToEndpoint, proposeTransfer: proposeTransfer, createProposedTransactions: createProposedTransactions, - proposeShielding: proposeShielding + proposeShielding: proposeShielding, + isSeedRelevantToAnyDerivedAccount: isSeedRelevantToAnyDerivedAccount ) } } diff --git a/modules/Sources/Features/BalanceBreakdown/BalanceBreakdownStore.swift b/modules/Sources/Features/BalanceBreakdown/BalanceBreakdownStore.swift index 44271f6..91bb239 100644 --- a/modules/Sources/Features/BalanceBreakdown/BalanceBreakdownStore.swift +++ b/modules/Sources/Features/BalanceBreakdown/BalanceBreakdownStore.swift @@ -210,11 +210,12 @@ public struct BalanceBreakdownReducer: Reducer { case .synchronizerStateChanged(let latestState): let accountBalance = latestState.data.accountBalance?.data - state.shieldedBalance = accountBalance?.saplingBalance.spendableValue ?? .zero - state.totalBalance = accountBalance?.saplingBalance.total() ?? .zero + + state.shieldedBalance = (accountBalance?.saplingBalance.spendableValue ?? .zero) + (accountBalance?.orchardBalance.spendableValue ?? .zero) + state.totalBalance = (accountBalance?.saplingBalance.total() ?? .zero) + (accountBalance?.orchardBalance.total() ?? .zero) state.transparentBalance = accountBalance?.unshielded ?? .zero - state.changePending = accountBalance?.saplingBalance.changePendingConfirmation ?? .zero - state.pendingTransactions = accountBalance?.saplingBalance.valuePendingSpendability ?? .zero + state.changePending = (accountBalance?.saplingBalance.changePendingConfirmation ?? .zero) + (accountBalance?.orchardBalance.changePendingConfirmation ?? .zero) + state.pendingTransactions = (accountBalance?.saplingBalance.valuePendingSpendability ?? .zero) + (accountBalance?.orchardBalance.valuePendingSpendability ?? .zero) return .none case .syncProgress: diff --git a/modules/Sources/Features/BalanceBreakdown/BalanceBreakdownView.swift b/modules/Sources/Features/BalanceBreakdown/BalanceBreakdownView.swift index e3bcc62..1eee367 100644 --- a/modules/Sources/Features/BalanceBreakdown/BalanceBreakdownView.swift +++ b/modules/Sources/Features/BalanceBreakdown/BalanceBreakdownView.swift @@ -70,6 +70,7 @@ public struct BalanceBreakdownView: View { ) ) .padding(.top, viewStore.isRestoringWallet ? 0 : 40) + .padding(.bottom, 25) .navigationLinkEmpty( isActive: viewStore.bindingForPartialProposalError, destination: { diff --git a/modules/Sources/Features/Home/HomeStore.swift b/modules/Sources/Features/Home/HomeStore.swift index a2f4878..6559f96 100644 --- a/modules/Sources/Features/Home/HomeStore.swift +++ b/modules/Sources/Features/Home/HomeStore.swift @@ -196,8 +196,9 @@ public struct HomeReducer: Reducer { state.synchronizerStatusSnapshot = snapshot let accountBalance = latestState.data.accountBalance?.data - state.shieldedBalance = accountBalance?.saplingBalance.spendableValue ?? .zero - state.totalBalance = accountBalance?.saplingBalance.total() ?? .zero + + state.shieldedBalance = (accountBalance?.saplingBalance.spendableValue ?? .zero) + (accountBalance?.orchardBalance.spendableValue ?? .zero) + state.totalBalance = (accountBalance?.saplingBalance.total() ?? .zero) + (accountBalance?.orchardBalance.total() ?? .zero) switch snapshot.syncStatus { case .error(let error): diff --git a/modules/Sources/Features/ImportWallet/ImportWalletStore.swift b/modules/Sources/Features/ImportWallet/ImportWalletStore.swift index b01c501..4379034 100644 --- a/modules/Sources/Features/ImportWallet/ImportWalletStore.swift +++ b/modules/Sources/Features/ImportWallet/ImportWalletStore.swift @@ -72,10 +72,11 @@ public struct ImportWalletReducer: Reducer { public enum Action: Equatable { case alert(PresentationAction) case birthdayInputChanged(RedactableString) - case restoreWallet case importPrivateOrViewingKey case initializeSDK + case nextPressed case onAppear + case restoreWallet case seedPhraseInputChanged(RedactableString) case successfullyRecovered case updateDestination(ImportWalletReducer.State.Destination?) @@ -130,6 +131,9 @@ public struct ImportWalletReducer: Reducer { case .alert: return .none + case .nextPressed: + return .none + case .restoreWallet: do { // validate the seed diff --git a/modules/Sources/Features/ImportWallet/ImportWalletView.swift b/modules/Sources/Features/ImportWallet/ImportWalletView.swift index dd54d0f..18cd965 100644 --- a/modules/Sources/Features/ImportWallet/ImportWalletView.swift +++ b/modules/Sources/Features/ImportWallet/ImportWalletView.swift @@ -97,7 +97,7 @@ public struct ImportWalletView: View { } Button(L10n.General.next.uppercased()) { - viewStore.send(.updateDestination(.birthday)) + viewStore.send(.nextPressed) } .zcashStyle() .frame(width: 236) diff --git a/modules/Sources/Features/Root/RootInitialization.swift b/modules/Sources/Features/Root/RootInitialization.swift index 650905b..47756c4 100644 --- a/modules/Sources/Features/Root/RootInitialization.swift +++ b/modules/Sources/Features/Root/RootInitialization.swift @@ -28,6 +28,8 @@ extension RootReducer { case nukeWallet case nukeWalletRequest case respondToWalletInitializationState(InitializationState) + case restoreExistingWallet + case seedValidationResult(Bool) case synchronizerStartFailed(ZcashError) case registerForSynchronizersUpdate case retryStart @@ -39,6 +41,7 @@ extension RootReducer { Reduce { state, action in switch action { case .initialization(.appDelegate(.didFinishLaunching)): + //walletStorage.nukeWallet() state.appStartState = .didFinishLaunching // TODO: [#704], trigger the review request logic when approved by the team, // https://github.com/Electric-Coin-Company/zashi-ios/issues/704 @@ -58,7 +61,7 @@ extension RootReducer { } else { return .send(.initialization(.retryStart)) } - + case .initialization(.appDelegate(.didEnterBackground)): sdkSynchronizer.stop() state.bgTask?.setTaskCompleted(success: false) @@ -86,14 +89,14 @@ extension RootReducer { await send(.initialization(.retryStart)) } } - + case .synchronizerStateChanged(let latestState): let snapshot = SyncStatusSnapshot.snapshotFor(state: latestState.data.syncStatus) - + guard state.bgTask != nil else { return .send(.initialization(.checkRestoreWalletFlag(snapshot.syncStatus))) } - + var finishBGTask = false var successOfBGTask = false @@ -106,16 +109,16 @@ extension RootReducer { finishBGTask = true default: break } - + if finishBGTask { LoggerProxy.event("BGTask setTaskCompleted(success: \(successOfBGTask)) from TCA") state.bgTask?.setTaskCompleted(success: successOfBGTask) state.bgTask = nil return .cancel(id: CancelStateId) } - + return .send(.initialization(.checkRestoreWalletFlag(snapshot.syncStatus))) - + case .initialization(.checkRestoreWalletFlag(let syncStatus)): if state.isRestoringWallet && syncStatus == .upToDate { state.isRestoringWallet = false @@ -125,10 +128,10 @@ extension RootReducer { } else { return .none } - + case .initialization(.synchronizerStartFailed): return .none - + case .initialization(.retryStart): // Try the start only if the synchronizer has been already prepared guard sdkSynchronizer.latestState().syncStatus.isPrepared else { @@ -172,7 +175,7 @@ extension RootReducer { } else { return Effect.send(.initialization(.walletConfigChanged(walletConfig))) } - + case .initialization(.walletConfigChanged(let walletConfig)): return .concatenate( Effect.send(.updateStateAfterConfigUpdate(walletConfig)), @@ -193,7 +196,7 @@ extension RootReducer { zcashNetwork: zcashSDKEnvironment.network ) return Effect.send(.initialization(.respondToWalletInitializationState(walletState))) - + /// Respond to all possible states of the wallet and initiate appropriate side effects including errors handling case .initialization(.respondToWalletInitializationState(let walletState)): switch walletState { @@ -203,11 +206,7 @@ extension RootReducer { return .none case .keysMissing: state.appInitializationState = .keysMissing - // TODO: [#1024] This is the case when this wallet migrated to another device - // https://github.com/Electric-Coin-Company/zashi-ios/issues/1024 - // Temporary alert view until #1024 is implemented - state.alert = AlertState.tmpMigrationToBeDeveloped() - return .none + return .send(.destination(.updateDestination(.onboarding))) case .initialized, .filesMissing: if walletState == .filesMissing { state.appInitializationState = .filesMissing @@ -235,14 +234,13 @@ extension RootReducer { } .cancellable(id: CancelId, cancelInFlight: true) } - + /// 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. case .initialization(.initializeSDK(let walletMode)): do { let storedWallet = try walletStorage.exportWallet() let birthday = storedWallet.birthday?.value() ?? zcashSDKEnvironment.latestCheckpoint - try mnemonic.isValid(storedWallet.seedPhrase.value()) let seedBytes = try mnemonic.toSeed(storedWallet.seedPhrase.value()) @@ -250,7 +248,7 @@ extension RootReducer { do { try await sdkSynchronizer.prepareWith(seedBytes, birthday, walletMode) try await sdkSynchronizer.start(false) - + let uAddress = try? await sdkSynchronizer.getUnifiedAddress(0) await send(.initialization(.initializationSuccessfullyDone(uAddress))) } catch { @@ -260,16 +258,16 @@ extension RootReducer { } catch { return Effect.send(.initialization(.initializationFailed(error.toZcashError()))) } - + case .initialization(.initializationSuccessfullyDone(let uAddress)): state.tabsState.addressDetailsState.uAddress = uAddress return .send(.initialization(.registerForSynchronizersUpdate)) - + case .initialization(.checkBackupPhraseValidation): do { let storedWallet = try walletStorage.exportWallet() var landingDestination = RootReducer.DestinationState.Destination.tabs - + if !storedWallet.hasUserPassedPhraseBackupTest { let phraseWords = mnemonic.asWords(storedWallet.seedPhrase.value()) @@ -297,7 +295,7 @@ extension RootReducer { case .initialization(.nukeWalletRequest): state.alert = AlertState.wipeRequest() return .none - + case .initialization(.nukeWallet): guard let wipePublisher = sdkSynchronizer.wipe() else { return Effect.send(.nukeWalletFailed) @@ -312,17 +310,40 @@ extension RootReducer { .cancellable(id: SynchronizerCancelId, cancelInFlight: true) case .nukeWalletSucceeded: - state = .initial + if state.appInitializationState != .keysMissing { + state = .initial + } state.splashAppeared = true walletStorage.nukeWallet() try? readTransactionsStorage.nukeWallet() - return .concatenate( - .cancel(id: SynchronizerCancelId), - .run { send in - await userStoredPreferences.removeAll() - }, - Effect.send(.initialization(.checkWalletInitialization)) - ) + + if state.appInitializationState == .keysMissing && state.onboardingState.destination == .importExistingWallet { + state.appInitializationState = .uninitialized + return .concatenate( + .cancel(id: SynchronizerCancelId), + .run { send in + await userStoredPreferences.removeAll() + }, + Effect.send(.onboarding(.importWallet(.updateDestination(.birthday)))) + ) + } else if state.appInitializationState == .keysMissing && state.onboardingState.destination == .createNewWallet { + state.appInitializationState = .uninitialized + return .concatenate( + .cancel(id: SynchronizerCancelId), + .run { send in + await userStoredPreferences.removeAll() + }, + Effect.send(.onboarding(.securityWarning(.createNewWallet))) + ) + } else { + return .concatenate( + .cancel(id: SynchronizerCancelId), + .run { send in + await userStoredPreferences.removeAll() + }, + Effect.send(.initialization(.checkWalletInitialization)) + ) + } case .nukeWalletFailed: let backDestination: Effect @@ -332,10 +353,15 @@ extension RootReducer { backDestination = Effect.send(.destination(.updateDestination(state.destinationState.destination))) } state.alert = AlertState.wipeFailed() - return .concatenate( - .cancel(id: SynchronizerCancelId), - backDestination - ) + + if state.appInitializationState == .keysMissing { + return .cancel(id: SynchronizerCancelId) + } else { + return .concatenate( + .cancel(id: SynchronizerCancelId), + backDestination + ) + } case .phraseDisplay(.finishedPressed): do { @@ -352,6 +378,38 @@ extension RootReducer { Effect.send(.destination(.updateDestination(.startup))) ) + case .onboarding(.securityWarning(.confirmTapped)): + if state.appInitializationState == .keysMissing { + state.alert = AlertState.existingWallet() + return .none + } else { + return .send(.onboarding(.securityWarning(.createNewWallet))) + } + + case .initialization(.restoreExistingWallet): + return .run { send in + await send(.onboarding(.updateDestination(nil))) + try await mainQueue.sleep(for: .seconds(1)) + await send(.onboarding(.importExistingWallet)) + } + + case .onboarding(.importWallet(.nextPressed)): + if state.appInitializationState == .keysMissing { + let seedPhrase = state.onboardingState.importWalletState.importedSeedPhrase.data + return .run { send in + do { + let seedBytes = try mnemonic.toSeed(seedPhrase) + let result = try await sdkSynchronizer.isSeedRelevantToAnyDerivedAccount(seedBytes) + await send(.initialization(.seedValidationResult(result))) + } catch { + await send(.initialization(.seedValidationResult(false))) + } + } + } else { + state.onboardingState.importWalletState.destination = .birthday + return .none + } + case .onboarding(.importWallet(.successfullyRecovered)): state.alert = AlertState.successfullyRecovered() return Effect.send(.destination(.updateDestination(.tabs))) @@ -365,6 +423,14 @@ extension RootReducer { } ) + case .initialization(.seedValidationResult(let validSeed)): + if validSeed { + return .send(.onboarding(.importWallet(.restoreWallet))) + } else { + state.alert = AlertState.differentSeed() + } + return .none + case .initialization(.configureCrashReporter): crashReporter.configure( !userStoredPreferences.isUserOptedOutOfCrashReporting() diff --git a/modules/Sources/Features/Root/RootStore.swift b/modules/Sources/Features/Root/RootStore.swift index 0957359..5cbdc03 100644 --- a/modules/Sources/Features/Root/RootStore.swift +++ b/modules/Sources/Features/Root/RootStore.swift @@ -297,11 +297,33 @@ extension AlertState where Action == RootReducer.Action { } } - public static func tmpMigrationToBeDeveloped() -> AlertState { + public static func differentSeed() -> AlertState { AlertState { - TextState("Automatic migration to be developed soon") + TextState("Warning") + } actions: { + ButtonState(role: .cancel, action: .alert(.dismiss)) { + TextState("Try Again") + } + ButtonState(role: .destructive, action: .initialization(.nukeWallet)) { + TextState("Continue") + } } message: { - TextState("This copy of Zashi has been migrated from another device. Your funds are safe provided that you have the seed phrase. This issue will be addressed soon; until then, delete Zashi and reinstall it, providing the seed phrase to restore your wallet.") + TextState("This recovery phrase doesn't match the Zashi database backup saved on this device. If you proceed, you will lose access to this database backup and if you try to restore later, some information may be lost.") + } + } + + public static func existingWallet() -> AlertState { + AlertState { + TextState("Warning") + } actions: { + ButtonState(role: .cancel, action: .initialization(.restoreExistingWallet)) { + TextState("Restore") + } + ButtonState(role: .destructive, action: .initialization(.nukeWallet)) { + TextState("Continue") + } + } message: { + TextState("We identified a Zashi database backup on this device. If you create a new wallet, you will lose access to this database backup and if you try to restore later, some information may be lost.") } } } diff --git a/modules/Sources/Features/SecurityWarning/SecurityWarningStore.swift b/modules/Sources/Features/SecurityWarning/SecurityWarningStore.swift index 66bf4c3..3267d32 100644 --- a/modules/Sources/Features/SecurityWarning/SecurityWarningStore.swift +++ b/modules/Sources/Features/SecurityWarning/SecurityWarningStore.swift @@ -48,6 +48,7 @@ public struct SecurityWarning { case alert(PresentationAction) case binding(BindingAction) case confirmTapped + case createNewWallet case newWalletCreated case onAppear case recoveryPhraseDisplay(RecoveryPhraseDisplay.Action) @@ -85,6 +86,9 @@ public struct SecurityWarning { return .none case .confirmTapped: + return .none + + case .createNewWallet: do { // get the random english mnemonic let newRandomPhrase = try mnemonic.randomMnemonic() diff --git a/modules/Sources/Features/SendFlow/SendFlowStore.swift b/modules/Sources/Features/SendFlow/SendFlowStore.swift index de6ca9d..f452e48 100644 --- a/modules/Sources/Features/SendFlow/SendFlowStore.swift +++ b/modules/Sources/Features/SendFlow/SendFlowStore.swift @@ -320,8 +320,10 @@ public struct SendFlowReducer: Reducer { return .none case .synchronizerStateChanged(let latestState): - state.spendableBalance = latestState.data.accountBalance?.data?.saplingBalance.spendableValue ?? .zero - state.totalBalance = latestState.data.accountBalance?.data?.saplingBalance.total() ?? .zero + let latestAccountBalance = latestState.data.accountBalance?.data + + state.spendableBalance = (latestAccountBalance?.saplingBalance.spendableValue ?? .zero) + (latestAccountBalance?.orchardBalance.spendableValue ?? .zero) + state.totalBalance = (latestAccountBalance?.saplingBalance.total() ?? .zero) + (latestAccountBalance?.orchardBalance.total() ?? .zero) state.transactionAmountInputState.maxValue = state.spendableBalance.amount.redacted return .none diff --git a/modules/Sources/Models/SyncStatusSnapshot.swift b/modules/Sources/Models/SyncStatusSnapshot.swift index c067c31..bc7f618 100644 --- a/modules/Sources/Models/SyncStatusSnapshot.swift +++ b/modules/Sources/Models/SyncStatusSnapshot.swift @@ -8,6 +8,7 @@ import Foundation import ZcashLightClientKit import Generated +import Utils public struct SyncStatusSnapshot: Equatable { public let message: String @@ -27,7 +28,7 @@ public struct SyncStatusSnapshot: Equatable { return SyncStatusSnapshot(state, L10n.Sync.Message.unprepared) case .error(let error): - return SyncStatusSnapshot(state, L10n.Sync.Message.error(error.toZcashError().message)) + return SyncStatusSnapshot(state, L10n.Sync.Message.error(error.toZcashError().detailedMessage)) case .stopped: return SyncStatusSnapshot(state, L10n.Sync.Message.stopped) diff --git a/secant.xcodeproj/project.pbxproj b/secant.xcodeproj/project.pbxproj index 4bed473..d85e5b9 100644 --- a/secant.xcodeproj/project.pbxproj +++ b/secant.xcodeproj/project.pbxproj @@ -1204,7 +1204,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\""; DEVELOPMENT_TEAM = RLPRR8CPQG; @@ -1234,7 +1234,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\""; DEVELOPMENT_TEAM = RLPRR8CPQG; ENABLE_BITCODE = NO; @@ -1539,7 +1539,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 2; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\""; DEVELOPMENT_TEAM = RLPRR8CPQG; @@ -1552,7 +1552,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.0.1; OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "co.electriccoin.secant-mainnet"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1569,7 +1569,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\""; DEVELOPMENT_TEAM = RLPRR8CPQG; ENABLE_BITCODE = NO; @@ -1581,7 +1581,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.0.1; OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "co.electriccoin.secant-mainnet"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1684,7 +1684,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 4; DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\""; DEVELOPMENT_TEAM = RLPRR8CPQG; ENABLE_BITCODE = NO; @@ -1759,7 +1759,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 3; + CURRENT_PROJECT_VERSION = 2; DEVELOPMENT_ASSET_PATHS = "\"secant/Preview Content\""; DEVELOPMENT_TEAM = RLPRR8CPQG; ENABLE_BITCODE = NO; @@ -1771,7 +1771,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.0.1; OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "co.electriccoin.secant-mainnet"; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/secant.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/secant.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f4761e4..74c0727 100644 --- a/secant.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/secant.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -374,17 +374,17 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/zcash-hackworks/zcash-light-client-ffi", "state" : { - "revision" : "7c801be1f445402a433b32835a50d832e8a50437", - "version" : "0.6.0" + "revision" : "c7e5158edf5e62af15492d30237163b78af35ce9", + "version" : "0.7.1" } }, { - "identity" : "zcashlightclientkit", + "identity" : "zcash-swift-wallet-sdk", "kind" : "remoteSourceControl", - "location" : "https://github.com/zcash/ZcashLightClientKit", + "location" : "https://github.com/Electric-Coin-Company/zcash-swift-wallet-sdk", "state" : { - "revision" : "6c9b7a91d6b9ec4f3b8cb699a558eac1178ba837", - "version" : "2.0.11" + "revision" : "8909f237225676be70dfeaa103b77139481dfd86", + "version" : "2.1.0" } } ], diff --git a/secantTests/RootTests/AppInitializationTests.swift b/secantTests/RootTests/AppInitializationTests.swift index e3d613e..1b969a4 100644 --- a/secantTests/RootTests/AppInitializationTests.swift +++ b/secantTests/RootTests/AppInitializationTests.swift @@ -203,7 +203,11 @@ class AppInitializationTests: XCTestCase { await store.receive(.initialization(.respondToWalletInitializationState(.keysMissing))) { state in state.appInitializationState = .keysMissing - state.alert = AlertState.tmpMigrationToBeDeveloped() + } + + await store.receive(.destination(.updateDestination(.onboarding))) { state in + state.destinationState.internalDestination = .onboarding + state.destinationState.previousDestination = .welcome } await store.finish() diff --git a/secantTests/RootTests/RootTests.swift b/secantTests/RootTests/RootTests.swift index 9a00902..18cdaa9 100644 --- a/secantTests/RootTests/RootTests.swift +++ b/secantTests/RootTests/RootTests.swift @@ -133,7 +133,11 @@ class RootTests: XCTestCase { await store.send(.initialization(.respondToWalletInitializationState(.keysMissing))) { state in state.appInitializationState = .keysMissing - state.alert = AlertState.tmpMigrationToBeDeveloped() + } + + await store.receive(.destination(.updateDestination(.onboarding))) { state in + state.destinationState.internalDestination = .onboarding + state.destinationState.previousDestination = .welcome } await store.finish()