diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3682efb9f5..0d459e7cb7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -80,6 +80,7 @@ dependencies { implementation(projects.core.deepLinks) implementation(projects.libs.crypto) implementation(projects.libs.auth) + implementation(projects.libs.blockchainSdk) implementation(projects.data.appCurrency) implementation(projects.data.appTheme) diff --git a/domain/legacy/build.gradle.kts b/domain/legacy/build.gradle.kts index bc0e716a7a..f21bb97aa5 100644 --- a/domain/legacy/build.gradle.kts +++ b/domain/legacy/build.gradle.kts @@ -13,6 +13,7 @@ dependencies { implementation(project(":core:utils")) implementation(project(":common")) implementation(project(":libs:auth")) + implementation(projects.libs.blockchainSdk) implementation(projects.domain.demo) implementation(projects.domain.models) implementation(projects.domain.tokens.models) diff --git a/libs/blockchain-sdk/.gitignore b/libs/blockchain-sdk/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/libs/blockchain-sdk/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/libs/blockchain-sdk/build.gradle.kts b/libs/blockchain-sdk/build.gradle.kts new file mode 100644 index 0000000000..3861d2b413 --- /dev/null +++ b/libs/blockchain-sdk/build.gradle.kts @@ -0,0 +1,40 @@ +plugins { + alias(deps.plugins.android.library) + alias(deps.plugins.kotlin.android) + alias(deps.plugins.kotlin.kapt) + id("configuration") +} + +android { + namespace = "com.tangem.libs.blockchain_sdk" +} + +dependencies { + + // region Core modules + implementation(projects.core.datasource) + implementation(projects.core.utils) + // endregion + + // region AndroidX libraries + implementation(deps.androidx.datastore) + // endregion + + // region DI libraries + implementation(deps.hilt.core) + kapt(deps.hilt.kapt) + // endregion + + // region Other libraries + implementation(deps.kotlin.coroutines) + implementation(deps.moshi) + implementation(deps.moshi.kotlin) + implementation(deps.timber) + // endregion + + // region Tangem libraries + implementation(deps.tangem.blockchain) { + exclude(module = "joda-time") + } + // endregion +} \ No newline at end of file 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 new file mode 100644 index 0000000000..c38d226ee4 --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/BlockchainSDKFactory.kt @@ -0,0 +1,21 @@ +package com.tangem.blockchainsdk + +import com.tangem.blockchain.common.WalletManagerFactory +import kotlinx.coroutines.flow.Flow + +/** + * Blockchain SDK components factory + * +[REDACTED_AUTHOR] + */ +interface BlockchainSDKFactory { + + /** Flow of [WalletManagerFactory] */ + val walletManagerFactory: Flow + + /** Initialize components */ + suspend fun init() + + /** Get [WalletManagerFactory] synchronously */ + suspend fun getWalletManagerFactorySync(): WalletManagerFactory? +} \ No newline at end of file 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 new file mode 100644 index 0000000000..7b059dfa48 --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/DefaultBlockchainSDKFactory.kt @@ -0,0 +1,55 @@ +package com.tangem.blockchainsdk + +import com.tangem.blockchain.common.AccountCreator +import com.tangem.blockchain.common.WalletManagerFactory +import com.tangem.blockchain.common.datastorage.BlockchainDataStorage +import com.tangem.blockchain.common.logging.BlockchainSDKLogger +import com.tangem.blockchainsdk.config.ConfigStorage +import com.tangem.blockchainsdk.converters.BlockchainSDKConfigConverter +import com.tangem.blockchainsdk.loader.ConfigLoader +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.map + +/** + * Implementation of Blockchain SDK components factory + * + * @property configLoader config loader + * @property configStorage config storage + * @property accountCreator account creator + * @property blockchainDataStorage blockchain data storage + * @property blockchainSDKLogger blockchain SDK logger + * +[REDACTED_AUTHOR] + */ +internal class DefaultBlockchainSDKFactory( + private val configLoader: ConfigLoader, + private val configStorage: ConfigStorage, + private val accountCreator: AccountCreator, + private val blockchainDataStorage: BlockchainDataStorage, + private val blockchainSDKLogger: BlockchainSDKLogger, +) : BlockchainSDKFactory { + + override val walletManagerFactory: Flow by lazy(::createWalletManagerFactory) + + override suspend fun init() { + val configValueModel = configLoader.load() ?: return + + configStorage.store( + config = BlockchainSDKConfigConverter.convert(value = configValueModel), + ) + } + + override suspend fun getWalletManagerFactorySync(): WalletManagerFactory? = walletManagerFactory.firstOrNull() + + private fun createWalletManagerFactory(): Flow { + return configStorage.get().map { config -> + WalletManagerFactory( + config = config, + 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/config/ConfigStorage.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/ConfigStorage.kt new file mode 100644 index 0000000000..e9aaaf807b --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/ConfigStorage.kt @@ -0,0 +1,18 @@ +package com.tangem.blockchainsdk.config + +import com.tangem.blockchain.common.BlockchainSdkConfig +import kotlinx.coroutines.flow.Flow + +/** + * Storage for [BlockchainSdkConfig] + * +[REDACTED_AUTHOR] + */ +internal interface ConfigStorage { + + /** Get flow of [BlockchainSdkConfig] */ + fun get(): Flow + + /** Store [BlockchainSdkConfig] */ + suspend fun store(config: BlockchainSdkConfig) +} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/RuntimeConfigStorage.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/RuntimeConfigStorage.kt new file mode 100644 index 0000000000..0adb68868e --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/config/RuntimeConfigStorage.kt @@ -0,0 +1,21 @@ +package com.tangem.blockchainsdk.config + +import com.tangem.blockchain.common.BlockchainSdkConfig +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow + +/** + * Runtime storage for [BlockchainSdkConfig] + * +[REDACTED_AUTHOR] + */ +internal class RuntimeConfigStorage : ConfigStorage { + + private val configFlow = MutableStateFlow(value = BlockchainSdkConfig()) + + override fun get(): Flow = configFlow + + override suspend fun store(config: BlockchainSdkConfig) { + configFlow.value = config + } +} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/converters/BlockchainSDKConfigConverter.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/converters/BlockchainSDKConfigConverter.kt new file mode 100644 index 0000000000..d7551a2eea --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/converters/BlockchainSDKConfigConverter.kt @@ -0,0 +1,83 @@ +package com.tangem.blockchainsdk.converters + +import com.tangem.blockchain.common.* +import com.tangem.datasource.config.models.ConfigValueModel +import com.tangem.utils.converter.Converter + +/** + * Converts [ConfigValueModel] to [BlockchainSdkConfig] + * +[REDACTED_AUTHOR] + */ +internal object BlockchainSDKConfigConverter : Converter { + + override fun convert(value: ConfigValueModel): BlockchainSdkConfig { + return BlockchainSdkConfig( + blockchairCredentials = BlockchairCredentials( + apiKey = value.blockchairApiKeys, + authToken = value.blockchairAuthorizationToken, + ), + blockcypherTokens = value.blockcypherTokens, + quickNodeSolanaCredentials = QuickNodeCredentials( + apiKey = value.quiknodeApiKey, + subdomain = value.quiknodeSubdomain, + ), + quickNodeBscCredentials = QuickNodeCredentials( + apiKey = value.bscQuiknodeApiKey, + subdomain = value.bscQuiknodeSubdomain, + ), + infuraProjectId = value.infuraProjectId, + tronGridApiKey = value.tronGridApiKey, + nowNodeCredentials = NowNodeCredentials(value.nowNodesApiKey), + getBlockCredentials = createGetBlockCredentials(value), + kaspaSecondaryApiUrl = value.kaspaSecondaryApiUrl, + tonCenterCredentials = TonCenterCredentials( + mainnetApiKey = value.tonCenterKeys.mainnet, + testnetApiKey = value.tonCenterKeys.testnet, + ), + chiaFireAcademyApiKey = value.chiaFireAcademyApiKey, + chiaTangemApiKey = value.chiaTangemApiKey, + ) + } + + private fun createGetBlockCredentials(configValues: ConfigValueModel): GetBlockCredentials? { + return configValues.getBlockAccessTokens?.let { accessTokens -> + GetBlockCredentials( + xrp = GetBlockAccessToken(jsonRpc = accessTokens.xrp?.jsonRPC), + cardano = GetBlockAccessToken(rosetta = accessTokens.cardano?.rosetta), + avalanche = GetBlockAccessToken(jsonRpc = accessTokens.avalanche?.jsonRPC), + eth = GetBlockAccessToken(jsonRpc = accessTokens.eth?.jsonRPC), + etc = GetBlockAccessToken(jsonRpc = accessTokens.etc?.jsonRPC), + fantom = GetBlockAccessToken(jsonRpc = accessTokens.fantom?.jsonRPC), + rsk = GetBlockAccessToken(jsonRpc = accessTokens.rsk?.jsonRPC), + bsc = GetBlockAccessToken(jsonRpc = accessTokens.bsc?.jsonRPC), + polygon = GetBlockAccessToken(jsonRpc = accessTokens.polygon?.jsonRPC), + gnosis = GetBlockAccessToken(jsonRpc = accessTokens.gnosis?.jsonRPC), + cronos = GetBlockAccessToken(jsonRpc = accessTokens.cronos?.jsonRPC), + solana = GetBlockAccessToken(jsonRpc = accessTokens.solana?.jsonRPC), + ton = GetBlockAccessToken(jsonRpc = accessTokens.ton?.jsonRPC), + tron = GetBlockAccessToken(rest = accessTokens.tron?.rest), + cosmos = GetBlockAccessToken(rest = accessTokens.cosmos?.rest), + near = GetBlockAccessToken(jsonRpc = accessTokens.near?.jsonRPC), + aptos = GetBlockAccessToken(rest = accessTokens.aptos?.rest), + dogecoin = GetBlockAccessToken( + jsonRpc = accessTokens.dogecoin?.jsonRPC, + blockBookRest = accessTokens.dogecoin?.blockBookRest, + ), + litecoin = GetBlockAccessToken( + jsonRpc = accessTokens.litecoin?.jsonRPC, + blockBookRest = accessTokens.litecoin?.blockBookRest, + ), + dash = GetBlockAccessToken( + jsonRpc = accessTokens.dash?.jsonRPC, + blockBookRest = accessTokens.dash?.blockBookRest, + ), + bitcoin = GetBlockAccessToken( + jsonRpc = accessTokens.bitcoin?.jsonRPC, + blockBookRest = accessTokens.bitcoin?.blockBookRest, + ), + algorand = GetBlockAccessToken(rest = accessTokens.algorand?.rest), + ) + } + } +} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/ConfigLoader.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/ConfigLoader.kt new file mode 100644 index 0000000000..570013fd4a --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/ConfigLoader.kt @@ -0,0 +1,14 @@ +package com.tangem.blockchainsdk.loader + +import com.tangem.datasource.config.models.ConfigValueModel + +/** + * Config loader + * +[REDACTED_AUTHOR] + */ +internal interface ConfigLoader { + + /** Load config [ConfigValueModel] */ + suspend fun load(): ConfigValueModel? +} \ No newline at end of file diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/LocalConfigLoader.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/LocalConfigLoader.kt new file mode 100644 index 0000000000..b516a909d3 --- /dev/null +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/loader/LocalConfigLoader.kt @@ -0,0 +1,48 @@ +package com.tangem.blockchainsdk.loader + +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.Moshi +import com.squareup.moshi.adapter +import com.tangem.datasource.asset.AssetReader +import com.tangem.datasource.config.models.ConfigValueModel +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.runCatching +import timber.log.Timber + +/** + * Local config loader + * + * @param buildEnvironment build environment that defines config file name + * @param moshi moshi + * @property assetReader asset reader + * @property dispatchers dispatchers + */ +internal class LocalConfigLoader( + buildEnvironment: String, + moshi: Moshi, + private val assetReader: AssetReader, + private val dispatchers: CoroutineDispatcherProvider, +) : ConfigLoader { + + private val configFileName = "tangem-app-config/config_$buildEnvironment" + + @OptIn(ExperimentalStdlibApi::class) + private val adapter: JsonAdapter = moshi.adapter() + + override suspend fun load(): ConfigValueModel? { + return runCatching(dispatchers.io) { + val json = assetReader.readJson(fileName = configFileName) + adapter.fromJson(json) + } + .fold( + onSuccess = { parsedConfig -> + if (parsedConfig == null) Timber.e(IllegalStateException("Parsed config is null")) + parsedConfig + }, + onFailure = { + Timber.e(it, "Failed to load config from assets") + null + }, + ) + } +} \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 8c94ba65a8..f852c13383 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -75,8 +75,9 @@ include(":core:deep-links:global") // endregion Core modules // region Libs modules -include(":libs:crypto") include(":libs:auth") +include(":libs:blockchain-sdk") +include(":libs:crypto") include(":libs:visa") // endregion Libs modules