Updated on 2026-08-14
This commit is contained in:
parent
d4eb48822a
commit
f084f256b4
7 changed files with 129 additions and 122 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
)
|
||||
|
|
@ -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) }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue