Updated on 2026-08-14

This commit is contained in:
Tangem 2023-04-17 12:37:49 +03:00
parent d4eb48822a
commit f084f256b4
7 changed files with 129 additions and 122 deletions

View file

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

View file

@ -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<String> by lazy { bip39Wordlist.words.toHashSet() }
override fun getWordsDictionary(): Set<String> = 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"
}
}

View file

@ -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<String> by lazy { bip39Wordlist.words.toHashSet() }
override fun getWordsDictionary(): Set<String> = 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"
}
}

View file

@ -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<Mnemonic> {
return runCatching {
repository.generateDefaultMnemonic().apply {
currentMnemonic = this
}
}
}
override suspend fun getMnemonicComponents(): Result<List<String>> {
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<List<String>> {
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<String> {
// 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)
}

View file

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

View file

@ -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<Mnemonic>
suspend fun getMnemonicComponents(): Result<List<String>>
suspend fun wordIsMatch(word: String): Boolean
suspend fun isWordMatch(word: String): Boolean
suspend fun validateMnemonicString(text: String): Result<List<String>>
suspend fun getSuggestions(text: String, hasSelection: Boolean, cursorPosition: Int): List<String>
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<Mnemonic> {
return try {
currentMnemonic = repository.generateDefaultMnemonic()
Result.success(currentMnemonic!!)
} catch (ex: TangemSdkError.MnemonicException) {
Result.failure(ex)
}
}
override suspend fun getMnemonicComponents(): Result<List<String>> {
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<List<String>> {
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<String> {
// 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)
}
)

View file

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