Updated on 2026-08-14
This commit is contained in:
parent
3d95481b13
commit
f345835792
8 changed files with 2274 additions and 26 deletions
|
|
@ -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
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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)
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue