238 lines
8.0 KiB
Swift
238 lines
8.0 KiB
Swift
//
|
|
// ImportWalletStore.swift
|
|
// secant-testnet
|
|
//
|
|
// Created by Lukáš Korba on 02/25/2022.
|
|
//
|
|
|
|
import ComposableArchitecture
|
|
import ZcashLightClientKit
|
|
import SwiftUI
|
|
import Utils
|
|
import Generated
|
|
import WalletStorage
|
|
import MnemonicClient
|
|
import ZcashSDKEnvironment
|
|
|
|
public typealias ImportWalletStore = Store<ImportWalletReducer.State, ImportWalletReducer.Action>
|
|
public typealias ImportWalletViewStore = ViewStore<ImportWalletReducer.State, ImportWalletReducer.Action>
|
|
|
|
public struct ImportWalletReducer: Reducer {
|
|
public struct State: Equatable {
|
|
public enum Destination: Equatable {
|
|
case birthday
|
|
}
|
|
|
|
@PresentationState public var alert: AlertState<Action>?
|
|
public var birthdayHeight = "".redacted
|
|
public var birthdayHeightValue: RedactableBlockHeight?
|
|
public var destination: Destination?
|
|
public var importedSeedPhrase = "".redacted
|
|
public var isValidMnemonic = false
|
|
public var isValidNumberOfWords = false
|
|
public var maxWordsCount = 0
|
|
public var wordsCount = 0
|
|
|
|
public var mnemonicStatus: String {
|
|
if isValidMnemonic {
|
|
return L10n.ImportWallet.Seed.valid
|
|
} else {
|
|
return "\(wordsCount)/\(maxWordsCount)"
|
|
}
|
|
}
|
|
|
|
public var isValidForm: Bool {
|
|
isValidMnemonic &&
|
|
(birthdayHeight.data.isEmpty ||
|
|
(!birthdayHeight.data.isEmpty && birthdayHeightValue != nil))
|
|
}
|
|
|
|
public init(
|
|
birthdayHeight: RedactableString = "".redacted,
|
|
birthdayHeightValue: RedactableBlockHeight? = nil,
|
|
destination: Destination? = nil,
|
|
importedSeedPhrase: RedactableString = "".redacted,
|
|
isValidMnemonic: Bool = false,
|
|
isValidNumberOfWords: Bool = false,
|
|
maxWordsCount: Int = 0,
|
|
wordsCount: Int = 0
|
|
) {
|
|
self.alert = alert
|
|
self.birthdayHeight = birthdayHeight
|
|
self.birthdayHeightValue = birthdayHeightValue
|
|
self.destination = destination
|
|
self.importedSeedPhrase = importedSeedPhrase
|
|
self.isValidMnemonic = isValidMnemonic
|
|
self.isValidNumberOfWords = isValidNumberOfWords
|
|
self.maxWordsCount = maxWordsCount
|
|
self.wordsCount = wordsCount
|
|
}
|
|
}
|
|
|
|
public enum Action: Equatable {
|
|
case alert(PresentationAction<Action>)
|
|
case birthdayInputChanged(RedactableString)
|
|
case importPrivateOrViewingKey
|
|
case initializeSDK
|
|
case nextPressed
|
|
case onAppear
|
|
case restoreWallet
|
|
case seedPhraseInputChanged(RedactableString)
|
|
case successfullyRecovered
|
|
case updateDestination(ImportWalletReducer.State.Destination?)
|
|
}
|
|
|
|
@Dependency(\.mnemonic) var mnemonic
|
|
@Dependency(\.walletStorage) var walletStorage
|
|
@Dependency(\.zcashSDKEnvironment) var zcashSDKEnvironment
|
|
|
|
public init() { }
|
|
|
|
public var body: some Reducer<State, Action> {
|
|
Reduce { state, action in
|
|
switch action {
|
|
case .onAppear:
|
|
state.maxWordsCount = zcashSDKEnvironment.mnemonicWordsMaxCount
|
|
return .none
|
|
|
|
case .seedPhraseInputChanged(let redactedSeedPhrase):
|
|
state.importedSeedPhrase = redactedSeedPhrase
|
|
state.wordsCount = state.importedSeedPhrase.data.split(separator: " ").count
|
|
state.isValidNumberOfWords = state.wordsCount == state.maxWordsCount
|
|
// is the mnemonic valid one?
|
|
do {
|
|
try mnemonic.isValid(state.importedSeedPhrase.data)
|
|
} catch {
|
|
state.isValidMnemonic = false
|
|
return .none
|
|
}
|
|
state.isValidMnemonic = true
|
|
return .none
|
|
|
|
case .birthdayInputChanged(let redactedBirthday):
|
|
let saplingActivation = zcashSDKEnvironment.network.constants.saplingActivationHeight
|
|
|
|
state.birthdayHeight = redactedBirthday
|
|
|
|
if let birthdayHeight = BlockHeight(state.birthdayHeight.data), birthdayHeight >= saplingActivation {
|
|
state.birthdayHeightValue = birthdayHeight.redacted
|
|
} else {
|
|
state.birthdayHeightValue = nil
|
|
}
|
|
return .none
|
|
|
|
case .alert(.presented(let action)):
|
|
return Effect.send(action)
|
|
|
|
case .alert(.dismiss):
|
|
state.alert = nil
|
|
return .none
|
|
|
|
case .alert:
|
|
return .none
|
|
|
|
case .nextPressed:
|
|
return .none
|
|
|
|
case .restoreWallet:
|
|
do {
|
|
// validate the seed
|
|
try mnemonic.isValid(state.importedSeedPhrase.data)
|
|
|
|
// store it to the keychain, if the user did not input a height,
|
|
// fall back to sapling activation
|
|
let birthday = state.birthdayHeightValue ?? zcashSDKEnvironment.network.constants.saplingActivationHeight.redacted
|
|
|
|
try walletStorage.importWallet(state.importedSeedPhrase.data, birthday.data, .english, false)
|
|
|
|
// update the backup phrase validation flag
|
|
try walletStorage.markUserPassedPhraseBackupTest(true)
|
|
|
|
state.birthdayHeight = "".redacted
|
|
state.importedSeedPhrase = "".redacted
|
|
state.destination = nil
|
|
|
|
// notify user
|
|
return .concatenate(
|
|
Effect.send(.successfullyRecovered),
|
|
Effect.send(.initializeSDK)
|
|
)
|
|
} catch {
|
|
state.alert = AlertState.failed(error.toZcashError())
|
|
}
|
|
return .none
|
|
|
|
case .updateDestination(let destination):
|
|
state.destination = destination
|
|
return .none
|
|
|
|
case .importPrivateOrViewingKey:
|
|
return .none
|
|
|
|
case .successfullyRecovered:
|
|
return .none
|
|
|
|
case .initializeSDK:
|
|
return .none
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - ViewStore
|
|
|
|
extension ImportWalletViewStore {
|
|
func bindingForDestination(_ destination: ImportWalletReducer.State.Destination) -> Binding<Bool> {
|
|
self.binding(
|
|
get: { $0.destination == destination },
|
|
send: { isActive in
|
|
return .updateDestination(isActive ? destination : nil)
|
|
}
|
|
)
|
|
}
|
|
|
|
func bindingForRedactableSeedPhrase(_ importedSeedPhrase: RedactableString) -> Binding<String> {
|
|
self.binding(
|
|
get: { _ in importedSeedPhrase.data },
|
|
send: { .seedPhraseInputChanged($0.redacted) }
|
|
)
|
|
}
|
|
|
|
func bindingForRedactableBirthday(_ birthdayHeight: RedactableString) -> Binding<String> {
|
|
self.binding(
|
|
get: { _ in birthdayHeight.data },
|
|
send: { .birthdayInputChanged($0.redacted) }
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: Alerts
|
|
|
|
extension AlertState where Action == ImportWalletReducer.Action {
|
|
public static func failed(_ error: ZcashError) -> AlertState {
|
|
AlertState {
|
|
TextState(L10n.ImportWallet.Alert.Failed.title)
|
|
} actions: {
|
|
ButtonState(action: .alert(.dismiss)) {
|
|
TextState(L10n.General.ok)
|
|
}
|
|
} message: {
|
|
TextState(L10n.ImportWallet.Alert.Failed.message(error.detailedMessage))
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Placeholders
|
|
|
|
extension ImportWalletReducer.State {
|
|
public static let initial = ImportWalletReducer.State()
|
|
}
|
|
|
|
extension ImportWalletStore {
|
|
public static let demo = Store(
|
|
initialState: .initial
|
|
) {
|
|
ImportWalletReducer()
|
|
}
|
|
}
|