diff --git a/core/datasource/src/main/java/com/tangem/datasource/asset/AssetReader.kt b/core/datasource/src/main/java/com/tangem/datasource/asset/AssetReader.kt index 5c02f67a7c..a3196c64aa 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/asset/AssetReader.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/asset/AssetReader.kt @@ -9,13 +9,9 @@ import java.io.InputStream */ interface AssetReader { - /** Read content of json file from asset - * @param fileName - name of the file - * */ + /** Read content of json file [fileName] from asset */ fun readJson(fileName: String): String - /** Read content of a file [file] from asset as InputStream - * @param file - name of the file with extension - * */ + /** Open a file [file] from asset as InputStream */ fun openFile(file: String): InputStream } \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/DefaultMnemonicRepository.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/DefaultMnemonicRepository.kt new file mode 100644 index 0000000000..d43a1a9e98 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/DefaultMnemonicRepository.kt @@ -0,0 +1,36 @@ +package com.tangem.feature.onboarding.data + +import com.tangem.crypto.bip39.BIP39Wordlist +import com.tangem.crypto.bip39.DefaultMnemonic +import com.tangem.crypto.bip39.EntropyLength +import com.tangem.crypto.bip39.Mnemonic +import com.tangem.crypto.bip39.Wordlist +import com.tangem.datasource.asset.AssetReader + +/** +[REDACTED_AUTHOR] + */ +internal class DefaultMnemonicRepository( + private val assetReader: AssetReader, +) : MnemonicRepository { + + // TODO: change initialization after adding it to the sdk + private val bip39Wordlist: Wordlist by lazy { + assetReader.openFile(MNEMONIC_FILE_NAME) + .use { BIP39Wordlist(it) } + } + + private val bip39Words: Set by lazy { bip39Wordlist.words.toHashSet() } + + override fun getWordsDictionary(): Set = bip39Words + + override fun generateDefaultMnemonic(): Mnemonic { + return DefaultMnemonic(EntropyLength.Bits128Length, bip39Wordlist) + } + + override fun createMnemonic(mnemonicString: String): Mnemonic = DefaultMnemonic(mnemonicString, bip39Wordlist) + + companion object { + private const val MNEMONIC_FILE_NAME = "mnemonic/mnemonic_dictionary_en.txt" + } +} diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/MnemonicRepository.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/MnemonicRepository.kt index 4425671e1a..b1b7ebfa2c 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/MnemonicRepository.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/data/MnemonicRepository.kt @@ -1,13 +1,7 @@ package com.tangem.feature.onboarding.data import com.tangem.common.core.TangemSdkError -import com.tangem.crypto.bip39.BIP39Wordlist -import com.tangem.crypto.bip39.DefaultMnemonic -import com.tangem.crypto.bip39.EntropyLength import com.tangem.crypto.bip39.Mnemonic -import com.tangem.crypto.bip39.Wordlist -import com.tangem.datasource.asset.AssetReader -import javax.inject.Inject /** [REDACTED_AUTHOR] @@ -21,24 +15,4 @@ interface MnemonicRepository { @Throws(TangemSdkError.MnemonicException::class) fun createMnemonic(mnemonicString: String): Mnemonic -} - -class DefaultMnemonicRepository @Inject constructor( - private val assetReader: AssetReader, -) : MnemonicRepository { - - private val bip39Wordlist: Wordlist by lazy { BIP39Wordlist(assetReader.openFile(MNEMONIC_FILE_NAME)) } - private val bip39Words: Set by lazy { bip39Wordlist.words.toHashSet() } - - override fun getWordsDictionary(): Set = bip39Words - - override fun generateDefaultMnemonic(): Mnemonic { - return DefaultMnemonic(EntropyLength.Bits128Length, bip39Wordlist) - } - - override fun createMnemonic(mnemonicString: String): Mnemonic = DefaultMnemonic(mnemonicString, bip39Wordlist) - - companion object { - private const val MNEMONIC_FILE_NAME = "mnemonic/mnemonic_dictionary_en.txt" - } } \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt new file mode 100644 index 0000000000..1056aa7996 --- /dev/null +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/DefaultSeedPhraseInteractor.kt @@ -0,0 +1,87 @@ +package com.tangem.feature.onboarding.domain + +import com.tangem.common.core.TangemSdkError +import com.tangem.crypto.bip39.Mnemonic +import com.tangem.crypto.bip39.MnemonicErrorResult +import com.tangem.feature.onboarding.data.MnemonicRepository + +/** +[REDACTED_AUTHOR] + */ +internal class DefaultSeedPhraseInteractor constructor( + private val repository: MnemonicRepository, +) : SeedPhraseInteractor { + + private var currentMnemonic: Mnemonic? = null + + override suspend fun generateMnemonic(): Result { + return runCatching { + repository.generateDefaultMnemonic().apply { + currentMnemonic = this + } + } + } + + override suspend fun getMnemonicComponents(): Result> { + return runCatching { + val mnemonic = currentMnemonic ?: repository.generateDefaultMnemonic().apply { + currentMnemonic = this + } + mnemonic.mnemonicComponents + } + } + + override suspend fun isWordMatch(word: String): Boolean { + return repository.getWordsDictionary().contains(word) + } + + override suspend fun validateMnemonicString(text: String): Result> { + val inputWords = text.split("\\s+".toRegex()).toSet() + val wordsDictionary = repository.getWordsDictionary() + + val invalidWords = inputWords + .filter { it.isNotEmpty() && !wordsDictionary.contains(it) } + .toSet() + + return if (invalidWords.isNotEmpty()) { + Result.failure(SeedPhraseError.InvalidWords(invalidWords)) + } else { + try { + val mnemonic = repository.createMnemonic(text) + Result.success(mnemonic.mnemonicComponents) + } catch (ex: TangemSdkError.MnemonicException) { + val error = ex.mnemonicResult.mapToError() + Result.failure(error) + } + } + } + + override suspend fun getSuggestions( + text: String, + hasSelection: Boolean, + cursorPosition: Int, + ): List { + // TODO: todo + return emptyList() + } + + override suspend fun insertSuggestionWord( + text: String, + suggestion: String, + cursorPosition: Int, + ): InsertSuggestionResult { + // TODO: todo + return InsertSuggestionResult("", 0) + } +} + +private fun MnemonicErrorResult.mapToError(): SeedPhraseError = when (this) { + MnemonicErrorResult.InvalidWordCount -> SeedPhraseError.InvalidEntropyLength + MnemonicErrorResult.InvalidEntropyLength -> SeedPhraseError.InvalidEntropyLength + MnemonicErrorResult.InvalidWordsFile -> SeedPhraseError.InvalidWordsFile + MnemonicErrorResult.InvalidChecksum -> SeedPhraseError.InvalidChecksum + MnemonicErrorResult.MnenmonicCreationFailed -> SeedPhraseError.MnenmonicCreationFailed + MnemonicErrorResult.NormalizationFailed -> SeedPhraseError.NormalizationFailed + MnemonicErrorResult.UnsupportedLanguage -> SeedPhraseError.UnsupportedLanguage + is MnemonicErrorResult.InvalidWords -> SeedPhraseError.InvalidWords(this.words) +} \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/OnboardingModuleError.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/OnboardingModuleError.kt index 1ce4c0588e..be71e9203f 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/OnboardingModuleError.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/OnboardingModuleError.kt @@ -6,6 +6,7 @@ import com.tangem.common.module.ModuleMessage /** [REDACTED_AUTHOR] + * For each pool of errors, use the codes lying in range 100 (200..299, 300..399, etc) */ abstract class OnboardingModuleError( subCode: Int, @@ -15,9 +16,7 @@ abstract class OnboardingModuleError( override val code: Int = ModuleErrorCode.ONBOARDING + subCode companion object { - // base code used for all errors in the module internal const val ERROR_CODE_SEED_PHRASE = 100 -// const val CODE_ANY_OTHER = 200..299, 300..399 etc } } diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt index 3958fd0a9f..43ed7ac068 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/domain/SeedPhraseInteractor.kt @@ -1,10 +1,6 @@ package com.tangem.feature.onboarding.domain -import com.tangem.common.core.TangemSdkError import com.tangem.crypto.bip39.Mnemonic -import com.tangem.crypto.bip39.MnemonicErrorResult -import com.tangem.feature.onboarding.data.MnemonicRepository -import javax.inject.Inject /** [REDACTED_AUTHOR] @@ -12,7 +8,7 @@ import javax.inject.Inject interface SeedPhraseInteractor { suspend fun generateMnemonic(): Result suspend fun getMnemonicComponents(): Result> - suspend fun wordIsMatch(word: String): Boolean + suspend fun isWordMatch(word: String): Boolean suspend fun validateMnemonicString(text: String): Result> suspend fun getSuggestions(text: String, hasSelection: Boolean, cursorPosition: Int): List suspend fun insertSuggestionWord(text: String, suggestion: String, cursorPosition: Int): InsertSuggestionResult @@ -22,88 +18,7 @@ interface SeedPhraseInteractor { } } -class DefaultSeedPhraseInteractor @Inject constructor( - private val repository: MnemonicRepository, -) : SeedPhraseInteractor { - - private var currentMnemonic: Mnemonic? = null - - override suspend fun generateMnemonic(): Result { - return try { - currentMnemonic = repository.generateDefaultMnemonic() - Result.success(currentMnemonic!!) - } catch (ex: TangemSdkError.MnemonicException) { - Result.failure(ex) - } - } - - override suspend fun getMnemonicComponents(): Result> { - return try { - val mnemonic = currentMnemonic ?: repository.generateDefaultMnemonic().apply { - currentMnemonic = this - } - Result.success(mnemonic.mnemonicComponents) - } catch (ex: TangemSdkError.MnemonicException) { - Result.failure(ex) - } - } - - override suspend fun wordIsMatch(word: String): Boolean { - return repository.getWordsDictionary().contains(word) - } - - override suspend fun validateMnemonicString(text: String): Result> { - val inputWords = text.split("\\s+".toRegex()).toSet() - val wordsDictionary = repository.getWordsDictionary() - - val invalidWords = inputWords - .filter { it.isNotEmpty() && !wordsDictionary.contains(it) } - .toSet() - - return if (invalidWords.isNotEmpty()) { - Result.failure(SeedPhraseError.InvalidWords(invalidWords)) - } else { - try { - val mnemonic = repository.createMnemonic(text) - Result.success(mnemonic.mnemonicComponents) - } catch (ex: TangemSdkError.MnemonicException) { - val error = ex.mnemonicResult.mapToError() - Result.failure(error) - } - } - } - - override suspend fun getSuggestions( - text: String, - hasSelection: Boolean, - cursorPosition: Int, - ): List { - // TODO: todo - return emptyList() - } - - override suspend fun insertSuggestionWord( - text: String, - suggestion: String, - cursorPosition: Int, - ): InsertSuggestionResult { - // TODO: todo - return InsertSuggestionResult("", 0) - } -} - data class InsertSuggestionResult( val text: String, val cursorPosition: Int, -) - -private fun MnemonicErrorResult.mapToError(): SeedPhraseError = when (this) { - MnemonicErrorResult.InvalidWordCount -> SeedPhraseError.InvalidEntropyLength - MnemonicErrorResult.InvalidEntropyLength -> SeedPhraseError.InvalidEntropyLength - MnemonicErrorResult.InvalidWordsFile -> SeedPhraseError.InvalidWordsFile - MnemonicErrorResult.InvalidChecksum -> SeedPhraseError.InvalidChecksum - MnemonicErrorResult.MnenmonicCreationFailed -> SeedPhraseError.MnenmonicCreationFailed - MnemonicErrorResult.NormalizationFailed -> SeedPhraseError.NormalizationFailed - MnemonicErrorResult.UnsupportedLanguage -> SeedPhraseError.UnsupportedLanguage - is MnemonicErrorResult.InvalidWords -> SeedPhraseError.InvalidWords(this.words) -} \ No newline at end of file +) \ No newline at end of file diff --git a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt index 524c8f0adb..77db2b3f13 100644 --- a/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt +++ b/features/onboarding/src/main/java/com/tangem/feature/onboarding/presentation/wallet2/viewmodel/SeedPhraseViewModel.kt @@ -105,7 +105,7 @@ class SeedPhraseViewModel @Inject constructor( } createOrGetDebouncer(field.name).debounce(viewModelScope, context = dispatchers.io) { - val hasError = !interactor.wordIsMatch(textFieldValue.text) + val hasError = !interactor.isWordMatch(textFieldValue.text) if (fieldState.isError != hasError) { updateUi { uiBuilder.checkSeedPhrase.updateTextFieldError(uiState, field, hasError) } }