Updated on 2026-08-14

This commit is contained in:
Tangem 2024-04-17 09:49:55 +08:00
parent 9d062102b9
commit c642d211b1
10 changed files with 206 additions and 48 deletions

View file

@ -0,0 +1,19 @@
package com.tangem.datasource.api.tangemTech
import com.tangem.datasource.config.models.ProviderModel
import retrofit2.http.GET
import retrofit2.http.Header
/**
* Tangem Tech API for app services
*
[REDACTED_AUTHOR]
*/
interface TangemTechServiceApi {
@GET("v1/networks")
suspend fun getBlockchainProviders(
@Header("card_public_key") cardPublicKey: String,
@Header("card_id") cardId: String,
): Map<String, List<ProviderModel>>
}

View file

@ -6,6 +6,7 @@ import com.tangem.datasource.BuildConfig
import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory
import com.tangem.datasource.api.express.TangemExpressApi
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.TangemTechServiceApi
import com.tangem.datasource.utils.RequestHeader.*
import com.tangem.datasource.utils.addHeaders
import com.tangem.datasource.utils.addLoggers
@ -19,6 +20,7 @@ import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import java.util.concurrent.TimeUnit
import javax.inject.Singleton
@Module
@ -96,6 +98,33 @@ class NetworkModule {
.create(TangemTechApi::class.java)
}
@Provides
@Singleton
fun provideTangemTechServiceApi(
@NetworkMoshi moshi: Moshi,
@ApplicationContext context: Context,
appVersionProvider: AppVersionProvider,
): TangemTechServiceApi {
return Retrofit.Builder()
.addConverterFactory(MoshiConverterFactory.create(moshi))
.addCallAdapterFactory(ApiResponseCallAdapterFactory.create())
.baseUrl(PROD_TANGEM_TECH_BASE_URL)
.client(
OkHttpClient.Builder()
.callTimeout(timeout = 5, unit = TimeUnit.SECONDS)
.addHeaders(
CacheControlHeader,
AppVersionPlatformHeaders(appVersionProvider),
// TODO("refactor header init") get auth data after biometric auth to avoid race condition
// AuthenticationHeader(authProvider),
)
.addLoggers(context)
.build(),
)
.build()
.create(TangemTechServiceApi::class.java)
}
private companion object {
const val PROD_EXPRESS_BASE_URL = "https://express.tangem.com/v1/"
const val DEV_EXPRESS_BASE_URL = "[REDACTED_ENV_URL]"

View file

@ -85,7 +85,7 @@ leakcanary = "2.13"
# endregion Other libraries
# region Tangem
tangemBlockchainSdk = "develop-593"
tangemBlockchainSdk = "develop-598"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "develop-344"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^

View file

@ -1,7 +1,6 @@
package com.tangem.blockchainsdk
import com.tangem.blockchain.common.WalletManagerFactory
import kotlinx.coroutines.flow.Flow
/**
* Blockchain SDK components factory
@ -10,9 +9,6 @@ import kotlinx.coroutines.flow.Flow
*/
interface BlockchainSDKFactory {
/** Flow of [WalletManagerFactory] */
val walletManagerFactory: Flow<WalletManagerFactory>
/** Initialize components */
suspend fun init()

View file

@ -1,25 +1,21 @@
package com.tangem.blockchainsdk
import com.tangem.blockchain.common.AccountCreator
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.blockchain.common.datastorage.BlockchainDataStorage
import com.tangem.blockchain.common.logging.BlockchainSDKLogger
import com.tangem.blockchain.common.network.providers.ProviderType
import com.tangem.blockchainsdk.converters.BlockchainProviderTypesConverter
import com.tangem.blockchainsdk.converters.BlockchainSDKConfigConverter
import com.tangem.blockchainsdk.storage.RuntimeStore
import com.tangem.blockchainsdk.loader.BlockchainProvidersResponseLoader
import com.tangem.blockchainsdk.store.RuntimeStore
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.config.models.ConfigValueModel
import com.tangem.datasource.config.models.ProviderModel
import com.tangem.libs.blockchain_sdk.BuildConfig
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import timber.log.Timber
internal typealias BlockchainProvidersResponse = Map<String, List<ProviderModel>>
internal typealias BlockchainProviderTypes = Map<Blockchain, List<ProviderType>>
@ -27,25 +23,26 @@ internal typealias BlockchainProviderTypes = Map<Blockchain, List<ProviderType>>
/**
* Implementation of Blockchain SDK components factory
*
* @property assetLoader asset loader
* @property configStore blockchain sdk config store
* @property blockchainProviderTypesStore blockchain provider types store
* @property accountCreator account creator
* @property blockchainDataStorage blockchain data storage
* @property blockchainSDKLogger blockchain SDK logger
* @property assetLoader asset loader
* @property blockchainProvidersResponseLoader blockchain providers response loader
* @property configStore blockchain sdk config store
* @property blockchainProviderTypesStore blockchain provider types store
* @property walletManagerFactoryCreator wallet manager factory creator
*
[REDACTED_AUTHOR]
*/
internal class DefaultBlockchainSDKFactory(
private val assetLoader: AssetLoader,
private val blockchainProvidersResponseLoader: BlockchainProvidersResponseLoader,
private val configStore: RuntimeStore<BlockchainSdkConfig>,
private val blockchainProviderTypesStore: RuntimeStore<BlockchainProviderTypes>,
private val accountCreator: AccountCreator,
private val blockchainDataStorage: BlockchainDataStorage,
private val blockchainSDKLogger: BlockchainSDKLogger,
private val walletManagerFactoryCreator: WalletManagerFactoryCreator,
dispatchers: CoroutineDispatcherProvider,
) : BlockchainSDKFactory {
override val walletManagerFactory: Flow<WalletManagerFactory> by lazy(::createWalletManagerFactory)
private val walletManagerFactory: Flow<WalletManagerFactory?> by lazy(::createWalletManagerFactory)
private val mainScope = CoroutineScope(dispatchers.main)
override suspend fun init() {
coroutineScope {
@ -56,24 +53,25 @@ internal class DefaultBlockchainSDKFactory(
override suspend fun getWalletManagerFactorySync(): WalletManagerFactory? = walletManagerFactory.firstOrNull()
private fun createWalletManagerFactory(): Flow<WalletManagerFactory> {
private fun createWalletManagerFactory(): Flow<WalletManagerFactory?> {
return combine(
flow = configStore.get(),
flow2 = blockchainProviderTypesStore.get(),
) { config, blockchainProviderTypes ->
WalletManagerFactory(
config = config,
blockchainProviderTypes = blockchainProviderTypes,
accountCreator = accountCreator,
blockchainDataStorage = blockchainDataStorage,
loggers = listOf(blockchainSDKLogger),
)
}
transform = walletManagerFactoryCreator::create,
)
.stateIn(scope = mainScope, started = SharingStarted.Eagerly, initialValue = null)
}
private fun CoroutineScope.updateBlockchainSDKConfig() {
launch {
val config = assetLoader.load<ConfigValueModel>(fileName = CONFIG_FILE_NAME) ?: return@launch
val config = assetLoader.load<ConfigValueModel>(fileName = CONFIG_FILE_NAME)
if (config == null) {
Timber.e("Error loading BlockchainSDKConfig")
return@launch
}
Timber.d("Update BlockchainSDKConfig")
configStore.store(
value = BlockchainSDKConfigConverter.convert(value = config),
@ -83,17 +81,22 @@ internal class DefaultBlockchainSDKFactory(
private fun CoroutineScope.updateBlockchainProviderTypes() {
launch {
val providerTypes = assetLoader.load<BlockchainProvidersResponse>(fileName = PROVIDER_TYPES_FILE_NAME)
?: return@launch
val response = blockchainProvidersResponseLoader.load()
if (response == null) {
Timber.e("Error loading BlockchainProviderTypes")
return@launch
}
Timber.d("Update BlockchainProviderTypes")
blockchainProviderTypesStore.store(
value = BlockchainProviderTypesConverter.convert(providerTypes),
value = BlockchainProviderTypesConverter.convert(response),
)
}
}
private companion object {
const val CONFIG_FILE_NAME = "tangem-app-config/config_${BuildConfig.ENVIRONMENT}"
const val PROVIDER_TYPES_FILE_NAME = "tangem-app-config/providers_order"
}
}

View file

@ -0,0 +1,37 @@
package com.tangem.blockchainsdk
import com.tangem.blockchain.common.AccountCreator
import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.blockchain.common.WalletManagerFactory
import com.tangem.blockchain.common.datastorage.BlockchainDataStorage
import com.tangem.blockchain.common.logging.BlockchainSDKLogger
import timber.log.Timber
import javax.inject.Inject
/**
* Creator of [WalletManagerFactory]
*
* @property accountCreator account creator
* @property blockchainDataStorage blockchain data storage
* @property blockchainSDKLogger blockchain SDK logger
*
[REDACTED_AUTHOR]
*/
internal class WalletManagerFactoryCreator @Inject constructor(
private val accountCreator: AccountCreator,
private val blockchainDataStorage: BlockchainDataStorage,
private val blockchainSDKLogger: BlockchainSDKLogger,
) {
fun create(config: BlockchainSdkConfig, blockchainProviderTypes: BlockchainProviderTypes): WalletManagerFactory {
Timber.d("Create WalletManagerFactory")
return WalletManagerFactory(
config = config,
blockchainProviderTypes = blockchainProviderTypes,
accountCreator = accountCreator,
blockchainDataStorage = blockchainDataStorage,
loggers = listOf(blockchainSDKLogger),
)
}
}

View file

@ -4,13 +4,16 @@ import com.tangem.blockchain.common.BlockchainSdkConfig
import com.tangem.blockchain.common.logging.BlockchainSDKLogger
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.blockchainsdk.DefaultBlockchainSDKFactory
import com.tangem.blockchainsdk.WalletManagerFactoryCreator
import com.tangem.blockchainsdk.accountcreator.DefaultAccountCreator
import com.tangem.blockchainsdk.datastorage.DefaultBlockchainDataStorage
import com.tangem.blockchainsdk.storage.DefaultRuntimeStore
import com.tangem.blockchainsdk.loader.BlockchainProvidersResponseLoader
import com.tangem.blockchainsdk.store.DefaultRuntimeStore
import com.tangem.datasource.api.common.AuthProvider
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -25,15 +28,29 @@ internal object BlockchainSDKFactoryModule {
@Singleton
fun provideBlockchainSDKFactory(
assetLoader: AssetLoader,
blockchainProvidersResponseLoader: BlockchainProvidersResponseLoader,
walletManagerFactoryCreator: WalletManagerFactoryCreator,
dispatchers: CoroutineDispatcherProvider,
): BlockchainSDKFactory {
return DefaultBlockchainSDKFactory(
assetLoader = assetLoader,
blockchainProvidersResponseLoader = blockchainProvidersResponseLoader,
configStore = DefaultRuntimeStore(defaultValue = BlockchainSdkConfig()),
blockchainProviderTypesStore = DefaultRuntimeStore(defaultValue = emptyMap()),
walletManagerFactoryCreator = walletManagerFactoryCreator,
dispatchers = dispatchers,
)
}
@Provides
@Singleton
fun provideWalletManagerFactoryCreator(
authProvider: AuthProvider,
tangemTechApi: TangemTechApi,
appPreferencesStore: AppPreferencesStore,
blockchainSDKLogger: BlockchainSDKLogger,
): BlockchainSDKFactory {
return DefaultBlockchainSDKFactory(
assetLoader = assetLoader,
configStore = DefaultRuntimeStore(defaultValue = BlockchainSdkConfig()),
blockchainProviderTypesStore = DefaultRuntimeStore(defaultValue = emptyMap()),
): WalletManagerFactoryCreator {
return WalletManagerFactoryCreator(
accountCreator = DefaultAccountCreator(authProvider, tangemTechApi),
blockchainDataStorage = DefaultBlockchainDataStorage(appPreferencesStore),
blockchainSDKLogger = blockchainSDKLogger,

View file

@ -0,0 +1,57 @@
package com.tangem.blockchainsdk.loader
import com.tangem.blockchainsdk.BlockchainProvidersResponse
import com.tangem.datasource.api.common.AuthProvider
import com.tangem.datasource.api.tangemTech.TangemTechServiceApi
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runCatching
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
/**
* Loader of [BlockchainProvidersResponse]
*
* @property tangemTechServiceApi tangem tech api
* @property authProvider auth provider
* @property assetLoader asset loader for local config loading
* @property dispatchers dispatchers
*
[REDACTED_AUTHOR]
*/
@Singleton
internal class BlockchainProvidersResponseLoader @Inject constructor(
private val tangemTechServiceApi: TangemTechServiceApi,
private val authProvider: AuthProvider,
private val assetLoader: AssetLoader,
private val dispatchers: CoroutineDispatcherProvider,
) {
/** Load [BlockchainProvidersResponse] */
suspend fun load(): BlockchainProvidersResponse? {
return runCatching(dispatcher = dispatchers.io, block = ::loadRemote)
.fold(
onSuccess = { it },
onFailure = {
Timber.e(it, "Failed to load blockchain provider types from backend")
loadLocal()
},
)
}
private suspend fun loadRemote(): BlockchainProvidersResponse {
return tangemTechServiceApi.getBlockchainProviders(
cardPublicKey = authProvider.getCardPublicKey(),
cardId = authProvider.getCardId(),
)
}
private suspend fun loadLocal(): BlockchainProvidersResponse? {
return assetLoader.load<BlockchainProvidersResponse>(fileName = PROVIDER_TYPES_FILE_NAME)
}
private companion object {
const val PROVIDER_TYPES_FILE_NAME = "tangem-app-config/providers_order"
}
}

View file

@ -1,4 +1,4 @@
package com.tangem.blockchainsdk.storage
package com.tangem.blockchainsdk.store
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

View file

@ -1,4 +1,4 @@
package com.tangem.blockchainsdk.storage
package com.tangem.blockchainsdk.store
import kotlinx.coroutines.flow.StateFlow