diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechServiceApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechServiceApi.kt new file mode 100644 index 0000000000..107db49e49 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechServiceApi.kt @@ -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> +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt index e52008bbc5..d229d1dbf1 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/NetworkModule.kt @@ -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]" diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 6b9028a0de..4f36bd773a 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -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 ^ diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/BlockchainSDKFactory.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/BlockchainSDKFactory.kt index c38d226ee4..991f599242 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/BlockchainSDKFactory.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/BlockchainSDKFactory.kt @@ -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 - /** Initialize components */ suspend fun init() diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt index 2a18ece1dc..406be2d877 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt @@ -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> internal typealias BlockchainProviderTypes = Map> @@ -27,25 +23,26 @@ internal typealias BlockchainProviderTypes = Map> /** * 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, private val blockchainProviderTypesStore: RuntimeStore, - private val accountCreator: AccountCreator, - private val blockchainDataStorage: BlockchainDataStorage, - private val blockchainSDKLogger: BlockchainSDKLogger, + private val walletManagerFactoryCreator: WalletManagerFactoryCreator, + dispatchers: CoroutineDispatcherProvider, ) : BlockchainSDKFactory { - override val walletManagerFactory: Flow by lazy(::createWalletManagerFactory) + private val walletManagerFactory: Flow 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 { + private fun createWalletManagerFactory(): Flow { 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(fileName = CONFIG_FILE_NAME) ?: return@launch + val config = assetLoader.load(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(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" } } \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/WalletManagerFactoryCreator.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/WalletManagerFactoryCreator.kt new file mode 100644 index 0000000000..feae18a4e4 --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/WalletManagerFactoryCreator.kt @@ -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), + ) + } +} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt index f3df5595b6..3ebbe75b92 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/di/BlockchainSDKFactoryModule.kt @@ -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, diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/BlockchainProvidersResponseLoader.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/BlockchainProvidersResponseLoader.kt new file mode 100644 index 0000000000..7613213ba4 --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/BlockchainProvidersResponseLoader.kt @@ -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(fileName = PROVIDER_TYPES_FILE_NAME) + } + + private companion object { + const val PROVIDER_TYPES_FILE_NAME = "tangem-app-config/providers_order" + } +} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/storage/DefaultRuntimeStore.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/store/DefaultRuntimeStore.kt similarity index 91% rename from libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/storage/DefaultRuntimeStore.kt rename to libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/store/DefaultRuntimeStore.kt index e670704e85..4278918e98 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/storage/DefaultRuntimeStore.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/store/DefaultRuntimeStore.kt @@ -1,4 +1,4 @@ -package com.tangem.blockchainsdk.storage +package com.tangem.blockchainsdk.store import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/storage/RuntimeStore.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/store/RuntimeStore.kt similarity index 85% rename from libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/storage/RuntimeStore.kt rename to libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/store/RuntimeStore.kt index a3de160654..2969e42576 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/storage/RuntimeStore.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/store/RuntimeStore.kt @@ -1,4 +1,4 @@ -package com.tangem.blockchainsdk.storage +package com.tangem.blockchainsdk.store import kotlinx.coroutines.flow.StateFlow