secant-ios-wallet/secant/Features/ImportWallet/ImportWalletStore.swift

179 lines
5.5 KiB
Swift

//
// ImportWalletStore.swift
// secant-testnet
//
// Created by Lukáš Korba on 02/25/2022.
//
import ComposableArchitecture
import ZcashLightClientKit
typealias ImportWalletReducer = Reducer<ImportWalletState, ImportWalletAction, ImportWalletEnvironment>
typealias ImportWalletStore = Store<ImportWalletState, ImportWalletAction>
typealias ImportWalletViewStore = ViewStore<ImportWalletState, ImportWalletAction>
// MARK: - State
struct ImportWalletState: Equatable {
@BindableState var alert: AlertState<ImportWalletAction>?
@BindableState var importedSeedPhrase: String = ""
@BindableState var birthdayHeight: String = ""
var wordsCount = 0
var maxWordsCount = 0
var isValidMnemonic = false
var isValidNumberOfWords = false
var birthdayHeightValue: BlockHeight?
var mnemonicStatus: String {
if isValidMnemonic {
return "VALID SEED PHRASE"
} else {
return "\(wordsCount)/\(maxWordsCount)"
}
}
var isValidForm: Bool {
isValidMnemonic &&
(birthdayHeight.isEmpty ||
(!birthdayHeight.isEmpty && birthdayHeightValue != nil))
}
}
// MARK: - Action
enum ImportWalletAction: Equatable, BindableAction {
case binding(BindingAction<ImportWalletState>)
case dismissAlert
case restoreWallet
case importPrivateOrViewingKey
case initializeSDK
case onAppear
case successfullyRecovered
}
// MARK: - Environment
struct ImportWalletEnvironment {
let mnemonic: WrappedMnemonic
let walletStorage: WrappedWalletStorage
let zcashSDKEnvironment: ZCashSDKEnvironment
}
extension ImportWalletEnvironment {
static let live = ImportWalletEnvironment(
mnemonic: .live,
walletStorage: .live(),
zcashSDKEnvironment: .mainnet
)
static let demo = ImportWalletEnvironment(
mnemonic: .mock,
walletStorage: .live(),
zcashSDKEnvironment: .testnet
)
}
// MARK: - Reducer
extension ImportWalletReducer {
static let `default` = ImportWalletReducer { state, action, environment in
switch action {
case .onAppear:
state.maxWordsCount = environment.zcashSDKEnvironment.mnemonicWordsMaxCount
return .none
case .binding(\.$importedSeedPhrase):
state.wordsCount = state.importedSeedPhrase.split(separator: " ").count
state.isValidNumberOfWords = state.wordsCount == state.maxWordsCount
// is the mnemonic valid one?
do {
try environment.mnemonic.isValid(state.importedSeedPhrase)
} catch {
state.isValidMnemonic = false
return .none
}
state.isValidMnemonic = true
return .none
case .binding(\.$birthdayHeight):
if let birthdayHeight = BlockHeight(state.birthdayHeight), birthdayHeight >= environment.zcashSDKEnvironment.defaultBirthday {
state.birthdayHeightValue = birthdayHeight
} else {
state.birthdayHeightValue = nil
}
return .none
case .binding:
return .none
case .dismissAlert:
state.alert = nil
return .none
case .restoreWallet:
do {
// validate the seed
try environment.mnemonic.isValid(state.importedSeedPhrase)
// store it to the keychain
var birthday = state.birthdayHeightValue ?? environment.zcashSDKEnvironment.defaultBirthday
try environment.walletStorage.importWallet(state.importedSeedPhrase, birthday, .english, false)
// update the backup phrase validation flag
try environment.walletStorage.markUserPassedPhraseBackupTest()
// notify user
// TODO [#221]: Proper Error/Success handling (https://github.com/zcash/secant-ios-wallet/issues/221)
state.alert = AlertState(
title: TextState("Success"),
message: TextState("The wallet has been successfully recovered."),
dismissButton: .default(
TextState("Ok"),
action: .send(.successfullyRecovered)
)
)
return Effect(value: .initializeSDK)
} catch {
// TODO [#221]: Proper Error/Success handling (https://github.com/zcash/secant-ios-wallet/issues/221)
state.alert = AlertState(
title: TextState("Wrong Seed Phrase"),
message: TextState("The seed phrase must be 24 words separated by space."),
dismissButton: .default(
TextState("Ok"),
action: .send(.dismissAlert)
)
)
}
return .none
case .importPrivateOrViewingKey:
return .none
case .successfullyRecovered:
return Effect(value: .dismissAlert)
case .initializeSDK:
return .none
}
}
.binding()
}
// MARK: - Placeholders
extension ImportWalletState {
static let placeholder = ImportWalletState()
static let live = ImportWalletState()
}
extension ImportWalletStore {
static let demo = Store(
initialState: .placeholder,
reducer: .default,
environment: .demo
)
}