[#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
This commit is contained in:
Lukas Korba 2024-03-12 15:34:00 +01:00
parent 2e51a9d839
commit 3e6afc69a8
19 changed files with 224 additions and 102 deletions

View File

@ -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.

View File

@ -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"

View File

@ -28,24 +28,24 @@ public struct SDKSynchronizerClient {
public let stateStream: () -> AnyPublisher<SynchronizerState, Never>
public let eventStream: () -> AnyPublisher<SynchronizerEvent, Never>
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<Void, Error>
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<Void, Error>?
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
}

View File

@ -173,6 +173,9 @@ extension SDKSynchronizerClient: DependencyKey {
memo: memo,
transparentReceiver: transparentReceiver
)
},
isSeedRelevantToAnyDerivedAccount: { seed in
try await synchronizer.isSeedRelevantToAnyDerivedAccount(seed: seed)
}
)
}

View File

@ -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<Void, Error>? = { 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
)
}
}

View File

@ -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:

View File

@ -70,6 +70,7 @@ public struct BalanceBreakdownView: View {
)
)
.padding(.top, viewStore.isRestoringWallet ? 0 : 40)
.padding(.bottom, 25)
.navigationLinkEmpty(
isActive: viewStore.bindingForPartialProposalError,
destination: {

View File

@ -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):

View File

@ -72,10 +72,11 @@ public struct ImportWalletReducer: Reducer {
public enum Action: Equatable {
case alert(PresentationAction<Action>)
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

View File

@ -97,7 +97,7 @@ public struct ImportWalletView: View {
}
Button(L10n.General.next.uppercased()) {
viewStore.send(.updateDestination(.birthday))
viewStore.send(.nextPressed)
}
.zcashStyle()
.frame(width: 236)

View File

@ -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<RootReducer.Action>
@ -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()

View File

@ -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.")
}
}
}

View File

@ -48,6 +48,7 @@ public struct SecurityWarning {
case alert(PresentationAction<Action>)
case binding(BindingAction<SecurityWarning.State>)
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()

View File

@ -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

View File

@ -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)

View File

@ -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)";

View File

@ -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"
}
}
],

View File

@ -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()

View File

@ -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()