Updated on 2026-08-14

This commit is contained in:
Tangem 2022-11-20 16:20:48 +03:00
commit d2fbd8d438
23 changed files with 461 additions and 73 deletions

View file

@ -111,7 +111,11 @@ dependencies {
implementation(project(":common"))
implementation(project(":core:ui"))
implementation(project(":libs:crypto"))
/** Features */
implementation(project(":features:referral:presentation"))
implementation(project(":features:referral:domain"))
implementation(project(":features:referral:data"))
/** AndroidX libraries */
implementation(AndroidX.coreKtx)
@ -139,9 +143,13 @@ dependencies {
implementation(Firebase.firebaseCrashlytics)
/** Tangem libraries */
implementation(Tangem.blockchain)
implementation(Tangem.blockchain) {
exclude(module = "joda-time")
}
implementation(Tangem.cardCore)
implementation(Tangem.cardAndroid)
implementation(Tangem.cardAndroid) {
exclude(module = "joda-time")
}
/** DI */
implementation(Library.hilt)

View file

@ -184,7 +184,7 @@ class TokensMiddleware {
}
}
private fun deriveMissingBlockchains(
fun deriveMissingBlockchains(
scanResponse: ScanResponse,
currencyList: List<Currency>,
onSuccess: (ScanResponse) -> Unit,

View file

@ -0,0 +1,26 @@
package com.tangem.tap.proxy
import com.tangem.common.card.Card
import com.tangem.domain.common.ScanResponse
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.domain.tokens.UserTokensRepository
import com.tangem.tap.features.tokens.redux.TokensMiddleware
import com.tangem.tap.features.wallet.redux.WalletState
import org.rekotlin.Store
/**
* Holds objects from old modules, that missing in DI graph
* Object sets manually to use in new modules and [AppStateHolder] proxies its to DI
*/
class AppStateHolder {
val scanResponse: ScanResponse? = null
val walletState: WalletState? = null
val userTokensRepository: UserTokensRepository? = null
val mainStore: Store<AppState>? = null
val tokesMiddleware: TokensMiddleware? = null
fun getActualCard(): Card? {
return scanResponse?.card
}
}

View file

@ -1,14 +1,60 @@
package com.tangem.tap.proxy
import com.tangem.crypto.DerivationManager
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.lib.crypto.DerivationManager
import com.tangem.lib.crypto.models.Currency
import com.tangem.lib.crypto.models.NonNativeToken
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import kotlin.coroutines.suspendCoroutine
class DerivationManagerImpl : DerivationManager {
class DerivationManagerImpl(
private val appStateHolder: AppStateHolder,
) : DerivationManager {
override fun deriveMissingBlockchains(networkId: String?) {
TODO("Not yet implemented")
override suspend fun deriveMissingBlockchains(currency: Currency) = suspendCoroutine<Boolean> { continuation ->
val tokesMiddleware = requireNotNull(appStateHolder.tokesMiddleware) { "tokesMiddleware is null" }
val blockchain = Blockchain.fromNetworkId(currency.networkId)
val card = appStateHolder.getActualCard()
if (blockchain != null && card != null) {
val appToken = if (currency is NonNativeToken) {
Token(
symbol = currency.symbol,
contractAddress = currency.contractAddress,
decimals = currency.decimalCount,
)
} else null
val blockchainNetwork = BlockchainNetwork(blockchain, card)
val appCurrency = com.tangem.tap.features.wallet.models.Currency.fromBlockchainNetwork(
blockchainNetwork,
appToken,
)
if (appStateHolder.scanResponse != null) {
tokesMiddleware.deriveMissingBlockchains(
scanResponse = appStateHolder.scanResponse,
listOf(appCurrency),
) {
continuation.resumeWith(Result.success(true))
}
}
} else {
continuation.resumeWith(Result.failure(IllegalStateException("no blockchain or card found")))
}
}
override fun hasDerivation(): Boolean {
TODO("Not yet implemented")
override fun hasDerivation(networkId: String): Boolean {
val scanResponse = appStateHolder.scanResponse
val blockchain = Blockchain.fromNetworkId(networkId)
if (scanResponse != null && blockchain != null) {
val derivationPath = blockchain.derivationPath(appStateHolder.getActualCard()?.derivationStyle)?.rawPath
if (derivationPath.isNullOrEmpty()) return false
return scanResponse.hasDerivation(
blockchain,
derivationPath,
)
}
return false
}
}

View file

@ -1,27 +1,152 @@
package com.tangem.tap.proxy
import com.tangem.crypto.UserWalletManager
import com.tangem.crypto.models.Token
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationParams
import com.tangem.blockchain.common.DerivationStyle
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.common.card.Card
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.common.extensions.toNetworkId
import com.tangem.lib.crypto.UserWalletManager
import com.tangem.lib.crypto.models.Currency
import com.tangem.lib.crypto.models.NativeToken
import com.tangem.lib.crypto.models.NonNativeToken
import com.tangem.tap.domain.extensions.getUserWalletId
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
import com.tangem.tap.features.wallet.redux.WalletAction
import org.rekotlin.Action
class UserWalletManagerImpl : UserWalletManager {
class UserWalletManagerImpl(
private val appStateHolder: AppStateHolder,
private val walletManagerFactory: WalletManagerFactory,
) : UserWalletManager {
override fun getUserTokens(): List<Token> {
TODO("Not yet implemented")
override suspend fun getUserTokens(): List<Currency> {
val card = appStateHolder.getActualCard()
val userTokensRepository =
requireNotNull(appStateHolder.userTokensRepository) { "userTokensRepository is null" }
if (card != null) {
userTokensRepository.getUserTokens(card)
.filter { it.isToken() }
.map {
if (it is com.tangem.tap.features.wallet.models.Currency.Token) {
NonNativeToken(
id = it.coinId ?: "",
name = it.currencyName,
symbol = it.currencySymbol,
networkId = it.blockchain.toNetworkId(),
contractAddress = it.token.contractAddress,
decimalCount = it.token.decimals,
)
} else {
NativeToken(
id = it.coinId ?: "",
name = it.currencyName,
symbol = it.currencySymbol,
networkId = it.blockchain.toNetworkId(),
)
}
}
}
return emptyList()
}
override fun getWalletId(): String {
TODO("Not yet implemented")
return appStateHolder.getActualCard()?.getUserWalletId() ?: ""
}
override fun isTokenAdded(token: Token): Boolean {
TODO("Not yet implemented")
override suspend fun isTokenAdded(currency: Currency): Boolean {
val card = appStateHolder.getActualCard()
val userTokensRepository =
requireNotNull(appStateHolder.userTokensRepository) { "userTokensRepository is null" }
if (card != null) {
return userTokensRepository.getUserTokens(card)
.any { it.coinId.equals(currency.id) } //todo ensure that its the same ids
}
return false
}
override fun addToken(token: Token) {
TODO("Not yet implemented")
//todo check is it sync operation
override fun addToken(currency: Currency) {
val action = if (currency is NativeToken) {
val card = appStateHolder.getActualCard()
if (card != null) {
addNativeTokenToWalletAction(currency, card)
} else {
throw IllegalStateException("card not found")
}
} else {
addNonNativeTokenToWalletAction(currency as NonNativeToken)
}
val mainStore = requireNotNull(appStateHolder.mainStore) { "mainStore is null" }
mainStore.dispatch(action)
}
override fun getWalletAddress(token: Token): String {
TODO("Not yet implemented")
override fun getWalletAddress(currency: Currency): String {
val blockchain = Blockchain.fromNetworkId(currency.networkId)
val card = appStateHolder.getActualCard()
if (blockchain != null && card != null) {
val blockchainNetwork = BlockchainNetwork(blockchain, card)
val walletManager = appStateHolder.walletState?.getWalletManager(blockchainNetwork)
if (walletManager != null) {
return walletManager.wallet.address
} else {
throw IllegalStateException("no wallet manager found")
}
} else {
throw IllegalStateException("no blockchain or card found")
}
}
private fun addNativeTokenToWalletAction(token: NativeToken, card: Card): Action {
val blockchain = Blockchain.fromNetworkId(token.networkId)
val scanResponse = appStateHolder.scanResponse
if (blockchain != null && scanResponse != null) {
val blockchainNetwork = BlockchainNetwork(blockchain, card)
var walletManager = appStateHolder.walletState?.getWalletManager(blockchainNetwork)
if (walletManager == null) {
walletManager = walletManagerFactory.makeWalletManagerForApp(
scanResponse = scanResponse,
blockchain = blockchain,
derivationParams = createDerivationParams(card.derivationStyle),
)
}
return WalletAction.MultiWallet.AddBlockchain(
blockchain = blockchainNetwork,
walletManager = walletManager,
save = true,
)
} else {
throw IllegalStateException("blockchain is not supported")
}
}
private fun addNonNativeTokenToWalletAction(token: NonNativeToken): Action {
val card = appStateHolder.getActualCard()
val blockchain = Blockchain.fromNetworkId(token.networkId)
if (card != null && blockchain != null) {
return WalletAction.MultiWallet.AddToken(
token = com.tangem.blockchain.common.Token(
id = token.id,
name = token.name,
symbol = token.symbol,
contractAddress = token.contractAddress,
decimals = token.decimalCount,
),
blockchain = BlockchainNetwork(
blockchain,
card,
),
save = true,
)
} else {
throw IllegalStateException("no card or blockchain found")
}
}
private fun createDerivationParams(derivationStyle: DerivationStyle?): DerivationParams? {
return derivationStyle?.let { DerivationParams.Default(derivationStyle) } //todo clarify if its need to add Custom
}
}

View file

@ -0,0 +1,41 @@
package com.tangem.tap.proxy.di
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.lib.crypto.DerivationManager
import com.tangem.lib.crypto.UserWalletManager
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.proxy.DerivationManagerImpl
import com.tangem.tap.proxy.UserWalletManagerImpl
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class ProxyModule {
@Provides
@Singleton
fun provideAppStateHolder(): AppStateHolder {
return AppStateHolder()
}
@Provides
@Singleton
fun provideUserWalletManager(appStateHolder: AppStateHolder): UserWalletManager {
return UserWalletManagerImpl(
appStateHolder = appStateHolder,
walletManagerFactory = WalletManagerFactory(),
)
}
@Provides
@Singleton
fun provideDerivationManager(appStateHolder: AppStateHolder): DerivationManager {
return DerivationManagerImpl(
appStateHolder = appStateHolder,
)
}
}

View file

@ -6,7 +6,6 @@ plugins {
}
android {
defaultConfig {
compileSdk = AppConfig.compileSdkVersion
minSdk = AppConfig.minSdkVersion

View file

@ -0,0 +1,16 @@
package com.tangem.utils.di
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
interface CoroutineDispatcherProviderModule {
@Binds
fun bindCoroutineDispatcherProvider(coroutineDispatcherProvider: AppCoroutineDispatcherProvider): CoroutineDispatcherProvider
}

View file

@ -75,9 +75,13 @@ dependencies {
implementation(project(":common"))
/** Tangem libraries */
implementation(Tangem.blockchain)
implementation(Tangem.blockchain) {
exclude(module = "joda-time")
}
implementation(Tangem.cardCore)
implementation(Tangem.cardAndroid)
implementation(Tangem.cardAndroid) {
exclude(module = "joda-time")
}
/** Other libraries */
implementation(Library.reKotlin)

View file

@ -7,9 +7,8 @@ import com.tangem.feature.referral.domain.ReferralRepository
import com.tangem.feature.referral.domain.models.ReferralData
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
import javax.inject.Inject
internal class ReferralRepositoryImpl @Inject constructor(
internal class ReferralRepositoryImpl(
private val referralApi: ReferralApi,
private val referralConverter: ReferralConverter,
private val coroutineDispatcher: CoroutineDispatcherProvider,

View file

@ -0,0 +1,29 @@
package com.tangem.feature.referral.di
import com.tangem.datasource.api.referral.ReferralApi
import com.tangem.feature.referral.converters.ReferralConverter
import com.tangem.feature.referral.data.ReferralRepositoryImpl
import com.tangem.feature.referral.domain.ReferralRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class ReferralRepositoryModule {
@Provides
@Singleton
fun provideReferralRepository(
referralApi: ReferralApi,
referralConverter: ReferralConverter,
coroutineDispatcherProvider: CoroutineDispatcherProvider,
): ReferralRepository {
return ReferralRepositoryImpl(
referralApi, referralConverter, coroutineDispatcherProvider,
)
}
}

View file

@ -1,11 +1,11 @@
package com.tangem.feature.referral.domain
import com.tangem.crypto.DerivationManager
import com.tangem.crypto.UserWalletManager
import com.tangem.crypto.models.Token
import com.tangem.feature.referral.domain.converter.TokensConverter
import com.tangem.feature.referral.domain.models.ReferralData
import com.tangem.feature.referral.domain.models.TokenData
import com.tangem.lib.crypto.DerivationManager
import com.tangem.lib.crypto.UserWalletManager
import com.tangem.lib.crypto.models.Currency
internal class ReferralInteractorImpl(
private val repository: ReferralRepository,
@ -24,13 +24,13 @@ internal class ReferralInteractorImpl(
override suspend fun startReferral(): ReferralData {
if (tokensForReferral.isNotEmpty()) {
val token = tokensConverter.convert(tokensForReferral.first())
deriveOrAddTokens(token)
val publicAddress = userWalletManager.getWalletAddress(token)
val currency = tokensConverter.convert(tokensForReferral.first())
deriveOrAddTokens(currency)
val publicAddress = userWalletManager.getWalletAddress(currency)
return repository.startReferral(
walletId = userWalletManager.getWalletId(),
networkId = token.networkId ?: "",
tokenId = token.id,
networkId = currency.networkId,
tokenId = currency.id,
address = publicAddress,
)
} else {
@ -38,12 +38,12 @@ internal class ReferralInteractorImpl(
}
}
private fun deriveOrAddTokens(token: Token) {
if (!derivationManager.hasDerivation()) {
derivationManager.deriveMissingBlockchains(token.networkId)
private suspend fun deriveOrAddTokens(currency: Currency) {
if (!derivationManager.hasDerivation(currency.networkId)) {
derivationManager.deriveMissingBlockchains(currency)
}
if (!userWalletManager.isTokenAdded(token)) {
userWalletManager.addToken(token)
if (!userWalletManager.isTokenAdded(currency)) {
userWalletManager.addToken(currency)
}
}

View file

@ -1,19 +1,33 @@
package com.tangem.feature.referral.domain.converter
import com.tangem.crypto.models.Token
import com.tangem.feature.referral.domain.models.TokenData
import com.tangem.lib.crypto.models.Currency
import com.tangem.lib.crypto.models.NativeToken
import com.tangem.lib.crypto.models.NonNativeToken
import com.tangem.utils.converter.Converter
import javax.inject.Inject
class TokensConverter : Converter<TokenData, Token> {
class TokensConverter @Inject constructor() : Converter<TokenData, Currency> {
override fun convert(value: TokenData): Token {
return Token(
id = value.id,
name = value.name,
symbol = value.symbol,
networkId = value.networkId,
contractAddress = value.contractAddress,
decimalCount = value.decimalCount,
)
override fun convert(value: TokenData): Currency {
return if (value.decimalCount != null &&
value.contractAddress != null
) {
NonNativeToken(
id = value.id,
name = value.name,
symbol = value.symbol,
networkId = value.networkId,
contractAddress = value.contractAddress,
decimalCount = value.decimalCount,
)
} else {
NativeToken(
id = value.id,
name = value.name,
symbol = value.symbol,
networkId = value.networkId,
)
}
}
}

View file

@ -0,0 +1,31 @@
package com.tangem.feature.referral.domain.di
import com.tangem.feature.referral.domain.ReferralInteractor
import com.tangem.feature.referral.domain.ReferralInteractorImpl
import com.tangem.feature.referral.domain.ReferralRepository
import com.tangem.feature.referral.domain.converter.TokensConverter
import com.tangem.lib.crypto.DerivationManager
import com.tangem.lib.crypto.UserWalletManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class ReferralDomainModule {
@Provides
@Singleton
fun provideReferralInteractor(
referralRepository: ReferralRepository,
derivationManager: DerivationManager,
userWalletManager: UserWalletManager,
tokensConverter: TokensConverter,
): ReferralInteractor {
return ReferralInteractorImpl(
referralRepository, derivationManager, userWalletManager, tokensConverter,
)
}
}

View file

@ -34,7 +34,7 @@ data class TokenData(
val id: String,
val name: String,
val symbol: String,
val networkId: String?,
val networkId: String,
val contractAddress: String?,
val decimalCount: Int?,
)

View file

@ -1,6 +1,8 @@
plugins {
id("com.android.library")
kotlin("android")
kotlin("kapt")
id("com.google.dagger.hilt.android")
}
android {
@ -42,6 +44,13 @@ dependencies {
implementation(Compose.material)
implementation(Compose.uiTooling)
/** Domain */
implementation(project(":features:referral:domain"))
/** Other libraries */
implementation(Library.composeShimmer)
/** DI */
implementation(Library.hilt)
kapt(Library.hiltKapt)
}

View file

@ -0,0 +1,11 @@
package com.tangem.feature.referral.presentation
import androidx.lifecycle.ViewModel
import com.tangem.feature.referral.domain.ReferralInteractor
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class ReferralViewModel @Inject constructor(referralInteractor: ReferralInteractor) : ViewModel() {
}

View file

@ -1,8 +1,18 @@
package com.tangem.crypto
package com.tangem.lib.crypto
import com.tangem.lib.crypto.models.Currency
interface DerivationManager {
fun deriveMissingBlockchains(networkId: String?)
/**
* Derives missing blockchain and returns flag true if did it successfully
*
* @param currency to derive (Native token or not)
*/
suspend fun deriveMissingBlockchains(currency: Currency): Boolean
fun hasDerivation(): Boolean
/**
* Checks that given [networkId] has derivations
*/
fun hasDerivation(networkId: String): Boolean
}

View file

@ -1,6 +1,6 @@
package com.tangem.crypto
package com.tangem.lib.crypto
import com.tangem.crypto.models.Token
import com.tangem.lib.crypto.models.Currency
/**
* Provider for user tokens data
@ -10,7 +10,7 @@ interface UserWalletManager {
/**
* Returns all user tokens (merged from local and backend)
*/
fun getUserTokens(): List<Token>
suspend fun getUserTokens(): List<Currency>
/**
* Returns user walletId
@ -20,21 +20,21 @@ interface UserWalletManager {
/**
* Checks that token added to user wallet
*
* @param token to receive referral payments
* @param currency to receive referral payments
*/
fun isTokenAdded(token: Token): Boolean
suspend fun isTokenAdded(currency: Currency): Boolean
/**
* Adds token to wallet if its not
*
* @param token to add to wallet
* @param currency to add to wallet
*/
fun addToken(token: Token)
fun addToken(currency: Currency)
/**
* Returns wallet public address for token
*
* @param token for which find address
* @param currency for which find address
*/
fun getWalletAddress(token: Token): String
fun getWalletAddress(currency: Currency): String
}

View file

@ -0,0 +1,12 @@
package com.tangem.lib.crypto.models
/**
* Currency data class that can be native blockchain Token
* or custom Token (used in this lib to replace and divide logic Currency from app module)
*/
abstract class Currency(
open val id: String,
open val name: String,
open val symbol: String,
open val networkId: String,
)

View file

@ -0,0 +1,8 @@
package com.tangem.lib.crypto.models
data class NativeToken(
override val id: String,
override val name: String,
override val symbol: String,
override val networkId: String,
) : Currency(id, name, symbol, networkId)

View file

@ -0,0 +1,10 @@
package com.tangem.lib.crypto.models
class NonNativeToken(
override val id: String,
override val name: String,
override val symbol: String,
override val networkId: String,
val contractAddress: String,
val decimalCount: Int,
) : Currency(id, name, symbol, networkId)

View file

@ -1,10 +0,0 @@
package com.tangem.crypto.models
data class Token(
val id: String,
val name: String,
val symbol: String,
val networkId: String?,
val contractAddress: String?,
val decimalCount: Int?,
)