Updated on 2026-08-14

This commit is contained in:
Tangem 2024-04-05 16:10:49 +08:00
parent a7fcb93df7
commit 500ea8e6f3
12 changed files with 305 additions and 1 deletions

View file

@ -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<WalletManagerFactory>
/** Initialize components */
suspend fun init()
/** Get [WalletManagerFactory] synchronously */
suspend fun getWalletManagerFactorySync(): WalletManagerFactory?
}

View file

@ -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<WalletManagerFactory> 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<WalletManagerFactory> {
return configStorage.get().map { config ->
WalletManagerFactory(
config = config,
accountCreator = accountCreator,
blockchainDataStorage = blockchainDataStorage,
loggers = listOf(blockchainSDKLogger),
)
}
}
}

View file

@ -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<BlockchainSdkConfig>
/** Store [BlockchainSdkConfig] */
suspend fun store(config: BlockchainSdkConfig)
}

View file

@ -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<BlockchainSdkConfig> = configFlow
override suspend fun store(config: BlockchainSdkConfig) {
configFlow.value = config
}
}

View file

@ -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<ConfigValueModel, BlockchainSdkConfig> {
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),
)
}
}
}

View file

@ -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?
}

View file

@ -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<ConfigValueModel> = 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
},
)
}
}