Updated on 2026-08-14

This commit is contained in:
Tangem 2023-04-16 15:46:56 +03:00
parent 3d95481b13
commit f345835792
8 changed files with 2274 additions and 26 deletions

View file

@ -10,4 +10,5 @@ object ModuleErrorCode {
const val NETWORK = 20000 const val NETWORK = 20000
const val DOMAIN = 30000 const val DOMAIN = 30000
const val SALT_PAY = 40000 const val SALT_PAY = 40000
const val ONBOARDING = 50000
} }

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,44 @@
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]
*/
interface MnemonicRepository {
fun getWordsDictionary(): Set<String>
@Throws(TangemSdkError.MnemonicException::class)
fun generateDefaultMnemonic(): Mnemonic
@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,21 @@
package com.tangem.feature.onboarding.data.di
import com.tangem.datasource.asset.AssetReader
import com.tangem.feature.onboarding.data.DefaultMnemonicRepository
import com.tangem.feature.onboarding.data.MnemonicRepository
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class OnboardingDataModule {
@Provides
@Singleton
fun provideSeedPhraseSdkRepository(assetReader: AssetReader): MnemonicRepository {
return DefaultMnemonicRepository(assetReader)
}
}

View file

@ -0,0 +1,41 @@
package com.tangem.feature.onboarding.domain
import com.tangem.common.module.ModuleError
import com.tangem.common.module.ModuleErrorCode
import com.tangem.common.module.ModuleMessage
/**
[REDACTED_AUTHOR]
*/
abstract class OnboardingModuleError(
subCode: Int,
override val message: String,
override val data: Any?,
) : ModuleMessage, ModuleError() {
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
}
}
sealed class SeedPhraseError(
subCode: Int = 0,
message: String? = null,
data: Any? = null,
) : OnboardingModuleError(
subCode = ERROR_CODE_SEED_PHRASE + subCode,
message = message ?: this::class.java.simpleName,
data = data,
) {
object InvalidWordCount : SeedPhraseError(0)
object InvalidEntropyLength : SeedPhraseError(1)
object InvalidWordsFile : SeedPhraseError(2)
object InvalidChecksum : SeedPhraseError(3)
object MnenmonicCreationFailed : SeedPhraseError(4)
object NormalizationFailed : SeedPhraseError(5)
object UnsupportedLanguage : SeedPhraseError(6)
data class InvalidWords(val words: Set<String>) : SeedPhraseError(7)
}

View file

@ -1,16 +0,0 @@
package com.tangem.feature.onboarding.domain
/**
[REDACTED_AUTHOR]
*/
interface SeedPhraseChecker {
suspend fun isSeedPhraseMatch(seedPhrase: List<String>): Boolean
suspend fun isPhraseMatch(phrase: String, source: List<String>): Boolean
}
class DummySeedPhraseChecker : SeedPhraseChecker {
override suspend fun isSeedPhraseMatch(seedPhrase: List<String>): Boolean = false
override suspend fun isPhraseMatch(phrase: String, source: List<String>): Boolean = source.contains(phrase)
}

View file

@ -1,21 +1,109 @@
package com.tangem.feature.onboarding.domain package com.tangem.feature.onboarding.domain
import com.tangem.feature.onboarding.data.DummySeedPhraseRepository import com.tangem.common.core.TangemSdkError
import com.tangem.feature.onboarding.data.SeedPhraseRepository 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] [REDACTED_AUTHOR]
*/ */
interface SeedPhraseInteractor : SeedPhraseChecker { interface SeedPhraseInteractor {
suspend fun generateSeedPhrase(): List<String> suspend fun generateMnemonic(): Result<Mnemonic>
suspend fun getMnemonicComponents(): Result<List<String>>
suspend fun wordIsMatch(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
companion object {
const val MNEMONIC_DELIMITER = " "
}
} }
class DummySeedPhraseInteractor( class DefaultSeedPhraseInteractor @Inject constructor(
private val sdkSeedPhraseRepository: SeedPhraseRepository = DummySeedPhraseRepository(), private val repository: MnemonicRepository,
private val seedPhraseChecker: SeedPhraseChecker = DummySeedPhraseChecker(), ) : SeedPhraseInteractor {
) : SeedPhraseInteractor, SeedPhraseChecker by seedPhraseChecker {
override suspend fun generateSeedPhrase(): List<String> { private var currentMnemonic: Mnemonic? = null
return sdkSeedPhraseRepository.getWordList().words
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

@ -0,0 +1,21 @@
package com.tangem.feature.onboarding.domain.di
import com.tangem.feature.onboarding.data.MnemonicRepository
import com.tangem.feature.onboarding.domain.DefaultSeedPhraseInteractor
import com.tangem.feature.onboarding.domain.SeedPhraseInteractor
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class OnboardingDomainModule {
@Provides
@Singleton
fun provideSeedPhraseInteractor(repository: MnemonicRepository): SeedPhraseInteractor {
return DefaultSeedPhraseInteractor(repository)
}
}