Updated on 2026-08-14
This commit is contained in:
parent
330ae73357
commit
e7efe7b76a
4636 changed files with 234864 additions and 63507 deletions
|
|
@ -1,37 +1,9 @@
|
|||
plugins {
|
||||
id("com.android.library")
|
||||
kotlin("android")
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
|
||||
defaultConfig {
|
||||
compileSdk = AppConfig.compileSdkVersion
|
||||
minSdk = AppConfig.minSdkVersion
|
||||
targetSdk = AppConfig.targetSdkVersion
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
isCoreLibraryDesugaringEnabled = false
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
create("debug_beta") {
|
||||
initWith(getByName("release"))
|
||||
BuildConfigFieldFactory(
|
||||
fields = listOf(
|
||||
Field.Environment("release"),
|
||||
Field.TestActionEnabled(true),
|
||||
Field.LogEnabled(true),
|
||||
),
|
||||
builder = ::buildConfigField,
|
||||
).create()
|
||||
}
|
||||
}
|
||||
namespace = "com.tangem.lib.auth"
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.tangem.lib.auth" />
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package com.tangem.lib.auth
|
||||
|
||||
/**
|
||||
* Provides auth for tangemTech API
|
||||
*/
|
||||
interface AuthProvider {
|
||||
|
||||
/**
|
||||
* Returns authToken for tangem tech api
|
||||
*/
|
||||
fun getCardPublicKey(): String
|
||||
|
||||
fun getCardId(): String
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.lib.auth
|
||||
|
||||
interface ExpressAuthProvider {
|
||||
|
||||
fun getUserId(): String
|
||||
|
||||
fun getSessionId(): String
|
||||
|
||||
fun getRefCode(): String
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.lib.auth
|
||||
|
||||
interface StakeKitAuthProvider {
|
||||
|
||||
fun getApiKey(): String
|
||||
}
|
||||
1
libs/blockchain-sdk/.gitignore
vendored
Normal file
1
libs/blockchain-sdk/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
56
libs/blockchain-sdk/build.gradle.kts
Normal file
56
libs/blockchain-sdk/build.gradle.kts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import com.tangem.plugin.configuration.configurations.extension.kaptForObfuscatingVariants
|
||||
|
||||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.libs.blockchain_sdk"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
// region Core modules
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.utils)
|
||||
// endregion
|
||||
|
||||
// region AndroidX libraries
|
||||
implementation(deps.androidx.datastore)
|
||||
// endregion
|
||||
|
||||
// region DI libraries
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
// endregion
|
||||
|
||||
// region Other libraries
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.moshi)
|
||||
implementation(deps.moshi.kotlin)
|
||||
implementation(deps.timber)
|
||||
kaptForObfuscatingVariants(deps.moshi.kotlin.codegen)
|
||||
kaptForObfuscatingVariants(deps.retrofit.response.type.keeper)
|
||||
// endregion
|
||||
|
||||
// region Firebase libraries
|
||||
implementation(platform(deps.firebase.bom))
|
||||
implementation(deps.firebase.analytics)
|
||||
implementation(deps.firebase.crashlytics)
|
||||
// endregion
|
||||
|
||||
// region Tangem libraries
|
||||
implementation(tangemDeps.blockchain) { exclude(module = "joda-time") }
|
||||
implementation(tangemDeps.card.core)
|
||||
// endregion
|
||||
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.junit)
|
||||
testImplementation(deps.test.mockk)
|
||||
testImplementation(deps.test.truth)
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.blockchainsdk
|
||||
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
|
||||
/**
|
||||
* Blockchain SDK components factory
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface BlockchainSDKFactory {
|
||||
|
||||
/** Initialize components */
|
||||
suspend fun init()
|
||||
|
||||
/** Get [WalletManagerFactory] synchronously */
|
||||
suspend fun getWalletManagerFactorySync(): WalletManagerFactory?
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.tangem.blockchainsdk
|
||||
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.blockchainsdk.providers.BlockchainProvidersTypesManager
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.datasource.local.config.providers.models.ProviderModel
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
internal typealias BlockchainProvidersResponse = Map<String, List<ProviderModel>>
|
||||
|
||||
/**
|
||||
* Implementation of Blockchain SDK components factory
|
||||
*
|
||||
* @property blockchainProvidersTypesManager blockchain providers types manager
|
||||
* @property environmentConfigStorage environment config storage
|
||||
* @property walletManagerFactoryCreator wallet manager factory creator
|
||||
* @param dispatchers coroutine dispatchers provider
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultBlockchainSDKFactory(
|
||||
private val blockchainProvidersTypesManager: BlockchainProvidersTypesManager,
|
||||
private val environmentConfigStorage: EnvironmentConfigStorage,
|
||||
private val walletManagerFactoryCreator: WalletManagerFactoryCreator,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
) : BlockchainSDKFactory {
|
||||
|
||||
private val mainScope = CoroutineScope(dispatchers.main)
|
||||
|
||||
private val walletManagerFactory: Flow<WalletManagerFactory?> = createWalletManagerFactory()
|
||||
|
||||
override suspend fun init() {
|
||||
coroutineScope {
|
||||
launch { blockchainProvidersTypesManager.update() }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getWalletManagerFactorySync(): WalletManagerFactory? = walletManagerFactory.firstOrNull()
|
||||
|
||||
private fun createWalletManagerFactory(): Flow<WalletManagerFactory?> {
|
||||
return combine(
|
||||
flow = environmentConfigStorage.getConfig().map { it.blockchainSdkConfig },
|
||||
flow2 = blockchainProvidersTypesManager.get(),
|
||||
// flow3 = subscribe on feature toggles changes, TODO: [REDACTED_JIRA]
|
||||
transform = walletManagerFactoryCreator::create,
|
||||
)
|
||||
// don't use Lazily because some features (WC) require initialized factory on app started
|
||||
.stateIn(scope = mainScope, started = SharingStarted.Eagerly, initialValue = null)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.tangem.blockchainsdk
|
||||
|
||||
import com.tangem.blockchain.common.AccountCreator
|
||||
import com.tangem.blockchain.common.BlockchainFeatureToggles
|
||||
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.blockchainsdk.featuretoggles.BlockchainSDKFeatureToggles
|
||||
import com.tangem.blockchainsdk.providers.BlockchainProviderTypes
|
||||
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,
|
||||
private val blockchainSDKFeatureToggles: BlockchainSDKFeatureToggles,
|
||||
) {
|
||||
|
||||
fun create(config: BlockchainSdkConfig, blockchainProviderTypes: BlockchainProviderTypes): WalletManagerFactory {
|
||||
Timber.i("Create WalletManagerFactory")
|
||||
|
||||
return WalletManagerFactory(
|
||||
config = config,
|
||||
blockchainProviderTypes = blockchainProviderTypes,
|
||||
accountCreator = accountCreator,
|
||||
featureToggles = BlockchainFeatureToggles(
|
||||
isEthereumEIP1559Enabled = blockchainSDKFeatureToggles.isEthereumEIP1559Enabled,
|
||||
),
|
||||
blockchainDataStorage = blockchainDataStorage,
|
||||
loggers = listOf(blockchainSDKLogger),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.blockchainsdk.accountcreator
|
||||
|
||||
import com.tangem.blockchain.common.AccountCreator
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.BlockchainSdkError
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.CreateUserNetworkAccountBody
|
||||
|
||||
internal class DefaultAccountCreator(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
) : AccountCreator {
|
||||
|
||||
override suspend fun createAccount(blockchain: Blockchain, walletPublicKey: ByteArray): Result<String> {
|
||||
val request = CreateUserNetworkAccountBody(
|
||||
networkId = blockchain.id.removeSuffix("/test"),
|
||||
walletPublicKey = walletPublicKey.toHexString(),
|
||||
)
|
||||
return try {
|
||||
val response = tangemTechApi.createUserNetworkAccount(
|
||||
body = request,
|
||||
).getOrThrow()
|
||||
Result.Success(response.data.accountId)
|
||||
} catch (e: Exception) {
|
||||
Result.Failure(BlockchainSdkError.FailedToCreateAccount)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.blockchainsdk.compatibility
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.toCoinId
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.datasource.api.markets.models.response.TokenMarketInfoResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.CoinsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
|
||||
val l2BlockchainsList = Blockchain.entries.filter { it.isL2EthereumNetwork() }
|
||||
|
||||
val l2BlockchainsCoinIds = l2BlockchainsList.map { it.toCoinId() }
|
||||
|
||||
val ETHEREUM_COIN_ID = Blockchain.Ethereum.toCoinId()
|
||||
|
||||
fun getL2CompatibilityTokenComparison(token: UserTokensResponse.Token, currencyId: String): Boolean {
|
||||
return if (currencyId == ETHEREUM_COIN_ID) {
|
||||
l2BlockchainsCoinIds.contains(token.id) || currencyId == token.id
|
||||
} else {
|
||||
token.id == currencyId
|
||||
}
|
||||
}
|
||||
|
||||
fun List<CoinsResponse.Coin.Network>.applyL2Compatibility(coinId: String): List<CoinsResponse.Coin.Network> {
|
||||
return if (coinId == ETHEREUM_COIN_ID) {
|
||||
val l2Networks = l2BlockchainsList.map {
|
||||
CoinsResponse.Coin.Network(
|
||||
networkId = it.toNetworkId(),
|
||||
)
|
||||
}
|
||||
this + l2Networks
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
fun TokenMarketInfoResponse.applyL2Compatibility(coinId: String): TokenMarketInfoResponse {
|
||||
val networks = this.networks ?: return this
|
||||
return if (coinId == ETHEREUM_COIN_ID) {
|
||||
val l2Networks = l2BlockchainsList.map {
|
||||
TokenMarketInfoResponse.Network(
|
||||
networkId = it.toNetworkId(),
|
||||
contractAddress = null,
|
||||
decimalCount = null,
|
||||
)
|
||||
}
|
||||
this.copy(networks = networks + l2Networks)
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
fun getTokenIdIfL2Network(tokenId: String): String {
|
||||
return if (l2BlockchainsCoinIds.contains(tokenId)) {
|
||||
ETHEREUM_COIN_ID
|
||||
} else {
|
||||
tokenId
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package com.tangem.blockchainsdk.converters
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.BlockchainProvidersResponse
|
||||
import com.tangem.blockchainsdk.providers.BlockchainProviderTypes
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Converts [BlockchainProvidersResponse] to [BlockchainProviderTypes] and vice versa
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal object BlockchainProviderTypesConverter :
|
||||
TwoWayConverter<BlockchainProvidersResponse, BlockchainProviderTypes> {
|
||||
|
||||
override fun convert(value: BlockchainProvidersResponse): BlockchainProviderTypes {
|
||||
return value.mapNotNull { (networkId, blockchainProviders) ->
|
||||
val blockchain = Blockchain.fromNetworkId(networkId) ?: return@mapNotNull null
|
||||
|
||||
val providerTypes = ProviderTypeConverter.convertList(input = blockchainProviders)
|
||||
|
||||
providerTypes.forEach {
|
||||
if (it == null) Timber.e("$blockchain provider type is not supported")
|
||||
}
|
||||
|
||||
blockchain to providerTypes.filterNotNull()
|
||||
}
|
||||
.toMap()
|
||||
}
|
||||
|
||||
override fun convertBack(value: BlockchainProviderTypes): BlockchainProvidersResponse {
|
||||
return value.mapNotNull { (blockchain, providerTypes) ->
|
||||
val networkId = blockchain.toNetworkId()
|
||||
|
||||
val blockchainProviders = ProviderTypeConverter.convertListBack(input = providerTypes)
|
||||
|
||||
networkId to blockchainProviders
|
||||
}
|
||||
.toMap()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.blockchainsdk.converters
|
||||
|
||||
import com.tangem.blockchain.common.network.providers.ProviderType
|
||||
import com.tangem.blockchainsdk.providers.ProviderTypeIdMapping
|
||||
import com.tangem.datasource.local.config.providers.models.ProviderModel
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
|
||||
/**
|
||||
* Converts [ProviderModel] to [ProviderType] and vice versa
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal object ProviderTypeConverter : TwoWayConverter<ProviderModel, ProviderType?> {
|
||||
|
||||
override fun convert(value: ProviderModel): ProviderType? {
|
||||
return when (value) {
|
||||
is ProviderModel.Public -> ProviderType.Public(url = value.url)
|
||||
is ProviderModel.Private -> ProviderTypeIdMapping.entries.firstOrNull { it.id == value.name }?.providerType
|
||||
ProviderModel.UnsupportedType -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun convertBack(value: ProviderType?): ProviderModel {
|
||||
return when (value) {
|
||||
null -> ProviderModel.UnsupportedType
|
||||
is ProviderType.Public -> ProviderModel.Public(url = value.url)
|
||||
else -> {
|
||||
val id = ProviderTypeIdMapping.entries.firstOrNull { it.providerType == value }?.id
|
||||
?: return ProviderModel.UnsupportedType
|
||||
|
||||
ProviderModel.Private(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.blockchainsdk.datastorage
|
||||
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import com.tangem.blockchain.common.datastorage.BlockchainDataStorage
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrNull
|
||||
|
||||
/**
|
||||
* [BlockchainDataStorage] implementation
|
||||
*
|
||||
* @property appPreferencesStore app preferences store
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultBlockchainDataStorage(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
) : BlockchainDataStorage {
|
||||
|
||||
override suspend fun getOrNull(key: String): String? {
|
||||
return appPreferencesStore.getSyncOrNull(key = stringPreferencesKey(name = key))
|
||||
}
|
||||
|
||||
override suspend fun store(key: String, value: String) {
|
||||
appPreferencesStore.edit {
|
||||
it[stringPreferencesKey(key)] = value
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun remove(key: String) {
|
||||
appPreferencesStore.edit {
|
||||
it.remove(stringPreferencesKey(key))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
package com.tangem.blockchainsdk.di
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.core.DataStoreFactory
|
||||
import androidx.datastore.dataStoreFile
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.common.logging.BlockchainSDKLogger
|
||||
import com.tangem.blockchainsdk.BlockchainProvidersResponse
|
||||
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.featuretoggles.DefaultBlockchainSDKFeatureToggles
|
||||
import com.tangem.blockchainsdk.providers.BlockchainProviderTypesStore
|
||||
import com.tangem.blockchainsdk.providers.BlockchainProvidersTypesManager
|
||||
import com.tangem.blockchainsdk.providers.DevBlockchainProvidersTypesManager
|
||||
import com.tangem.blockchainsdk.providers.ProdBlockchainProvidersTypesManager
|
||||
import com.tangem.blockchainsdk.providers.dev.BlockchainProvidersResponseSerializer
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.libs.blockchain_sdk.BuildConfig
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object BlockchainSDKFactoryModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBlockchainSDKFactory(
|
||||
blockchainProvidersTypesManager: BlockchainProvidersTypesManager,
|
||||
environmentConfigStorage: EnvironmentConfigStorage,
|
||||
walletManagerFactoryCreator: WalletManagerFactoryCreator,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): BlockchainSDKFactory {
|
||||
return DefaultBlockchainSDKFactory(
|
||||
blockchainProvidersTypesManager = blockchainProvidersTypesManager,
|
||||
environmentConfigStorage = environmentConfigStorage,
|
||||
walletManagerFactoryCreator = walletManagerFactoryCreator,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideBlockchainProvidersTypesManager(
|
||||
prodBlockchainProvidersTypesManager: ProdBlockchainProvidersTypesManager,
|
||||
blockchainProviderTypesStore: BlockchainProviderTypesStore,
|
||||
changedBlockchainProvidersStore: DataStore<BlockchainProvidersResponse>,
|
||||
): BlockchainProvidersTypesManager {
|
||||
return if (BuildConfig.TESTER_MENU_ENABLED) {
|
||||
DevBlockchainProvidersTypesManager(
|
||||
prodBlockchainProvidersTypesManager = prodBlockchainProvidersTypesManager,
|
||||
blockchainProviderTypesStore = blockchainProviderTypesStore,
|
||||
changedBlockchainProvidersStore = changedBlockchainProvidersStore,
|
||||
)
|
||||
} else {
|
||||
prodBlockchainProvidersTypesManager
|
||||
}
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideChangedBlockchainProvidersResponseDataStore(
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
@ApplicationContext context: Context,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): DataStore<BlockchainProvidersResponse> {
|
||||
return DataStoreFactory.create(
|
||||
serializer = BlockchainProvidersResponseSerializer(moshi),
|
||||
produceFile = { context.dataStoreFile("changed_providers") },
|
||||
scope = CoroutineScope(dispatchers.io + SupervisorJob()),
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideWalletManagerFactoryCreator(
|
||||
tangemTechApi: TangemTechApi,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
blockchainSDKLogger: BlockchainSDKLogger,
|
||||
featureTogglesManager: FeatureTogglesManager,
|
||||
): WalletManagerFactoryCreator {
|
||||
return WalletManagerFactoryCreator(
|
||||
accountCreator = DefaultAccountCreator(tangemTechApi),
|
||||
blockchainDataStorage = DefaultBlockchainDataStorage(appPreferencesStore),
|
||||
blockchainSDKLogger = blockchainSDKLogger,
|
||||
blockchainSDKFeatureToggles = DefaultBlockchainSDKFeatureToggles(featureTogglesManager),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.blockchainsdk.di
|
||||
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object ExcludedBlockchainsModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun bindExcludedBlockchains(excludedBlockchainsManager: ExcludedBlockchainsManager): ExcludedBlockchains {
|
||||
return ExcludedBlockchains(excludedBlockchainsManager)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.blockchainsdk.featuretoggles
|
||||
|
||||
internal interface BlockchainSDKFeatureToggles {
|
||||
|
||||
val isEthereumEIP1559Enabled: Boolean
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.blockchainsdk.featuretoggles
|
||||
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
|
||||
internal class DefaultBlockchainSDKFeatureToggles(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : BlockchainSDKFeatureToggles {
|
||||
|
||||
override val isEthereumEIP1559Enabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled(name = "IS_ETHEREUM_EIP_1559_ENABLED")
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.blockchainsdk.providers
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.network.providers.ProviderType
|
||||
import com.tangem.datasource.local.datastore.RuntimeStateStore
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
internal typealias BlockchainProviderTypes = Map<Blockchain, List<ProviderType>>
|
||||
|
||||
/**
|
||||
* Blockchain provider types store
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Singleton
|
||||
class BlockchainProviderTypesStore @Inject constructor() :
|
||||
RuntimeStateStore<BlockchainProviderTypes> by RuntimeStateStore(emptyMap())
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
package com.tangem.blockchainsdk.providers
|
||||
|
||||
import com.tangem.blockchainsdk.BlockchainProvidersResponse
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.local.config.providers.BlockchainProvidersStorage
|
||||
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 tangemTechApi tangem tech api
|
||||
* @property blockchainProvidersStorage blockchain providers storage
|
||||
* @property dispatchers dispatchers
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Singleton
|
||||
internal class BlockchainProvidersResponseLoader @Inject constructor(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val blockchainProvidersStorage: BlockchainProvidersStorage,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
/** Load [BlockchainProvidersResponse] */
|
||||
suspend fun load(): BlockchainProvidersResponse? {
|
||||
val localResponse = loadLocal().ifEmpty { return null }
|
||||
|
||||
return loadRemote().fold(
|
||||
onSuccess = { remoteResponse ->
|
||||
BlockchainProvidersResponseMerger.merge(
|
||||
local = localResponse,
|
||||
remote = remoteResponse,
|
||||
)
|
||||
},
|
||||
onFailure = {
|
||||
Timber.e(it, "Failed to load blockchain provider types from backend")
|
||||
localResponse
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun loadLocal(): BlockchainProvidersResponse = blockchainProvidersStorage.getConfigSync()
|
||||
|
||||
private suspend fun loadRemote() = runCatching(
|
||||
dispatcher = dispatchers.io,
|
||||
block = tangemTechApi::getBlockchainProviders,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
package com.tangem.blockchainsdk.providers
|
||||
|
||||
import androidx.core.util.PatternsCompat
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.tangem.blockchainsdk.BlockchainProvidersResponse
|
||||
import com.tangem.datasource.local.config.providers.models.ProviderModel
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Merger of [BlockchainProvidersResponse]
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal object BlockchainProvidersResponseMerger {
|
||||
|
||||
private val firebaseCrashlytics by lazy(FirebaseCrashlytics::getInstance)
|
||||
|
||||
private val forbiddenSchemes = listOf("wss://")
|
||||
|
||||
/**
|
||||
* Merge blockchains with non-empty providers from [remote] with blockchains from [local]
|
||||
*
|
||||
* Example:
|
||||
* val remote = mapOf("a" to 1, "b" to 2, "c" to 3)
|
||||
* val local = mapOf("a" to 11, "e" to 4, "f" to 5)
|
||||
*
|
||||
* local + remote // { a = 1, e = 4, f = 5, b = 2, c = 3 }
|
||||
*/
|
||||
fun merge(local: BlockchainProvidersResponse, remote: BlockchainProvidersResponse): BlockchainProvidersResponse {
|
||||
val remoteWithoutInvalidProviders = remote
|
||||
.mapValues {
|
||||
it.value
|
||||
.filterUnsupportedProviders()
|
||||
.filterInvalidProviders()
|
||||
}
|
||||
.filterValues { it.isNotEmpty() }
|
||||
|
||||
val result = local + remoteWithoutInvalidProviders
|
||||
|
||||
if (result != remote) {
|
||||
val missingBlockchains = result.keys - remote.keys
|
||||
val blockchainsWithoutProviders = remote.filterValues { it.isEmpty() }.keys
|
||||
|
||||
recordException(missingBlockchains = missingBlockchains + blockchainsWithoutProviders)
|
||||
}
|
||||
|
||||
return result.guaranteeUrlsEndWithSlash()
|
||||
}
|
||||
|
||||
private fun List<ProviderModel>.filterUnsupportedProviders() = filter { model ->
|
||||
val isSupportedType = model !is ProviderModel.UnsupportedType
|
||||
|
||||
val isSupportedPrivateType = if (model is ProviderModel.Private) {
|
||||
ProviderTypeIdMapping.entries.any { it.id == model.name }
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
isSupportedType && isSupportedPrivateType
|
||||
}
|
||||
|
||||
private fun List<ProviderModel>.filterInvalidProviders() = mapNotNull { provider ->
|
||||
if (provider is ProviderModel.Public) {
|
||||
if (isValidUrl(provider.url)) provider else null
|
||||
} else {
|
||||
provider
|
||||
}
|
||||
}
|
||||
|
||||
private fun isValidUrl(url: String): Boolean {
|
||||
val forbiddenScheme = forbiddenSchemes.firstOrNull { url.startsWith(prefix = it) }
|
||||
val inputUrl = if (forbiddenScheme != null) url.substringAfter(forbiddenScheme) else url
|
||||
|
||||
return PatternsCompat.WEB_URL.matcher(inputUrl).matches()
|
||||
}
|
||||
|
||||
private fun recordException(missingBlockchains: Set<String>) {
|
||||
val exception = IllegalStateException(
|
||||
"Remote config does not contain required blockchains or providers information: " +
|
||||
missingBlockchains.joinToString(),
|
||||
)
|
||||
|
||||
Timber.e(exception)
|
||||
|
||||
firebaseCrashlytics.recordException(exception)
|
||||
}
|
||||
|
||||
/*
|
||||
* Example:
|
||||
* https://qwe.com --> https://qwe.com/
|
||||
*/
|
||||
private fun BlockchainProvidersResponse.guaranteeUrlsEndWithSlash(): BlockchainProvidersResponse {
|
||||
return mapValues {
|
||||
it.value.map { provider -> provider.addSlashIfAbsent() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun ProviderModel.addSlashIfAbsent(): ProviderModel {
|
||||
return if (this is ProviderModel.Public && url.last() != '/') {
|
||||
copy(url = "$url/")
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.blockchainsdk.providers
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/** Blockchain providers types manager */
|
||||
interface BlockchainProvidersTypesManager {
|
||||
|
||||
fun get(): Flow<BlockchainProviderTypes>
|
||||
|
||||
suspend fun update()
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package com.tangem.blockchainsdk.providers
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.network.providers.ProviderType
|
||||
import com.tangem.blockchainsdk.BlockchainProvidersResponse
|
||||
import com.tangem.blockchainsdk.converters.BlockchainProviderTypesConverter
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.map
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Implementation of [BlockchainProvidersTypesManager] in DEV environment
|
||||
*
|
||||
* @property prodBlockchainProvidersTypesManager prod manager
|
||||
* @property blockchainProviderTypesStore blockchain provider types store
|
||||
* @property changedBlockchainProvidersStore changed blockchain providers store
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DevBlockchainProvidersTypesManager(
|
||||
private val prodBlockchainProvidersTypesManager: ProdBlockchainProvidersTypesManager,
|
||||
private val blockchainProviderTypesStore: BlockchainProviderTypesStore,
|
||||
private val changedBlockchainProvidersStore: DataStore<BlockchainProvidersResponse>,
|
||||
) : MutableBlockchainProvidersTypesManager {
|
||||
|
||||
override fun get(): Flow<BlockchainProviderTypes> = changedBlockchainProvidersStore.data
|
||||
.map(BlockchainProviderTypesConverter::convert)
|
||||
|
||||
override suspend fun update() {
|
||||
prodBlockchainProvidersTypesManager.update()
|
||||
|
||||
val initial = blockchainProviderTypesStore.get().value
|
||||
|
||||
val changed = changedBlockchainProvidersStore.data.firstOrNull().orEmpty()
|
||||
|
||||
if (changed.isEmpty()) {
|
||||
Timber.i("Initialize ChangedBlockchainProvidersStore")
|
||||
changedBlockchainProvidersStore.updateData {
|
||||
BlockchainProviderTypesConverter.convertBack(initial)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun recoverInitialState() {
|
||||
val initial = blockchainProviderTypesStore.get().value
|
||||
|
||||
changedBlockchainProvidersStore.updateData {
|
||||
BlockchainProviderTypesConverter.convertBack(initial)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun update(blockchain: Blockchain, providers: List<ProviderType>) {
|
||||
changedBlockchainProvidersStore.updateData {
|
||||
val providerModels = BlockchainProviderTypesConverter.convertBack(value = mapOf(blockchain to providers))
|
||||
|
||||
it + providerModels
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun isMatchWithMerged(): Boolean {
|
||||
val initial = blockchainProviderTypesStore.get().value
|
||||
val changed = changedBlockchainProvidersStore.data.firstOrNull().orEmpty()
|
||||
|
||||
return initial == BlockchainProviderTypesConverter.convert(changed)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.blockchainsdk.providers
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.network.providers.ProviderType
|
||||
|
||||
/**
|
||||
* Mutable blockchain providers types manager
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
interface MutableBlockchainProvidersTypesManager : BlockchainProvidersTypesManager {
|
||||
|
||||
/** Update [providers] for [blockchain] */
|
||||
suspend fun update(blockchain: Blockchain, providers: List<ProviderType>)
|
||||
|
||||
/** Recover initial state */
|
||||
suspend fun recoverInitialState()
|
||||
|
||||
/** Checks that current [BlockchainProviderTypes] was changed */
|
||||
suspend fun isMatchWithMerged(): Boolean
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.blockchainsdk.providers
|
||||
|
||||
import com.tangem.blockchainsdk.converters.BlockchainProviderTypesConverter
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* Implementation of [BlockchainProvidersTypesManager] in PROD environment
|
||||
*
|
||||
* @property blockchainProvidersResponseLoader blockchain providers response loader
|
||||
* @property blockchainProviderTypesStore blockchain provider types store
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Singleton
|
||||
internal class ProdBlockchainProvidersTypesManager @Inject constructor(
|
||||
private val blockchainProvidersResponseLoader: BlockchainProvidersResponseLoader,
|
||||
private val blockchainProviderTypesStore: BlockchainProviderTypesStore,
|
||||
) : BlockchainProvidersTypesManager {
|
||||
|
||||
override fun get(): Flow<BlockchainProviderTypes> = blockchainProviderTypesStore.get()
|
||||
|
||||
override suspend fun update() {
|
||||
val response = blockchainProvidersResponseLoader.load()
|
||||
|
||||
if (response == null) {
|
||||
Timber.e("Error loading BlockchainProviderTypes")
|
||||
return
|
||||
}
|
||||
|
||||
Timber.i("Update BlockchainProviderTypes")
|
||||
|
||||
blockchainProviderTypesStore.store(
|
||||
value = BlockchainProviderTypesConverter.convert(response),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.tangem.blockchainsdk.providers
|
||||
|
||||
import com.tangem.blockchain.common.network.providers.ProviderType
|
||||
|
||||
/** Mapping of [providerType] with [id] */
|
||||
internal enum class ProviderTypeIdMapping(val id: String, val providerType: ProviderType) {
|
||||
NowNodes(id = "nownodes", providerType = ProviderType.NowNodes),
|
||||
GetBlock(id = "getblock", providerType = ProviderType.GetBlock),
|
||||
QuickNode(id = "quicknode", providerType = ProviderType.QuickNode),
|
||||
BitcoinBlockchair(id = "blockchair", providerType = ProviderType.BitcoinLike.Blockchair),
|
||||
BitcoinBlockcypher(id = "blockcypher", providerType = ProviderType.BitcoinLike.Blockcypher),
|
||||
CardanoAdalite(id = "adalite", providerType = ProviderType.Cardano.Adalite),
|
||||
CardanoRosetta(id = "tangemRosetta", providerType = ProviderType.Cardano.Rosetta),
|
||||
ChiaFireAcademy(id = "fireAcademy", providerType = ProviderType.Chia.FireAcademy),
|
||||
ChiaTangem(id = "tangemChia", providerType = ProviderType.Chia.Tangem),
|
||||
ChiaTangemNew(id = "tangemChia3", providerType = ProviderType.Chia.TangemNew),
|
||||
EthereumInfura(id = "infura", providerType = ProviderType.EthereumLike.Infura),
|
||||
HederaArkhia(id = "arkhiaHedera", providerType = ProviderType.Hedera.Arkhia),
|
||||
KaspaSecondary(id = "kaspa", providerType = ProviderType.Kaspa.SecondaryAPI),
|
||||
SolanaOfficial(id = "solana", providerType = ProviderType.Solana.Official),
|
||||
TonCentral(id = "ton", providerType = ProviderType.Ton.TonCentral),
|
||||
TronGrid(id = "tron", providerType = ProviderType.Tron.TronGrid),
|
||||
BittensorDwellir(id = "dwellirBittensor", providerType = ProviderType.Bittensor.Dwellir),
|
||||
BittensorOnfinality(id = "onfinalityBittensor", providerType = ProviderType.Bittensor.Onfinality),
|
||||
KoinosPro(id = "koinospro", providerType = ProviderType.Koinos.KoinosPro),
|
||||
AlephiumTangem(id = "tangemAlephium", providerType = ProviderType.Alephium.Tangem),
|
||||
;
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.blockchainsdk.providers.dev
|
||||
|
||||
import androidx.datastore.core.Serializer
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.Types
|
||||
import com.tangem.blockchainsdk.BlockchainProvidersResponse
|
||||
import com.tangem.datasource.local.config.providers.models.ProviderModel
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
|
||||
/** DataStore serializer for [BlockchainProvidersResponse]. Implemented by [Moshi]. */
|
||||
internal class BlockchainProvidersResponseSerializer(moshi: Moshi) : Serializer<BlockchainProvidersResponse> {
|
||||
|
||||
private val adapter by lazy {
|
||||
val providersType = Types.newParameterizedType(List::class.java, ProviderModel::class.java)
|
||||
val type = Types.newParameterizedType(Map::class.java, String::class.java, providersType)
|
||||
|
||||
moshi.adapter<BlockchainProvidersResponse>(type)
|
||||
}
|
||||
|
||||
override val defaultValue: BlockchainProvidersResponse = emptyMap()
|
||||
|
||||
override suspend fun readFrom(input: InputStream): BlockchainProvidersResponse {
|
||||
return input.bufferedReader().use { reader ->
|
||||
adapter.fromJson(reader.readText()) ?: defaultValue
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun writeTo(t: BlockchainProvidersResponse, output: OutputStream) {
|
||||
output.bufferedWriter().use { writer ->
|
||||
writer.write(adapter.toJson(t))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,464 @@
|
|||
package com.tangem.blockchainsdk.utils
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.ReserveAmountProvider
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Suppress("ComplexMethod", "LongMethod")
|
||||
fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
|
||||
return when (networkId) {
|
||||
"alephium" -> Blockchain.Alephium
|
||||
"alephium/test" -> Blockchain.AlephiumTestnet
|
||||
"arbitrum-one" -> Blockchain.Arbitrum
|
||||
"arbitrum-one/test" -> Blockchain.ArbitrumTestnet
|
||||
"avalanche", "avalanche-2" -> Blockchain.Avalanche
|
||||
"avalanche/test", "avalanche-2/test" -> Blockchain.AvalancheTestnet
|
||||
"binancecoin" -> Blockchain.Binance
|
||||
"binancecoin/test" -> Blockchain.BinanceTestnet
|
||||
"binance-smart-chain" -> Blockchain.BSC
|
||||
"binance-smart-chain/test" -> Blockchain.BSCTestnet
|
||||
"ethereum" -> Blockchain.Ethereum
|
||||
"ethereum/test" -> Blockchain.EthereumTestnet
|
||||
"ethereum-classic" -> Blockchain.EthereumClassic
|
||||
"ethereum-classic/test" -> Blockchain.EthereumClassicTestnet
|
||||
"polygon-pos", "matic-network" -> Blockchain.Polygon
|
||||
"polygon-pos/test", "matic-network/test" -> Blockchain.PolygonTestnet
|
||||
"solana" -> Blockchain.Solana
|
||||
"solana/test" -> Blockchain.SolanaTestnet
|
||||
"fantom" -> Blockchain.Fantom
|
||||
"fantom/test" -> Blockchain.FantomTestnet
|
||||
"bitcoin" -> Blockchain.Bitcoin
|
||||
"bitcoin/test" -> Blockchain.BitcoinTestnet
|
||||
"bitcoin-cash" -> Blockchain.BitcoinCash
|
||||
"bitcoin-cash/test" -> Blockchain.BitcoinCashTestnet
|
||||
"cardano" -> Blockchain.Cardano
|
||||
"dogecoin" -> Blockchain.Dogecoin
|
||||
"ducatus" -> Blockchain.Ducatus
|
||||
"litecoin" -> Blockchain.Litecoin
|
||||
"rootstock" -> Blockchain.RSK
|
||||
"stellar" -> Blockchain.Stellar
|
||||
"stellar/test" -> Blockchain.StellarTestnet
|
||||
"tezos" -> Blockchain.Tezos
|
||||
"tron" -> Blockchain.Tron
|
||||
"tron/test" -> Blockchain.TronTestnet
|
||||
"xrp", "ripple" -> Blockchain.XRP
|
||||
"xdai" -> Blockchain.Gnosis
|
||||
"ethereum-pow-iou" -> Blockchain.EthereumPow
|
||||
"ethereum-pow-iou/test" -> Blockchain.EthereumPowTestnet
|
||||
"ethereumfair", "dischain" -> Blockchain.Dischain // for old client compatibility
|
||||
"polkadot" -> Blockchain.Polkadot
|
||||
"polkadot/test" -> Blockchain.PolkadotTestnet
|
||||
"kusama" -> Blockchain.Kusama
|
||||
"optimistic-ethereum" -> Blockchain.Optimism
|
||||
"optimistic-ethereum/test" -> Blockchain.OptimismTestnet
|
||||
"dash" -> Blockchain.Dash
|
||||
"kaspa" -> Blockchain.Kaspa
|
||||
"the-open-network" -> Blockchain.TON
|
||||
"the-open-network/test" -> Blockchain.TONTestnet
|
||||
"kava" -> Blockchain.Kava
|
||||
"kava/test" -> Blockchain.KavaTestnet
|
||||
"ravencoin" -> Blockchain.Ravencoin
|
||||
"ravencoin/test" -> Blockchain.RavencoinTestnet
|
||||
"cosmos" -> Blockchain.Cosmos
|
||||
"cosmos/test" -> Blockchain.CosmosTestnet
|
||||
"terra" -> Blockchain.TerraV1
|
||||
"terra-2" -> Blockchain.TerraV2
|
||||
"cronos" -> Blockchain.Cronos
|
||||
"telos" -> Blockchain.Telos
|
||||
"telos/test" -> Blockchain.TelosTestnet
|
||||
"aleph-zero" -> Blockchain.AlephZero
|
||||
"aleph-zero/test" -> Blockchain.AlephZeroTestnet
|
||||
"octaspace" -> Blockchain.OctaSpace
|
||||
"octaspace/test" -> Blockchain.OctaSpaceTestnet
|
||||
"chia" -> Blockchain.Chia
|
||||
"chia/test" -> Blockchain.ChiaTestnet
|
||||
"near-protocol" -> Blockchain.Near
|
||||
"near-protocol/test" -> Blockchain.NearTestnet
|
||||
"decimal" -> Blockchain.Decimal
|
||||
"decimal/test" -> Blockchain.DecimalTestnet
|
||||
"xdc-network" -> Blockchain.XDC
|
||||
"xdc-network/test" -> Blockchain.XDCTestnet
|
||||
"vechain" -> Blockchain.VeChain
|
||||
"vechain/test" -> Blockchain.VeChainTestnet
|
||||
"aptos" -> Blockchain.Aptos
|
||||
"aptos/test" -> Blockchain.AptosTestnet
|
||||
"playa3ull-games" -> Blockchain.Playa3ull
|
||||
"shibarium" -> Blockchain.Shibarium
|
||||
"shibarium/test" -> Blockchain.ShibariumTestnet
|
||||
"algorand" -> Blockchain.Algorand
|
||||
"algorand/test" -> Blockchain.AlgorandTestnet
|
||||
"hedera-hashgraph" -> Blockchain.Hedera
|
||||
"hedera-hashgraph/test" -> Blockchain.HederaTestnet
|
||||
"aurora" -> Blockchain.Aurora
|
||||
"aurora/test" -> Blockchain.AuroraTestnet
|
||||
"areon-network" -> Blockchain.Areon
|
||||
"areon-network/test" -> Blockchain.AreonTestnet
|
||||
"pulsechain" -> Blockchain.PulseChain
|
||||
"pulsechain/test" -> Blockchain.PulseChainTestnet
|
||||
"zksync" -> Blockchain.ZkSyncEra
|
||||
"zksync/test" -> Blockchain.ZkSyncEraTestnet
|
||||
"moonbeam" -> Blockchain.Moonbeam
|
||||
"moonbeam/test" -> Blockchain.MoonbeamTestnet
|
||||
"manta-pacific" -> Blockchain.Manta
|
||||
"manta-pacific/test" -> Blockchain.MantaTestnet
|
||||
"polygon-zkevm" -> Blockchain.PolygonZkEVM
|
||||
"polygon-zkevm/test" -> Blockchain.PolygonZkEVMTestnet
|
||||
"nexa" -> Blockchain.Nexa // FIXME
|
||||
"nexa/test" -> Blockchain.NexaTestnet // FIXME
|
||||
"radiant" -> Blockchain.Radiant
|
||||
"fact0rn" -> Blockchain.Fact0rn
|
||||
"moonriver" -> Blockchain.Moonriver
|
||||
"moonriver/test" -> Blockchain.MoonriverTestnet
|
||||
"mantle" -> Blockchain.Mantle
|
||||
"mantle/test" -> Blockchain.MantleTestnet
|
||||
"flare-network" -> Blockchain.Flare
|
||||
"flare-network/test" -> Blockchain.FlareTestnet
|
||||
"taraxa" -> Blockchain.Taraxa
|
||||
"taraxa/test" -> Blockchain.TaraxaTestnet
|
||||
"base" -> Blockchain.Base
|
||||
"base/test" -> Blockchain.BaseTestnet
|
||||
"koinos" -> Blockchain.Koinos
|
||||
"koinos/test" -> Blockchain.KoinosTestnet
|
||||
"joystream" -> Blockchain.Joystream
|
||||
"bittensor" -> Blockchain.Bittensor
|
||||
"filecoin" -> Blockchain.Filecoin
|
||||
"blast" -> Blockchain.Blast
|
||||
"blast/test" -> Blockchain.BlastTestnet
|
||||
"cyber" -> Blockchain.Cyber
|
||||
"cyber/test" -> Blockchain.CyberTestnet
|
||||
"sei-network" -> Blockchain.Sei
|
||||
"sei-network/test" -> Blockchain.SeiTestnet
|
||||
"internet-computer" -> Blockchain.InternetComputer
|
||||
"sui" -> Blockchain.Sui
|
||||
"sui/test" -> Blockchain.SuiTestnet
|
||||
"energy-web-chain" -> Blockchain.EnergyWebChain
|
||||
"energy-web-chain/test" -> Blockchain.EnergyWebChainTestnet
|
||||
"energy-web-x" -> Blockchain.EnergyWebX
|
||||
"energy-web-x/test" -> Blockchain.EnergyWebXTestnet
|
||||
"add_later" -> Blockchain.Casper
|
||||
"add_later_test" -> Blockchain.CasperTestnet
|
||||
"core" -> Blockchain.Core
|
||||
"core/test" -> Blockchain.CoreTestnet
|
||||
"casper-network" -> Blockchain.Casper
|
||||
"casper-network/test" -> Blockchain.CasperTestnet
|
||||
"chiliz" -> Blockchain.Chiliz
|
||||
"chiliz/test" -> Blockchain.ChilizTestnet
|
||||
"vanar-chain" -> Blockchain.VanarChain
|
||||
"vanar-chain/test" -> Blockchain.VanarChainTestnet
|
||||
"xodex" -> Blockchain.Xodex
|
||||
"canxium" -> Blockchain.Canxium
|
||||
"clore-ai" -> Blockchain.Clore
|
||||
"dione" -> Blockchain.OdysseyChain
|
||||
"dione/test" -> Blockchain.OdysseyChainTestnet
|
||||
"bitrock" -> Blockchain.Bitrock
|
||||
"bitrock/test" -> Blockchain.BitrockTestnet
|
||||
"sonic" -> Blockchain.Sonic
|
||||
"sonic/test" -> Blockchain.SonicTestnet
|
||||
"apechain" -> Blockchain.ApeChain
|
||||
"apechain/test" -> Blockchain.ApeChainTestnet
|
||||
"kaspa/test" -> Blockchain.KaspaTestnet
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod", "LongMethod")
|
||||
fun Blockchain.toNetworkId(): String {
|
||||
return when (this) {
|
||||
Blockchain.Unknown -> "unknown"
|
||||
Blockchain.Alephium -> "alephium"
|
||||
Blockchain.AlephiumTestnet -> "alephium/test"
|
||||
Blockchain.Arbitrum -> "arbitrum-one"
|
||||
Blockchain.ArbitrumTestnet -> "arbitrum-one/test"
|
||||
Blockchain.Avalanche -> "avalanche"
|
||||
Blockchain.AvalancheTestnet -> "avalanche/test"
|
||||
Blockchain.Binance -> "binancecoin"
|
||||
Blockchain.BinanceTestnet -> "binancecoin/test"
|
||||
Blockchain.BSC -> "binance-smart-chain"
|
||||
Blockchain.BSCTestnet -> "binance-smart-chain/test"
|
||||
Blockchain.Bitcoin -> "bitcoin"
|
||||
Blockchain.BitcoinTestnet -> "bitcoin/test"
|
||||
Blockchain.BitcoinCash -> "bitcoin-cash"
|
||||
Blockchain.BitcoinCashTestnet -> "bitcoin-cash/test"
|
||||
Blockchain.Cardano -> "cardano"
|
||||
Blockchain.Dogecoin -> "dogecoin"
|
||||
Blockchain.Ducatus -> "ducatus"
|
||||
Blockchain.Ethereum -> "ethereum"
|
||||
Blockchain.EthereumTestnet -> "ethereum/test"
|
||||
Blockchain.EthereumClassic -> "ethereum-classic"
|
||||
Blockchain.EthereumClassicTestnet -> "ethereum-classic/test"
|
||||
Blockchain.Fantom -> "fantom"
|
||||
Blockchain.FantomTestnet -> "fantom/test"
|
||||
Blockchain.Litecoin -> "litecoin"
|
||||
Blockchain.Polygon -> "polygon-pos"
|
||||
Blockchain.PolygonTestnet -> "polygon-pos/test"
|
||||
Blockchain.RSK -> "rootstock"
|
||||
Blockchain.Stellar -> "stellar"
|
||||
Blockchain.StellarTestnet -> "stellar/test"
|
||||
Blockchain.Solana -> "solana"
|
||||
Blockchain.SolanaTestnet -> "solana/test"
|
||||
Blockchain.Tezos -> "tezos"
|
||||
Blockchain.XRP -> "xrp"
|
||||
Blockchain.Tron -> "tron"
|
||||
Blockchain.TronTestnet -> "tron/test"
|
||||
Blockchain.Gnosis -> "xdai"
|
||||
Blockchain.EthereumPow -> "ethereum-pow-iou"
|
||||
Blockchain.EthereumPowTestnet -> "ethereum-pow-iou/test"
|
||||
Blockchain.Dischain -> "ethereumfair" // for backend compatibility
|
||||
Blockchain.Polkadot -> "polkadot"
|
||||
Blockchain.PolkadotTestnet -> "polkadot/test"
|
||||
Blockchain.Kusama -> "kusama"
|
||||
Blockchain.Optimism -> "optimistic-ethereum"
|
||||
Blockchain.OptimismTestnet -> "optimistic-ethereum/test"
|
||||
Blockchain.Dash -> "dash"
|
||||
Blockchain.Kaspa -> "kaspa"
|
||||
Blockchain.KaspaTestnet -> "kaspa/test"
|
||||
Blockchain.TON -> "the-open-network"
|
||||
Blockchain.TONTestnet -> "the-open-network/test"
|
||||
Blockchain.Kava -> "kava"
|
||||
Blockchain.KavaTestnet -> "kava/test"
|
||||
Blockchain.Ravencoin -> "ravencoin"
|
||||
Blockchain.RavencoinTestnet -> "ravencoin/test"
|
||||
Blockchain.Cosmos -> "cosmos"
|
||||
Blockchain.CosmosTestnet -> "cosmos/test"
|
||||
Blockchain.TerraV1 -> "terra"
|
||||
Blockchain.TerraV2 -> "terra-2"
|
||||
Blockchain.Cronos -> "cronos"
|
||||
Blockchain.Telos -> "telos"
|
||||
Blockchain.TelosTestnet -> "telos/test"
|
||||
Blockchain.AlephZero -> "aleph-zero"
|
||||
Blockchain.AlephZeroTestnet -> "aleph-zero/test"
|
||||
Blockchain.OctaSpace -> "octaspace"
|
||||
Blockchain.OctaSpaceTestnet -> "octaspace/test"
|
||||
Blockchain.Chia -> "chia"
|
||||
Blockchain.ChiaTestnet -> "chia/test"
|
||||
Blockchain.Near -> "near-protocol"
|
||||
Blockchain.NearTestnet -> "near-protocol/test"
|
||||
Blockchain.Decimal -> "decimal"
|
||||
Blockchain.DecimalTestnet -> "decimal/test"
|
||||
Blockchain.XDC -> "xdc-network"
|
||||
Blockchain.XDCTestnet -> "xdc-network/test"
|
||||
Blockchain.VeChain -> "vechain"
|
||||
Blockchain.VeChainTestnet -> "vechain/test"
|
||||
Blockchain.Aptos -> "aptos"
|
||||
Blockchain.AptosTestnet -> "aptos/test"
|
||||
Blockchain.Playa3ull -> "playa3ull-games"
|
||||
Blockchain.Shibarium -> "shibarium"
|
||||
Blockchain.ShibariumTestnet -> "shibarium/test"
|
||||
Blockchain.Algorand -> "algorand"
|
||||
Blockchain.AlgorandTestnet -> "algorand/test"
|
||||
Blockchain.Hedera -> "hedera-hashgraph"
|
||||
Blockchain.HederaTestnet -> "hedera-hashgraph/test"
|
||||
Blockchain.Aurora -> "aurora"
|
||||
Blockchain.AuroraTestnet -> "aurora/test"
|
||||
Blockchain.Areon -> "areon-network"
|
||||
Blockchain.AreonTestnet -> "areon-network/test"
|
||||
Blockchain.PulseChain -> "pulsechain"
|
||||
Blockchain.PulseChainTestnet -> "pulsechain/test"
|
||||
Blockchain.ZkSyncEra -> "zksync"
|
||||
Blockchain.ZkSyncEraTestnet -> "zksync/test"
|
||||
Blockchain.Moonbeam -> "moonbeam"
|
||||
Blockchain.MoonbeamTestnet -> "moonbeam/test"
|
||||
Blockchain.Manta -> "manta-pacific"
|
||||
Blockchain.MantaTestnet -> "manta-pacific/test"
|
||||
Blockchain.PolygonZkEVM -> "polygon-zkevm"
|
||||
Blockchain.PolygonZkEVMTestnet -> "polygon-zkevm/test"
|
||||
Blockchain.Nexa -> "nexa" // FIXME
|
||||
Blockchain.NexaTestnet -> "nexa/test" // FIXME
|
||||
Blockchain.Radiant -> "radiant"
|
||||
Blockchain.Fact0rn -> "fact0rn"
|
||||
Blockchain.Moonriver -> "moonriver"
|
||||
Blockchain.MoonriverTestnet -> "moonriver/test"
|
||||
Blockchain.Mantle -> "mantle"
|
||||
Blockchain.MantleTestnet -> "mantle/test"
|
||||
Blockchain.Flare -> "flare-network"
|
||||
Blockchain.FlareTestnet -> "flare-network/test"
|
||||
Blockchain.Taraxa -> "taraxa"
|
||||
Blockchain.TaraxaTestnet -> "taraxa/test"
|
||||
Blockchain.Base -> "base"
|
||||
Blockchain.BaseTestnet -> "base/test"
|
||||
Blockchain.Koinos -> "koinos"
|
||||
Blockchain.KoinosTestnet -> "koinos/test"
|
||||
Blockchain.Joystream -> "joystream"
|
||||
Blockchain.Bittensor -> "bittensor"
|
||||
Blockchain.Filecoin -> "filecoin"
|
||||
Blockchain.Blast -> "blast"
|
||||
Blockchain.BlastTestnet -> "blast/test"
|
||||
Blockchain.Cyber -> "cyber"
|
||||
Blockchain.CyberTestnet -> "cyber/test"
|
||||
Blockchain.Sei -> "sei-network"
|
||||
Blockchain.SeiTestnet -> "sei-network/test"
|
||||
Blockchain.InternetComputer -> "internet-computer"
|
||||
Blockchain.Sui -> "sui"
|
||||
Blockchain.SuiTestnet -> "sui/test"
|
||||
Blockchain.EnergyWebChain -> "energy-web-chain"
|
||||
Blockchain.EnergyWebChainTestnet -> "energy-web-chain/test"
|
||||
Blockchain.EnergyWebX -> "energy-web-x"
|
||||
Blockchain.EnergyWebXTestnet -> "energy-web-x/test"
|
||||
Blockchain.Casper -> "casper-network"
|
||||
Blockchain.CasperTestnet -> "casper-network/test"
|
||||
Blockchain.Core -> "core"
|
||||
Blockchain.CoreTestnet -> "core/test"
|
||||
Blockchain.Chiliz -> "chiliz"
|
||||
Blockchain.ChilizTestnet -> "chiliz/test"
|
||||
Blockchain.VanarChain -> "vanar-chain"
|
||||
Blockchain.VanarChainTestnet -> "vanar-chain/test"
|
||||
Blockchain.Xodex -> "xodex"
|
||||
Blockchain.Canxium -> "canxium"
|
||||
Blockchain.Clore -> "clore-ai"
|
||||
Blockchain.OdysseyChain -> "dione"
|
||||
Blockchain.OdysseyChainTestnet -> "dione/test"
|
||||
Blockchain.Bitrock -> "bitrock"
|
||||
Blockchain.BitrockTestnet -> "bitrock/test"
|
||||
Blockchain.Sonic -> "sonic"
|
||||
Blockchain.SonicTestnet -> "sonic/test"
|
||||
Blockchain.ApeChain -> "apechain"
|
||||
Blockchain.ApeChainTestnet -> "apechain/test"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* CoinId is id from tangem backend response coin "id" field
|
||||
*/
|
||||
@Suppress("ComplexMethod", "LongMethod")
|
||||
fun Blockchain.toCoinId(): String {
|
||||
return when (this) {
|
||||
Blockchain.Binance, Blockchain.BinanceTestnet, Blockchain.BSC, Blockchain.BSCTestnet -> "binancecoin"
|
||||
Blockchain.Bitcoin, Blockchain.BitcoinTestnet -> "bitcoin"
|
||||
Blockchain.BitcoinCash, Blockchain.BitcoinCashTestnet -> "bitcoin-cash"
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestnet -> "ethereum"
|
||||
Blockchain.EthereumClassic, Blockchain.EthereumClassicTestnet -> "ethereum-classic"
|
||||
Blockchain.Stellar, Blockchain.StellarTestnet -> "stellar"
|
||||
Blockchain.Cardano -> "cardano"
|
||||
Blockchain.Polygon, Blockchain.PolygonTestnet -> NEW_POLYGON_NAME
|
||||
Blockchain.Alephium, Blockchain.AlephiumTestnet -> "alephium"
|
||||
Blockchain.Arbitrum, Blockchain.ArbitrumTestnet -> "arbitrum-one"
|
||||
Blockchain.Avalanche, Blockchain.AvalancheTestnet -> "avalanche-2"
|
||||
Blockchain.Solana, Blockchain.SolanaTestnet -> "solana"
|
||||
Blockchain.Fantom, Blockchain.FantomTestnet -> "fantom"
|
||||
Blockchain.Tron, Blockchain.TronTestnet -> "tron"
|
||||
Blockchain.Polkadot, Blockchain.PolkadotTestnet -> "polkadot"
|
||||
Blockchain.Ducatus -> "ducatus"
|
||||
Blockchain.Litecoin -> "litecoin"
|
||||
Blockchain.RSK -> "rootstock"
|
||||
Blockchain.Tezos -> "tezos"
|
||||
Blockchain.XRP -> "ripple"
|
||||
Blockchain.Dogecoin -> "dogecoin"
|
||||
Blockchain.Gnosis -> "xdai"
|
||||
Blockchain.EthereumPow, Blockchain.EthereumPowTestnet -> "ethereum-pow-iou"
|
||||
Blockchain.Dischain -> "ethereumfair" // for backend compatibility
|
||||
Blockchain.Kusama -> "kusama"
|
||||
Blockchain.Optimism, Blockchain.OptimismTestnet -> "optimistic-ethereum"
|
||||
Blockchain.Dash -> "dash"
|
||||
Blockchain.Kaspa, Blockchain.KaspaTestnet -> "kaspa"
|
||||
Blockchain.TON, Blockchain.TONTestnet -> "the-open-network"
|
||||
Blockchain.Kava, Blockchain.KavaTestnet -> "kava"
|
||||
Blockchain.Ravencoin, Blockchain.RavencoinTestnet -> "ravencoin"
|
||||
Blockchain.Cosmos, Blockchain.CosmosTestnet -> "cosmos"
|
||||
Blockchain.TerraV1 -> "terra-luna"
|
||||
Blockchain.TerraV2 -> "terra-luna-2"
|
||||
Blockchain.Cronos -> "crypto-com-chain"
|
||||
Blockchain.Telos, Blockchain.TelosTestnet -> "telos"
|
||||
Blockchain.AlephZero, Blockchain.AlephZeroTestnet -> "aleph-zero"
|
||||
Blockchain.OctaSpace, Blockchain.OctaSpaceTestnet -> "octaspace"
|
||||
Blockchain.Chia, Blockchain.ChiaTestnet -> "chia"
|
||||
Blockchain.Near -> "near"
|
||||
Blockchain.NearTestnet -> "near/test"
|
||||
Blockchain.Decimal, Blockchain.DecimalTestnet -> "decimal"
|
||||
Blockchain.XDC, Blockchain.XDCTestnet -> "xdce-crowd-sale"
|
||||
Blockchain.VeChain, Blockchain.VeChainTestnet -> "vechain"
|
||||
Blockchain.Aptos -> "aptos"
|
||||
Blockchain.AptosTestnet -> "aptos/test"
|
||||
Blockchain.Playa3ull -> "playa3ull-games-2"
|
||||
Blockchain.Shibarium -> "bone-shibaswap"
|
||||
Blockchain.ShibariumTestnet -> "bone-shibaswap/test"
|
||||
Blockchain.Algorand -> "algorand"
|
||||
Blockchain.AlgorandTestnet -> "algorand/test"
|
||||
Blockchain.Unknown -> "unknown"
|
||||
Blockchain.Hedera -> "hedera-hashgraph"
|
||||
Blockchain.HederaTestnet -> "hedera-hashgraph/test"
|
||||
Blockchain.Aurora, Blockchain.AuroraTestnet -> "aurora-ethereum"
|
||||
Blockchain.Areon, Blockchain.AreonTestnet -> "areon-network"
|
||||
Blockchain.PulseChain, Blockchain.PulseChainTestnet -> "pulsechain"
|
||||
Blockchain.ZkSyncEra, Blockchain.ZkSyncEraTestnet -> "zksync-ethereum"
|
||||
Blockchain.Moonbeam, Blockchain.MoonbeamTestnet -> "moonbeam"
|
||||
Blockchain.Manta, Blockchain.MantaTestnet -> "manta-pacific"
|
||||
Blockchain.PolygonZkEVM, Blockchain.PolygonZkEVMTestnet -> "polygon-zkevm-ethereum"
|
||||
Blockchain.Nexa, Blockchain.NexaTestnet -> "nexa" // FIXME
|
||||
Blockchain.Radiant -> "radiant"
|
||||
Blockchain.Fact0rn -> "fact0rn"
|
||||
Blockchain.Moonriver, Blockchain.MoonriverTestnet -> "moonriver"
|
||||
Blockchain.Mantle, Blockchain.MantleTestnet -> "mantle"
|
||||
Blockchain.Flare, Blockchain.FlareTestnet -> "flare-networks"
|
||||
Blockchain.Taraxa, Blockchain.TaraxaTestnet -> "taraxa"
|
||||
Blockchain.Base, Blockchain.BaseTestnet -> "base-ethereum"
|
||||
Blockchain.Koinos, Blockchain.KoinosTestnet -> "koinos"
|
||||
Blockchain.Joystream -> "joystream"
|
||||
Blockchain.Bittensor -> "bittensor"
|
||||
Blockchain.Filecoin -> "filecoin"
|
||||
Blockchain.Blast, Blockchain.BlastTestnet -> "blast-ethereum"
|
||||
Blockchain.Cyber, Blockchain.CyberTestnet -> "cyber-ethereum"
|
||||
Blockchain.Sei, Blockchain.SeiTestnet -> "sei-network"
|
||||
Blockchain.InternetComputer -> "internet-computer"
|
||||
Blockchain.Sui, Blockchain.SuiTestnet -> "sui"
|
||||
Blockchain.EnergyWebChain, Blockchain.EnergyWebChainTestnet -> "energy-web-token"
|
||||
Blockchain.EnergyWebX, Blockchain.EnergyWebXTestnet -> "energy-web-token"
|
||||
Blockchain.Casper, Blockchain.CasperTestnet -> "casper-network"
|
||||
Blockchain.Core, Blockchain.CoreTestnet -> "coredaoorg"
|
||||
Blockchain.Chiliz, Blockchain.ChilizTestnet -> "chiliz"
|
||||
Blockchain.VanarChain, Blockchain.VanarChainTestnet -> "vanar-chain"
|
||||
Blockchain.Xodex -> "xodex"
|
||||
Blockchain.Canxium -> "canxium"
|
||||
Blockchain.Clore -> "clore-ai"
|
||||
Blockchain.OdysseyChain, Blockchain.OdysseyChainTestnet -> "dione"
|
||||
Blockchain.Bitrock, Blockchain.BitrockTestnet -> "bitrock"
|
||||
Blockchain.Sonic, Blockchain.SonicTestnet -> "sonic-3"
|
||||
Blockchain.ApeChain, Blockchain.ApeChainTestnet -> "apecoin"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* New CoinId to existing coin "id" field.
|
||||
* To support both old and new coin id.
|
||||
*/
|
||||
fun Blockchain.toMigratedCoinId(): String = when (this) {
|
||||
Blockchain.Polygon, Blockchain.PolygonTestnet -> NEW_POLYGON_NAME
|
||||
else -> toCoinId()
|
||||
}
|
||||
|
||||
fun Blockchain.amountToCreateAccount(walletManager: WalletManager, token: Token? = null): BigDecimal? {
|
||||
return when (this) {
|
||||
Blockchain.Stellar -> {
|
||||
val reserve = if (walletManager is ReserveAmountProvider) {
|
||||
walletManager.getReserveAmount()
|
||||
} else {
|
||||
BigDecimal.ONE
|
||||
}
|
||||
if (token?.symbol == NODL) BigDecimal(NODL_AMOUNT_TO_CREATE_ACCOUNT) else reserve
|
||||
}
|
||||
Blockchain.XRP -> {
|
||||
if (walletManager is ReserveAmountProvider) {
|
||||
walletManager.getReserveAmount()
|
||||
} else {
|
||||
BigDecimal.ONE
|
||||
}
|
||||
}
|
||||
Blockchain.Near, Blockchain.NearTestnet -> 0.00182.toBigDecimal()
|
||||
Blockchain.Aptos, Blockchain.AptosTestnet,
|
||||
Blockchain.Filecoin,
|
||||
Blockchain.Casper, Blockchain.CasperTestnet,
|
||||
-> BigDecimal.ZERO
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun Blockchain.minimalAmount(): BigDecimal {
|
||||
return BigDecimal.ONE.movePointLeft(decimals())
|
||||
}
|
||||
|
||||
const val OLD_POLYGON_NAME = "matic-network"
|
||||
const val NEW_POLYGON_NAME = "polygon-ecosystem-token"
|
||||
|
||||
private const val NODL = "NODL"
|
||||
private const val NODL_AMOUNT_TO_CREATE_ACCOUNT = 1.5
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.blockchainsdk.utils
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import javax.inject.Inject
|
||||
|
||||
class ExcludedBlockchains @Inject internal constructor(
|
||||
private val excludedBlockchainsManager: ExcludedBlockchainsManager,
|
||||
) : Set<Blockchain> {
|
||||
|
||||
private val excludedBlockchains: Set<Blockchain> by lazy(mode = LazyThreadSafetyMode.NONE) {
|
||||
excludedBlockchainsManager.excludedBlockchainsIds.fold(mutableSetOf()) { acc, blockchainId ->
|
||||
val blockchain = Blockchain.fromId(blockchainId)
|
||||
acc.add(blockchain)
|
||||
|
||||
blockchain.getTestnetVersion()?.let { acc.add(it) }
|
||||
|
||||
acc
|
||||
}
|
||||
}
|
||||
|
||||
override val size: Int
|
||||
get() = excludedBlockchains.size
|
||||
|
||||
@TestOnly
|
||||
constructor() : this(
|
||||
excludedBlockchainsManager = object : ExcludedBlockchainsManager {
|
||||
|
||||
override val excludedBlockchainsIds: Set<String> = emptySet()
|
||||
|
||||
override suspend fun init() {
|
||||
/* no-op */
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
override fun contains(element: Blockchain): Boolean = excludedBlockchains.contains(element)
|
||||
|
||||
override fun containsAll(elements: Collection<Blockchain>): Boolean = excludedBlockchains.containsAll(elements)
|
||||
|
||||
override fun isEmpty(): Boolean = excludedBlockchains.isEmpty()
|
||||
|
||||
override fun iterator(): Iterator<Blockchain> = excludedBlockchains.iterator()
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package com.tangem.blockchainsdk.providers
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.tangem.blockchainsdk.providers.BlockchainProvidersResponseMergerTest.Companion.localResponse
|
||||
import com.tangem.blockchainsdk.providers.BlockchainProvidersResponseMergerTest.Companion.remoteResponse
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.local.config.providers.BlockchainProvidersStorage
|
||||
import com.tangem.datasource.local.config.providers.models.ProviderModel
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class BlockchainProvidersResponseLoaderTest {
|
||||
|
||||
private val tangemTechApi = mockk<TangemTechApi>()
|
||||
private val blockchainProvidersStorage = mockk<BlockchainProvidersStorage>()
|
||||
|
||||
private val loader = BlockchainProvidersResponseLoader(
|
||||
tangemTechApi = tangemTechApi,
|
||||
blockchainProvidersStorage = blockchainProvidersStorage,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockkStatic(FirebaseCrashlytics::class)
|
||||
val firebaseCrashlytics = mockk<FirebaseCrashlytics>()
|
||||
every { FirebaseCrashlytics.getInstance() } returns firebaseCrashlytics
|
||||
every { firebaseCrashlytics.recordException(any()) } just Runs
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_if_local_config_is_empty() = runTest {
|
||||
coEvery { blockchainProvidersStorage.getConfigSync() } returns emptyMap()
|
||||
|
||||
val expected = null
|
||||
|
||||
val actual = loader.load()
|
||||
|
||||
coVerifyOrder { blockchainProvidersStorage.getConfigSync() }
|
||||
coVerify(inverse = true) { tangemTechApi.getBlockchainProviders() }
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_if_remote_config_loading_is_failed() = runTest {
|
||||
coEvery { blockchainProvidersStorage.getConfigSync() } returns localResponse
|
||||
coEvery { tangemTechApi.getBlockchainProviders() } throws IllegalStateException("Test exception")
|
||||
|
||||
val expected = localResponse
|
||||
|
||||
val actual = loader.load()
|
||||
|
||||
coVerifyOrder {
|
||||
blockchainProvidersStorage.getConfigSync()
|
||||
tangemTechApi.getBlockchainProviders()
|
||||
}
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_if_remote_config_is_loaded_successfully() = runTest {
|
||||
val eth = "ethereum" to listOf(ProviderModel.Private(name = "nownodes"))
|
||||
|
||||
coEvery { blockchainProvidersStorage.getConfigSync() } returns localResponse + eth
|
||||
coEvery { tangemTechApi.getBlockchainProviders() } returns remoteResponse
|
||||
|
||||
// Because configs are merged in BlockchainProvidersResponseMerger
|
||||
val expected = remoteResponse + eth
|
||||
|
||||
val actual = loader.load()
|
||||
|
||||
coVerifyOrder {
|
||||
blockchainProvidersStorage.getConfigSync()
|
||||
tangemTechApi.getBlockchainProviders()
|
||||
}
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,205 @@
|
|||
package com.tangem.blockchainsdk.providers
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.tangem.blockchainsdk.BlockchainProvidersResponse
|
||||
import com.tangem.datasource.local.config.providers.models.ProviderModel
|
||||
import io.mockk.*
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class BlockchainProvidersResponseMergerTest {
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockkStatic(FirebaseCrashlytics::class)
|
||||
val firebaseCrashlytics = mockk<FirebaseCrashlytics>()
|
||||
every { FirebaseCrashlytics.getInstance() } returns firebaseCrashlytics
|
||||
every { firebaseCrashlytics.recordException(any()) } just Runs
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_if_both_configs_are_empty() {
|
||||
val expected = emptyMap<String, List<ProviderModel>>()
|
||||
|
||||
val actual = BlockchainProvidersResponseMerger.merge(
|
||||
local = emptyMap(),
|
||||
remote = emptyMap(),
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_if_local_config_is_empty() {
|
||||
val expected = remoteResponse
|
||||
|
||||
val actual = BlockchainProvidersResponseMerger.merge(
|
||||
local = emptyMap(),
|
||||
remote = remoteResponse,
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_if_remote_config_is_empty() {
|
||||
val expected = localResponse
|
||||
|
||||
val actual = BlockchainProvidersResponseMerger.merge(
|
||||
local = localResponse,
|
||||
remote = emptyMap(),
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_if_both_configs_are_not_empty() {
|
||||
val expected = remoteResponse
|
||||
|
||||
val actual = BlockchainProvidersResponseMerger.merge(
|
||||
local = localResponse,
|
||||
remote = remoteResponse,
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_if_configs_are_equal() {
|
||||
val expected = remoteResponse
|
||||
|
||||
val actual = BlockchainProvidersResponseMerger.merge(
|
||||
local = remoteResponse,
|
||||
remote = remoteResponse,
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_if_local_config_has_blockchain_with_empty_providers() {
|
||||
val eth = "ethereum" to emptyList<ProviderModel>()
|
||||
val localResponseWithEth = localResponse + eth
|
||||
|
||||
val expected = localResponseWithEth + remoteResponse
|
||||
|
||||
val actual = BlockchainProvidersResponseMerger.merge(
|
||||
local = localResponseWithEth,
|
||||
remote = remoteResponse,
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_if_remote_config_has_blockchain_with_empty_providers() {
|
||||
val expected = remoteResponse
|
||||
|
||||
val eth = "ethereum" to emptyList<ProviderModel>()
|
||||
val remoteWithEth = remoteResponse + eth
|
||||
|
||||
val actual = BlockchainProvidersResponseMerger.merge(
|
||||
local = localResponse,
|
||||
remote = remoteWithEth,
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_if_remote_config_doesnt_contain_local_providers() {
|
||||
val remoteWithoutLocal = remoteResponse - localResponse.keys
|
||||
|
||||
/**
|
||||
* The expected result does not contain local blockchains, since they can be disabled remotely.
|
||||
* For the opposite case, see [test_if_both_configs_are_not_empty].
|
||||
*/
|
||||
val expected = remoteResponse
|
||||
|
||||
val actual = BlockchainProvidersResponseMerger.merge(
|
||||
local = localResponse,
|
||||
remote = remoteWithoutLocal,
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_if_remote_config_contains_unsupported_providers() {
|
||||
val nowNodesProvider = ProviderModel.Private(name = "nownodes")
|
||||
val eth = "ethereum" to listOf(ProviderModel.UnsupportedType, nowNodesProvider)
|
||||
val remoteWithEth = remoteResponse + eth
|
||||
|
||||
val expected = remoteResponse + ("ethereum" to listOf(nowNodesProvider))
|
||||
|
||||
val actual = BlockchainProvidersResponseMerger.merge(
|
||||
local = localResponse,
|
||||
remote = remoteWithEth,
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_if_remote_config_contains_invalid_public_providers() {
|
||||
val eth = "ethereum" to listOf(ProviderModel.UnsupportedType, ProviderModel.Public("adbw2138"))
|
||||
val remoteWithEth = remoteResponse + eth
|
||||
|
||||
val expected = remoteResponse
|
||||
|
||||
val actual = BlockchainProvidersResponseMerger.merge(
|
||||
local = localResponse,
|
||||
remote = remoteWithEth,
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun test_if_configs_contain_public_providers_without_slash_in_the_end() {
|
||||
val eth = "ethereum" to listOf(ProviderModel.Public("https://qwe.com"))
|
||||
val kaspa = "kaspa" to listOf(ProviderModel.Public("https://qwe.com"))
|
||||
|
||||
val localWithKaspa = localResponse + kaspa
|
||||
val remoteWithEth = remoteResponse + eth
|
||||
|
||||
val expected = remoteResponse + eth.addSlash() + kaspa.addSlash()
|
||||
|
||||
val actual = BlockchainProvidersResponseMerger.merge(
|
||||
local = localWithKaspa,
|
||||
remote = remoteWithEth,
|
||||
)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val localResponse: BlockchainProvidersResponse = mapOf(
|
||||
"aptos" to listOf(ProviderModel.Private(name = "nownodes")),
|
||||
"algorand" to listOf(
|
||||
ProviderModel.Private(name = "nownodes"),
|
||||
ProviderModel.Public(url = "https://public_alg.com/"),
|
||||
),
|
||||
)
|
||||
|
||||
// local + bitcoin
|
||||
val remoteResponse: BlockchainProvidersResponse = mapOf(
|
||||
"aptos" to listOf(ProviderModel.Private(name = "nownodes")),
|
||||
"algorand" to listOf(
|
||||
ProviderModel.Private(name = "nownodes"),
|
||||
ProviderModel.Public(url = "https://public_alg.com/"),
|
||||
),
|
||||
"bitcoin" to listOf(ProviderModel.Private(name = "blockchair")),
|
||||
)
|
||||
|
||||
private fun Pair<String, List<ProviderModel.Public>>.addSlash(): Pair<String, List<ProviderModel.Public>> {
|
||||
return first to second.map { it.copy(url = "${it.url}/") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,15 +1,26 @@
|
|||
plugins {
|
||||
id("java-library")
|
||||
id("org.jetbrains.kotlin.jvm")
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.lib.crypto"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/** Coroutines */
|
||||
implementation(Library.coroutine)
|
||||
}
|
||||
implementation(deps.kotlin.coroutines)
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
/** SDK */
|
||||
implementation(tangemDeps.blockchain)
|
||||
|
||||
/** Core */
|
||||
implementation(projects.core.utils)
|
||||
|
||||
/** Libs */
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="com.tangem.lib.crypto" />
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
package com.tangem.lib.crypto
|
||||
|
||||
import com.tangem.blockchain.blockchains.xrp.XrpAddressService
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.compatibility.l2BlockchainsList
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.minimalAmount
|
||||
import com.tangem.lib.crypto.converter.XrpTaggedAddressConverter
|
||||
import com.tangem.lib.crypto.models.XrpTaggedAddress
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* !!!IMPORTANT!!!
|
||||
* Methods for working with different blockchains
|
||||
* All methods are depend on specific blockchain or check for specific blockchain
|
||||
*
|
||||
* Temporary solution for domain specific logic for Blockchain.
|
||||
* Instead of creating repositories and unnecessary and overkill use cases
|
||||
*/
|
||||
object BlockchainUtils {
|
||||
|
||||
private const val XRP_X_ADDRESS = 'X'
|
||||
|
||||
/** Decodes XRP Blockchain address */
|
||||
fun decodeRippleXAddress(xAddress: String, blockchainId: String): XrpTaggedAddress? {
|
||||
return if (blockchainId == Blockchain.XRP.id && xAddress.firstOrNull() == XRP_X_ADDRESS) {
|
||||
val decodedAddress = XrpAddressService.decodeXAddress(xAddress)
|
||||
return decodedAddress?.let(XrpTaggedAddressConverter()::convert)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/** If current [networkId] is Bitcoin */
|
||||
fun isBitcoin(blockchainId: String): Boolean {
|
||||
val blockchain = Blockchain.fromId(blockchainId)
|
||||
return blockchain == Blockchain.Bitcoin || blockchain == Blockchain.BitcoinTestnet
|
||||
}
|
||||
|
||||
/** If current [networkId] is use custom fee */
|
||||
fun isUseBitcoinFeeConverter(blockchainId: String): Boolean {
|
||||
val blockchain = Blockchain.fromId(blockchainId)
|
||||
return isBitcoin(blockchainId) || blockchain == Blockchain.Fact0rn
|
||||
}
|
||||
|
||||
/** If current [blockchainId] is Tezos */
|
||||
fun isTezos(blockchainId: String): Boolean {
|
||||
val blockchain = Blockchain.fromId(blockchainId)
|
||||
return blockchain == Blockchain.Tezos
|
||||
}
|
||||
|
||||
fun isCardano(blockchainId: String): Boolean {
|
||||
val blockchain = Blockchain.fromId(blockchainId)
|
||||
return blockchain == Blockchain.Cardano
|
||||
}
|
||||
|
||||
/** If current [blockchainId] is BeaconChain */
|
||||
fun isBeaconChain(blockchainId: String): Boolean {
|
||||
val blockchain = Blockchain.fromId(blockchainId)
|
||||
return blockchain == Blockchain.Binance || blockchain == Blockchain.BinanceTestnet
|
||||
}
|
||||
|
||||
/** If current [blockchainId] is Polygon */
|
||||
fun isPolygonChain(blockchainId: String): Boolean {
|
||||
val blockchain = Blockchain.fromId(blockchainId)
|
||||
return blockchain == Blockchain.Polygon || blockchain == Blockchain.PolygonTestnet
|
||||
}
|
||||
|
||||
fun isTron(blockchainId: String): Boolean {
|
||||
val blockchain = Blockchain.fromId(blockchainId)
|
||||
return blockchain == Blockchain.Tron || blockchain == Blockchain.TronTestnet
|
||||
}
|
||||
|
||||
fun isSupportedNetworkId(blockchainId: String, excludedBlockchains: ExcludedBlockchains): Boolean {
|
||||
val blockchain = Blockchain.fromNetworkId(blockchainId)
|
||||
|
||||
return blockchain != null && blockchain !in excludedBlockchains
|
||||
}
|
||||
|
||||
fun isArbitrum(blockchainId: String): Boolean {
|
||||
val blockchain = Blockchain.fromId(blockchainId)
|
||||
return blockchain == Blockchain.Arbitrum
|
||||
}
|
||||
|
||||
fun isSolana(blockchainId: String): Boolean {
|
||||
val blockchain = Blockchain.fromId(blockchainId)
|
||||
return blockchain == Blockchain.Solana
|
||||
}
|
||||
|
||||
fun isPolkadot(blockchainId: String): Boolean {
|
||||
val blockchain = Blockchain.fromId(blockchainId)
|
||||
return blockchain == Blockchain.Polkadot || blockchain == Blockchain.PolkadotTestnet
|
||||
}
|
||||
|
||||
fun isCosmos(blockchainId: String): Boolean {
|
||||
val blockchain = Blockchain.fromId(blockchainId)
|
||||
return blockchain == Blockchain.Cosmos || blockchain == Blockchain.CosmosTestnet
|
||||
}
|
||||
|
||||
fun isBSC(blockchainId: String): Boolean {
|
||||
val blockchain = Blockchain.fromId(blockchainId)
|
||||
return blockchain == Blockchain.BSC || blockchain == Blockchain.BSCTestnet
|
||||
}
|
||||
|
||||
data class BlockchainInfo(
|
||||
val blockchainId: String,
|
||||
val name: String,
|
||||
val protocolName: String,
|
||||
)
|
||||
|
||||
fun getNetworkInfo(networkId: String): BlockchainInfo? {
|
||||
val blockchain = Blockchain.fromNetworkId(networkId) ?: return null
|
||||
|
||||
return BlockchainInfo(
|
||||
blockchainId = blockchain.id,
|
||||
name = getNetworkNameWithoutTestnet(blockchain),
|
||||
protocolName = getNetworkStandardName(blockchain),
|
||||
)
|
||||
}
|
||||
|
||||
fun isL2Network(networkId: String): Boolean {
|
||||
val blockchain = Blockchain.fromNetworkId(networkId) ?: return false
|
||||
return l2BlockchainsList.contains(blockchain)
|
||||
}
|
||||
|
||||
fun getTezosThreshold(): BigDecimal = Blockchain.Tezos.minimalAmount()
|
||||
|
||||
/**
|
||||
* Blockchains not affecting total balance counting on errors
|
||||
*/
|
||||
fun isIncludeToBalanceOnError(blockchainId: String): Boolean {
|
||||
val blockchain = Blockchain.fromId(blockchainId)
|
||||
return when (blockchain) {
|
||||
Blockchain.Binance, Blockchain.BinanceTestnet -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNetworkStandardName(blockchain: Blockchain): String {
|
||||
return when (blockchain) {
|
||||
Blockchain.Ethereum, Blockchain.EthereumTestnet -> "ERC20"
|
||||
Blockchain.BSC, Blockchain.BSCTestnet -> "BEP20"
|
||||
Blockchain.Binance, Blockchain.BinanceTestnet -> "BEP2"
|
||||
Blockchain.Tron, Blockchain.TronTestnet -> "TRC20"
|
||||
Blockchain.TON -> "TON"
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun getNetworkNameWithoutTestnet(blockchain: Blockchain): String {
|
||||
return blockchain.fullName.replace(oldValue = " Testnet", newValue = "")
|
||||
}
|
||||
|
||||
fun isTokenBetaFunctionality(blockchainId: String): Boolean {
|
||||
val blockchain = Blockchain.fromId(blockchainId)
|
||||
return blockchain == Blockchain.Kaspa
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
package com.tangem.lib.crypto
|
||||
|
||||
import com.tangem.lib.crypto.models.Currency
|
||||
|
||||
interface DerivationManager {
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* Checks that given [networkId] has derivations
|
||||
*/
|
||||
fun hasDerivation(networkId: String): Boolean
|
||||
}
|
||||
|
|
@ -1,50 +1,40 @@
|
|||
package com.tangem.lib.crypto
|
||||
|
||||
import com.tangem.lib.crypto.models.Currency
|
||||
import com.tangem.lib.crypto.models.ProxyAmount
|
||||
import com.tangem.lib.crypto.models.ProxyNetworkInfo
|
||||
import com.tangem.lib.crypto.models.transactions.SendTxResult
|
||||
import java.math.BigDecimal
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.lib.crypto.models.*
|
||||
import java.math.BigInteger
|
||||
|
||||
interface TransactionManager {
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun sendApproveTransaction(
|
||||
networkId: String,
|
||||
feeAmount: BigDecimal,
|
||||
estimatedGas: Int,
|
||||
destinationAddress: String,
|
||||
dataToSign: String,
|
||||
): SendTxResult
|
||||
|
||||
/**
|
||||
* Get fee
|
||||
*
|
||||
* @param networkId network id of blockchain
|
||||
* @param amountToSend amount
|
||||
* @param currencyToSend currency to send in tx
|
||||
* @param destinationAddress address to send tx
|
||||
* @param increaseBy percents in format 125 = 25%
|
||||
* @param data data for tx
|
||||
* @param derivationPath derivation path
|
||||
* @return
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun sendTransaction(
|
||||
networkId: String,
|
||||
amountToSend: BigDecimal,
|
||||
feeAmount: BigDecimal,
|
||||
estimatedGas: Int,
|
||||
destinationAddress: String,
|
||||
dataToSign: String,
|
||||
isSwap: Boolean,
|
||||
currencyToSend: Currency,
|
||||
): SendTxResult
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun getFee(
|
||||
networkId: String,
|
||||
amountToSend: BigDecimal,
|
||||
amountToSend: Amount,
|
||||
currencyToSend: Currency,
|
||||
destinationAddress: String,
|
||||
): ProxyAmount
|
||||
increaseBy: Int?,
|
||||
data: String?,
|
||||
derivationPath: String?,
|
||||
): ProxyFees
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
fun getNativeTokenDecimals(networkId: String): Int
|
||||
suspend fun getFeeForGas(networkId: String, gas: BigInteger, derivationPath: String?): ProxyFees
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun updateWalletManager(networkId: String)
|
||||
|
||||
fun calculateFee(networkId: String, gasPrice: String, estimatedGas: Int): BigDecimal
|
||||
suspend fun updateWalletManager(networkId: String, derivationPath: String?)
|
||||
|
||||
/**
|
||||
* In app blockchain id, actual in blockchain sdk, not the same as networkId
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
package com.tangem.lib.crypto
|
||||
|
||||
import com.tangem.lib.crypto.models.Currency
|
||||
import com.tangem.lib.crypto.models.ProxyAmount
|
||||
import com.tangem.lib.crypto.models.ProxyFiatCurrency
|
||||
|
||||
/**
|
||||
* Provider for user tokens data
|
||||
|
|
@ -10,62 +8,24 @@ import com.tangem.lib.crypto.models.ProxyFiatCurrency
|
|||
interface UserWalletManager {
|
||||
|
||||
/**
|
||||
* Returns all user tokens (merged from local and backend)
|
||||
*/
|
||||
suspend fun getUserTokens(networkId: String): List<Currency>
|
||||
|
||||
fun getNativeTokenForNetwork(networkId: String): Currency
|
||||
|
||||
/**
|
||||
* Returns user walletId
|
||||
* Returns user walletId or empty string
|
||||
*/
|
||||
fun getWalletId(): String
|
||||
|
||||
/**
|
||||
* Checks that token added to user wallet
|
||||
*
|
||||
* @param currency to receive referral payments
|
||||
*/
|
||||
suspend fun isTokenAdded(currency: Currency): Boolean
|
||||
|
||||
/**
|
||||
* Adds token to wallet if its not
|
||||
*
|
||||
* @param currency to add to wallet
|
||||
*/
|
||||
@Throws(IllegalStateException::class)
|
||||
fun addToken(currency: Currency)
|
||||
suspend fun hideAllTokens()
|
||||
|
||||
/**
|
||||
* Returns wallet public address for token
|
||||
*
|
||||
* @param networkId for currency
|
||||
* @param derivationPath if null uses default
|
||||
*/
|
||||
@Throws(IllegalStateException::class)
|
||||
fun getWalletAddress(networkId: String): String
|
||||
|
||||
/**
|
||||
* Return balances from wallet found by networkId
|
||||
*
|
||||
* @param networkId
|
||||
* @return map of <Symbol, [ProxyAmount]>
|
||||
*/
|
||||
@Throws(IllegalStateException::class)
|
||||
fun getCurrentWalletTokensBalance(networkId: String): Map<String, ProxyAmount>
|
||||
|
||||
fun getNativeTokenBalance(networkId: String): ProxyAmount?
|
||||
|
||||
/**
|
||||
* @param networkId
|
||||
* @return currency name
|
||||
*/
|
||||
fun getNetworkCurrency(networkId: String): String
|
||||
|
||||
/**
|
||||
* Returns selected app currency
|
||||
*/
|
||||
fun getUserAppCurrency(): ProxyFiatCurrency
|
||||
suspend fun getWalletAddress(networkId: String, derivationPath: String?): String
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
fun getLastTransactionHash(networkId: String): String?
|
||||
suspend fun getNativeTokenBalance(networkId: String, derivationPath: String?): ProxyAmount?
|
||||
|
||||
@Throws(IllegalStateException::class)
|
||||
suspend fun getLastTransactionHash(networkId: String, derivationPath: String?): String?
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.lib.crypto.converter
|
||||
|
||||
import com.tangem.lib.crypto.models.XrpTaggedAddress
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.blockchain.blockchains.xrp.XrpTaggedAddress as BlockchainXrpTaggedAddress
|
||||
|
||||
internal class XrpTaggedAddressConverter : Converter<BlockchainXrpTaggedAddress, XrpTaggedAddress> {
|
||||
|
||||
override fun convert(value: BlockchainXrpTaggedAddress): XrpTaggedAddress {
|
||||
return XrpTaggedAddress(
|
||||
address = value.address,
|
||||
destinationTag = value.destinationTag,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.lib.crypto.models
|
||||
|
||||
/**
|
||||
* Analytics data for send events in analytics engine
|
||||
*
|
||||
* @property feeType type of fee (min,max,normal)
|
||||
* @property tokenSymbol symbol
|
||||
* @property permissionType optional parameter used for type tx approve
|
||||
*/
|
||||
data class AnalyticsData(
|
||||
val feeType: String,
|
||||
val tokenSymbol: String,
|
||||
val permissionType: String? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.lib.crypto.models
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Tx data for create and make approve transaction
|
||||
*
|
||||
* @property networkId id of network
|
||||
* @property feeAmount amount of fee
|
||||
* @property gasLimit gasLimit for given tx
|
||||
* @property destinationAddress address to send tx
|
||||
* @property dataToSign data to sing with signer
|
||||
*/
|
||||
data class ApproveTxData(
|
||||
val networkId: String,
|
||||
val feeAmount: BigDecimal,
|
||||
val gasLimit: Int,
|
||||
val destinationAddress: String,
|
||||
val dataToSign: String,
|
||||
)
|
||||
|
|
@ -13,4 +13,11 @@ data class ProxyAmount(
|
|||
val currencySymbol: String,
|
||||
var value: BigDecimal,
|
||||
val decimals: Int,
|
||||
)
|
||||
) {
|
||||
|
||||
companion object {
|
||||
fun empty(): ProxyAmount {
|
||||
return ProxyAmount("", BigDecimal.ZERO, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.lib.crypto.models
|
||||
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
|
||||
sealed interface ProxyFee {
|
||||
|
||||
val gasLimit: BigInteger
|
||||
val fee: ProxyAmount
|
||||
|
||||
data class Common(
|
||||
override val gasLimit: BigInteger,
|
||||
override val fee: ProxyAmount,
|
||||
) : ProxyFee
|
||||
|
||||
data class CardanoToken(
|
||||
override val gasLimit: BigInteger,
|
||||
override val fee: ProxyAmount,
|
||||
val minAdaValue: BigDecimal,
|
||||
) : ProxyFee
|
||||
|
||||
data class Filecoin(
|
||||
override val gasLimit: BigInteger,
|
||||
override val fee: ProxyAmount,
|
||||
val gasPremium: Long,
|
||||
) : ProxyFee
|
||||
|
||||
data class Sui(
|
||||
override val gasLimit: BigInteger,
|
||||
override val fee: ProxyAmount,
|
||||
val gasPrice: Long,
|
||||
val gasBudget: Long,
|
||||
) : ProxyFee
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.lib.crypto.models
|
||||
|
||||
sealed class ProxyFees {
|
||||
|
||||
data class MultipleFees(
|
||||
val minFee: ProxyFee,
|
||||
val normalFee: ProxyFee,
|
||||
val priorityFee: ProxyFee,
|
||||
) : ProxyFees()
|
||||
|
||||
data class SingleFee(
|
||||
val singleFee: ProxyFee,
|
||||
) : ProxyFees()
|
||||
}
|
||||
|
|
@ -3,5 +3,4 @@ package com.tangem.lib.crypto.models
|
|||
data class ProxyNetworkInfo(
|
||||
val name: String,
|
||||
val blockchainId: String,
|
||||
val blockchainCurrency: String,
|
||||
)
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package com.tangem.lib.crypto.models
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Tx data for create and make transaction
|
||||
*
|
||||
* @property networkId id of network
|
||||
* @property feeAmount amount of fee
|
||||
* @property gasLimit gasLimit for given tx
|
||||
* @property destinationAddress address to send tx
|
||||
* @property dataToSign data to sing with signer
|
||||
* @property amountToSend amount of tx
|
||||
* @property currencyToSend currency for tx
|
||||
*/
|
||||
// TODO split to cex,dex
|
||||
data class SwapTxData(
|
||||
val networkId: String,
|
||||
val feeAmount: BigDecimal,
|
||||
val gasLimit: Int,
|
||||
val destinationAddress: String,
|
||||
val dataToSign: String,
|
||||
val amountToSend: BigDecimal,
|
||||
val currencyToSend: Currency,
|
||||
)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.lib.crypto.models
|
||||
|
||||
data class XrpTaggedAddress(
|
||||
val address: String,
|
||||
val destinationTag: Long?,
|
||||
)
|
||||
|
|
@ -6,5 +6,6 @@ sealed interface SendTxResult {
|
|||
object UserCancelledError : SendTxResult
|
||||
data class TangemSdkError(val code: Int, val cause: Throwable?) : SendTxResult
|
||||
data class BlockchainSdkError(val code: Int, val cause: Throwable?) : SendTxResult
|
||||
data class NetworkError(val ex: Exception? = null) : SendTxResult
|
||||
data class UnknownError(val ex: Exception? = null) : SendTxResult
|
||||
}
|
||||
1
libs/tangem-sdk-api/.gitignore
vendored
Normal file
1
libs/tangem-sdk-api/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
36
libs/tangem-sdk-api/build.gradle.kts
Normal file
36
libs/tangem-sdk-api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.legacy"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.common)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.legacy)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.visa.models)
|
||||
|
||||
implementation(projects.core.res)
|
||||
|
||||
/** Tangem libraries */
|
||||
implementation(tangemDeps.card.core)
|
||||
implementation(tangemDeps.card.android) {
|
||||
exclude(module = "joda-time")
|
||||
}
|
||||
|
||||
/** Other libraries */
|
||||
implementation(deps.timber)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.sdk.api
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.operations.backup.BackupService
|
||||
import com.tangem.sdk.extensions.init
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class BackupServiceHolder {
|
||||
|
||||
lateinit var backupService: WeakReference<BackupService>
|
||||
private set
|
||||
|
||||
fun createAndSetService(tangemSdk: TangemSdk, activity: ComponentActivity) {
|
||||
backupService = WeakReference(BackupService.init(tangemSdk, activity))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.sdk.api
|
||||
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.KeyWalletPublicKey
|
||||
import com.tangem.operations.CommandResponse
|
||||
import com.tangem.operations.backup.PrimaryCard
|
||||
import com.tangem.operations.derivation.ExtendedPublicKeysMap
|
||||
|
||||
data class CreateProductWalletTaskResponse(
|
||||
val card: CardDTO,
|
||||
val derivedKeys: Map<KeyWalletPublicKey, ExtendedPublicKeysMap> = mapOf(),
|
||||
val primaryCard: PrimaryCard? = null,
|
||||
) : CommandResponse {
|
||||
constructor(
|
||||
card: Card,
|
||||
derivedKeys: Map<KeyWalletPublicKey, ExtendedPublicKeysMap> = mapOf(),
|
||||
primaryCard: PrimaryCard? = null,
|
||||
) : this(
|
||||
card = CardDTO(card),
|
||||
derivedKeys = derivedKeys,
|
||||
primaryCard = primaryCard,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
package com.tangem.sdk.api
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import com.tangem.Message
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.KeyPair
|
||||
import com.tangem.common.SuccessResponse
|
||||
import com.tangem.common.authentication.keystore.KeystoreManager
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.UserCodeRequestPolicy
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.services.secure.SecureStorage
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.visa.model.VisaActivationInput
|
||||
import com.tangem.domain.visa.model.VisaDataForApprove
|
||||
import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||
import com.tangem.operations.preflightread.PreflightReadFilter
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
import com.tangem.sdk.api.visa.VisaCardActivationResponse
|
||||
import com.tangem.sdk.api.visa.VisaCardActivationTaskMode
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
interface TangemSdkManager {
|
||||
|
||||
val canUseBiometry: Boolean
|
||||
|
||||
val needEnrollBiometrics: Boolean
|
||||
|
||||
val keystoreManager: KeystoreManager
|
||||
|
||||
val secureStorage: SecureStorage
|
||||
|
||||
val userCodeRequestPolicy: UserCodeRequestPolicy
|
||||
|
||||
suspend fun checkCanUseBiometry(awaitInitialization: Boolean = true): Boolean
|
||||
|
||||
suspend fun checkNeedEnrollBiometrics(awaitInitialization: Boolean = true): Boolean
|
||||
|
||||
suspend fun scanProduct(
|
||||
cardId: String? = null,
|
||||
messageRes: Int? = null,
|
||||
allowsRequestAccessCodeFromRepository: Boolean = false,
|
||||
): CompletionResult<ScanResponse>
|
||||
|
||||
suspend fun createProductWallet(
|
||||
scanResponse: ScanResponse,
|
||||
shouldReset: Boolean = false,
|
||||
): CompletionResult<CreateProductWalletTaskResponse>
|
||||
|
||||
// Wallet2 specific
|
||||
suspend fun importWallet(
|
||||
scanResponse: ScanResponse,
|
||||
mnemonic: String,
|
||||
passphrase: String?,
|
||||
shouldReset: Boolean,
|
||||
): CompletionResult<CreateProductWalletTaskResponse>
|
||||
|
||||
suspend fun derivePublicKeys(
|
||||
cardId: String?,
|
||||
derivations: Map<ByteArrayKey, List<DerivationPath>>,
|
||||
preflightReadFilter: PreflightReadFilter?,
|
||||
): CompletionResult<DerivationTaskResponse>
|
||||
|
||||
suspend fun deriveExtendedPublicKey(
|
||||
cardId: String?,
|
||||
walletPublicKey: ByteArray,
|
||||
derivation: DerivationPath,
|
||||
): CompletionResult<ExtendedPublicKey>
|
||||
|
||||
suspend fun resetToFactorySettings(
|
||||
cardId: String,
|
||||
allowsRequestAccessCodeFromRepository: Boolean,
|
||||
): CompletionResult<Boolean>
|
||||
|
||||
suspend fun resetBackupCard(cardNumber: Int, userWalletId: UserWalletId): CompletionResult<Boolean>
|
||||
|
||||
suspend fun saveAccessCode(accessCode: String, cardsIds: Set<String>): CompletionResult<Unit>
|
||||
|
||||
suspend fun deleteSavedUserCodes(cardsIds: Set<String>): CompletionResult<Unit>
|
||||
|
||||
suspend fun clearSavedUserCodes(): CompletionResult<Unit>
|
||||
|
||||
suspend fun setPasscode(cardId: String?): CompletionResult<SuccessResponse>
|
||||
|
||||
suspend fun setAccessCode(cardId: String?): CompletionResult<SuccessResponse>
|
||||
|
||||
suspend fun setLongTap(cardId: String?): CompletionResult<SuccessResponse>
|
||||
|
||||
suspend fun setAccessCodeRecoveryEnabled(cardId: String?, enabled: Boolean): CompletionResult<SuccessResponse>
|
||||
|
||||
suspend fun scanCard(
|
||||
cardId: String? = null,
|
||||
allowRequestAccessCodeFromRepository: Boolean = false,
|
||||
): CompletionResult<CardDTO>
|
||||
|
||||
@Deprecated(
|
||||
"com.tangem.sdk.api.TangemSdkManager shouldn't run custom tasks. " +
|
||||
"All of them should be specified in com.tangem.sdk.api.TangemSdkManager certain methods.",
|
||||
)
|
||||
suspend fun <T> runTaskAsync(
|
||||
runnable: CardSessionRunnable<T>,
|
||||
preflightReadFilter: PreflightReadFilter?,
|
||||
cardId: String? = null,
|
||||
initialMessage: Message? = null,
|
||||
accessCode: String? = null,
|
||||
@DrawableRes iconScanRes: Int? = null,
|
||||
): CompletionResult<T>
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
fun changeDisplayedCardIdNumbersCount(scanResponse: ScanResponse?)
|
||||
|
||||
@Deprecated("com.tangem.sdk.api.TangemSdkManager shouldn't returns a string from resources")
|
||||
fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String
|
||||
|
||||
fun setUserCodeRequestPolicy(policy: UserCodeRequestPolicy)
|
||||
|
||||
// region Twin-specific
|
||||
|
||||
suspend fun finalizeTwin(
|
||||
secondCardPublicKey: ByteArray,
|
||||
issuerKeyPair: KeyPair,
|
||||
cardId: String,
|
||||
initialMessage: Message,
|
||||
): CompletionResult<ScanResponse>
|
||||
|
||||
suspend fun createFirstTwinWallet(cardId: String, initialMessage: Message): CompletionResult<CreateWalletResponse>
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
suspend fun createSecondTwinWallet(
|
||||
firstPublicKey: String,
|
||||
firstCardId: String,
|
||||
issuerKeys: KeyPair,
|
||||
preparingMessage: Message,
|
||||
creatingWalletMessage: Message,
|
||||
initialMessage: Message,
|
||||
): CompletionResult<CreateWalletResponse>
|
||||
|
||||
fun changeProductType(isRing: Boolean)
|
||||
|
||||
fun clearProductType()
|
||||
|
||||
// endregion
|
||||
|
||||
// region Visa-specific
|
||||
|
||||
suspend fun activateVisaCard(
|
||||
mode: VisaCardActivationTaskMode,
|
||||
activationInput: VisaActivationInput,
|
||||
): CompletionResult<VisaCardActivationResponse>
|
||||
|
||||
suspend fun visaCustomerWalletApprove(
|
||||
visaDataForApprove: VisaDataForApprove,
|
||||
): CompletionResult<VisaSignedDataByCustomerWallet>
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.tangem.sdk.api
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.tangem.common.core.TangemError
|
||||
import com.tangem.legacy.R
|
||||
|
||||
interface TapErrors
|
||||
|
||||
interface ArgError {
|
||||
val args: List<Any>?
|
||||
}
|
||||
|
||||
interface MultiMessageError : TapErrors {
|
||||
val errorList: List<TapError>
|
||||
val builder: (List<String>) -> String
|
||||
}
|
||||
|
||||
sealed class TapError(
|
||||
@StringRes val messageResource: Int,
|
||||
override val args: List<Any>? = null,
|
||||
) : Throwable(), TapErrors, ArgError {
|
||||
|
||||
object UnknownError : TapError(R.string.send_error_unknown)
|
||||
open class CustomError(val customMessage: String) : TapError(R.string.common_custom_string, listOf(customMessage))
|
||||
|
||||
object NoInternetConnection : TapError(R.string.wallet_notification_no_internet)
|
||||
|
||||
sealed class WalletManager {
|
||||
class NoAccountError(amountToCreateAccount: String) : CustomError(amountToCreateAccount)
|
||||
class InternalError(message: String) : CustomError(message)
|
||||
object BlockchainIsUnreachableTryLater : TapError(R.string.wallet_balance_blockchain_unreachable_try_later)
|
||||
}
|
||||
}
|
||||
|
||||
sealed class TapSdkError(override val messageResId: Int?) : TangemError(code = 50100) {
|
||||
override var customMessage: String = code.toString()
|
||||
|
||||
object CardForDifferentApp : TapSdkError(R.string.alert_unsupported_card)
|
||||
object CardNotSupportedByRelease : TapSdkError(R.string.error_wrong_card_type)
|
||||
}
|
||||
|
||||
fun TapErrors.assembleErrors(): MutableList<Pair<Int, List<Any>?>> {
|
||||
val idList = mutableListOf<Pair<Int, List<Any>?>>()
|
||||
when (this) {
|
||||
is MultiMessageError -> this.errorList.forEach { idList.addAll(it.assembleErrors()) }
|
||||
is TapError -> idList.add(Pair(this.messageResource, this.args))
|
||||
}
|
||||
return idList
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.sdk.api.visa
|
||||
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.visa.model.VisaSignedActivationDataByCardWallet
|
||||
|
||||
data class VisaCardActivationResponse(
|
||||
val signedActivationData: VisaSignedActivationDataByCardWallet,
|
||||
val newCardDTO: CardDTO,
|
||||
)
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.sdk.api.visa
|
||||
|
||||
import com.tangem.domain.visa.model.VisaAuthChallenge
|
||||
import com.tangem.domain.visa.model.VisaDataToSignByCardWallet
|
||||
|
||||
sealed class VisaCardActivationTaskMode {
|
||||
/**
|
||||
* Full activation process with getting remote activation status.
|
||||
*/
|
||||
data class Full(
|
||||
val accessCode: String,
|
||||
val authorizationChallenge: VisaAuthChallenge.Card,
|
||||
) : VisaCardActivationTaskMode()
|
||||
|
||||
/**
|
||||
* Activation process with only sign data by card wallet.
|
||||
* This is used when activation process was interrupted and we need to finish it.
|
||||
*/
|
||||
data class SignOnly(val dataToSignByCardWallet: VisaDataToSignByCardWallet) : VisaCardActivationTaskMode()
|
||||
}
|
||||
1
libs/visa/.gitignore
vendored
Normal file
1
libs/visa/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
35
libs/visa/build.gradle.kts
Normal file
35
libs/visa/build.gradle.kts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import com.tangem.plugin.configuration.configurations.extension.kaptForObfuscatingVariants
|
||||
|
||||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.libs.visa"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
/** Project */
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.data.common)
|
||||
|
||||
/** Libs - Network */
|
||||
implementation(deps.moshi.kotlin)
|
||||
implementation(deps.okHttp)
|
||||
implementation(deps.okHttp.prettyLogging)
|
||||
implementation(deps.retrofit)
|
||||
implementation(deps.retrofit.moshi)
|
||||
kaptForObfuscatingVariants(deps.moshi.kotlin.codegen)
|
||||
kaptForObfuscatingVariants(deps.retrofit.response.type.keeper)
|
||||
|
||||
/** Libs - Other */
|
||||
implementation(deps.web3j.core)
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.arrow.fx)
|
||||
implementation(deps.jodatime)
|
||||
}
|
||||
267
libs/visa/src/main/java/com/tangem/lib/visa/ERC20.java
Normal file
267
libs/visa/src/main/java/com/tangem/lib/visa/ERC20.java
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
package com.tangem.lib.visa;
|
||||
|
||||
import org.web3j.abi.EventEncoder;
|
||||
import org.web3j.abi.TypeReference;
|
||||
import org.web3j.abi.datatypes.*;
|
||||
import org.web3j.abi.datatypes.generated.Uint256;
|
||||
import org.web3j.abi.datatypes.generated.Uint8;
|
||||
import org.web3j.crypto.Credentials;
|
||||
import org.web3j.protocol.Web3j;
|
||||
import org.web3j.protocol.core.DefaultBlockParameter;
|
||||
import org.web3j.protocol.core.RemoteFunctionCall;
|
||||
import org.web3j.protocol.core.methods.request.EthFilter;
|
||||
import org.web3j.protocol.core.methods.response.BaseEventResponse;
|
||||
import org.web3j.protocol.core.methods.response.Log;
|
||||
import org.web3j.protocol.core.methods.response.TransactionReceipt;
|
||||
import org.web3j.tx.Contract;
|
||||
import org.web3j.tx.TransactionManager;
|
||||
import org.web3j.tx.gas.ContractGasProvider;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.reactivex.Flowable;
|
||||
|
||||
/**
|
||||
* <p>Auto generated code.
|
||||
* <p><strong>Do not modify!</strong>
|
||||
* <p>Please use the <a href="https://docs.web3j.io/command_line.html">web3j command line tools</a>,
|
||||
* or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the
|
||||
* <a href="https://github.com/web3j/web3j/tree/master/codegen">codegen module</a> to update.
|
||||
*
|
||||
* <p>Generated with web3j version 1.5.2.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
class ERC20 extends Contract {
|
||||
public static final String BINARY = "Bin file was not provided";
|
||||
|
||||
public static final String FUNC_ALLOWANCE = "allowance";
|
||||
|
||||
public static final String FUNC_APPROVE = "approve";
|
||||
|
||||
public static final String FUNC_BALANCEOF = "balanceOf";
|
||||
|
||||
public static final String FUNC_DECIMALS = "decimals";
|
||||
|
||||
public static final String FUNC_NAME = "name";
|
||||
|
||||
public static final String FUNC_SYMBOL = "symbol";
|
||||
|
||||
public static final String FUNC_TOTALSUPPLY = "totalSupply";
|
||||
|
||||
public static final String FUNC_TRANSFER = "transfer";
|
||||
|
||||
public static final String FUNC_TRANSFERFROM = "transferFrom";
|
||||
|
||||
public static final Event APPROVAL_EVENT = new Event("Approval",
|
||||
Arrays.asList(new TypeReference<Address>(true) {
|
||||
}, new TypeReference<Address>(true) {
|
||||
}, new TypeReference<Uint256>() {
|
||||
}));
|
||||
|
||||
public static final Event TRANSFER_EVENT = new Event("Transfer",
|
||||
Arrays.asList(new TypeReference<Address>(true) {
|
||||
}, new TypeReference<Address>(true) {
|
||||
}, new TypeReference<Uint256>() {
|
||||
}));
|
||||
|
||||
@Deprecated
|
||||
protected ERC20(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
|
||||
super(BINARY, contractAddress, web3j, credentials, gasPrice, gasLimit);
|
||||
}
|
||||
|
||||
protected ERC20(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) {
|
||||
super(BINARY, contractAddress, web3j, credentials, contractGasProvider);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
protected ERC20(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) {
|
||||
super(BINARY, contractAddress, web3j, transactionManager, gasPrice, gasLimit);
|
||||
}
|
||||
|
||||
protected ERC20(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) {
|
||||
super(BINARY, contractAddress, web3j, transactionManager, contractGasProvider);
|
||||
}
|
||||
|
||||
public static List<ApprovalEventResponse> getApprovalEvents(TransactionReceipt transactionReceipt) {
|
||||
List<EventValuesWithLog> valueList = staticExtractEventParametersWithLog(APPROVAL_EVENT, transactionReceipt);
|
||||
ArrayList<ApprovalEventResponse> responses = new ArrayList<ApprovalEventResponse>(valueList.size());
|
||||
for (EventValuesWithLog eventValues : valueList) {
|
||||
ApprovalEventResponse typedResponse = new ApprovalEventResponse();
|
||||
typedResponse.log = eventValues.getLog();
|
||||
typedResponse.owner = (String) eventValues.getIndexedValues().get(0).getValue();
|
||||
typedResponse.spender = (String) eventValues.getIndexedValues().get(1).getValue();
|
||||
typedResponse.value = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue();
|
||||
responses.add(typedResponse);
|
||||
}
|
||||
return responses;
|
||||
}
|
||||
|
||||
public static ApprovalEventResponse getApprovalEventFromLog(Log log) {
|
||||
EventValuesWithLog eventValues = staticExtractEventParametersWithLog(APPROVAL_EVENT, log);
|
||||
ApprovalEventResponse typedResponse = new ApprovalEventResponse();
|
||||
typedResponse.log = log;
|
||||
typedResponse.owner = (String) eventValues.getIndexedValues().get(0).getValue();
|
||||
typedResponse.spender = (String) eventValues.getIndexedValues().get(1).getValue();
|
||||
typedResponse.value = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue();
|
||||
return typedResponse;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static ERC20 load(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
|
||||
return new ERC20(contractAddress, web3j, credentials, gasPrice, gasLimit);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public static ERC20 load(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) {
|
||||
return new ERC20(contractAddress, web3j, transactionManager, gasPrice, gasLimit);
|
||||
}
|
||||
|
||||
public static List<TransferEventResponse> getTransferEvents(TransactionReceipt transactionReceipt) {
|
||||
List<EventValuesWithLog> valueList = staticExtractEventParametersWithLog(TRANSFER_EVENT, transactionReceipt);
|
||||
ArrayList<TransferEventResponse> responses = new ArrayList<TransferEventResponse>(valueList.size());
|
||||
for (EventValuesWithLog eventValues : valueList) {
|
||||
TransferEventResponse typedResponse = new TransferEventResponse();
|
||||
typedResponse.log = eventValues.getLog();
|
||||
typedResponse.from = (String) eventValues.getIndexedValues().get(0).getValue();
|
||||
typedResponse.to = (String) eventValues.getIndexedValues().get(1).getValue();
|
||||
typedResponse.value = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue();
|
||||
responses.add(typedResponse);
|
||||
}
|
||||
return responses;
|
||||
}
|
||||
|
||||
public static TransferEventResponse getTransferEventFromLog(Log log) {
|
||||
EventValuesWithLog eventValues = staticExtractEventParametersWithLog(TRANSFER_EVENT, log);
|
||||
TransferEventResponse typedResponse = new TransferEventResponse();
|
||||
typedResponse.log = log;
|
||||
typedResponse.from = (String) eventValues.getIndexedValues().get(0).getValue();
|
||||
typedResponse.to = (String) eventValues.getIndexedValues().get(1).getValue();
|
||||
typedResponse.value = (BigInteger) eventValues.getNonIndexedValues().get(0).getValue();
|
||||
return typedResponse;
|
||||
}
|
||||
|
||||
public Flowable<TransferEventResponse> transferEventFlowable(EthFilter filter) {
|
||||
return web3j.ethLogFlowable(filter).map(log -> getTransferEventFromLog(log));
|
||||
}
|
||||
|
||||
public Flowable<TransferEventResponse> transferEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) {
|
||||
EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress());
|
||||
filter.addSingleTopic(EventEncoder.encode(TRANSFER_EVENT));
|
||||
return transferEventFlowable(filter);
|
||||
}
|
||||
|
||||
public static ERC20 load(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) {
|
||||
return new ERC20(contractAddress, web3j, credentials, contractGasProvider);
|
||||
}
|
||||
|
||||
public static ERC20 load(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) {
|
||||
return new ERC20(contractAddress, web3j, transactionManager, contractGasProvider);
|
||||
}
|
||||
|
||||
public Flowable<ApprovalEventResponse> approvalEventFlowable(EthFilter filter) {
|
||||
return web3j.ethLogFlowable(filter).map(log -> getApprovalEventFromLog(log));
|
||||
}
|
||||
|
||||
public Flowable<ApprovalEventResponse> approvalEventFlowable(DefaultBlockParameter startBlock, DefaultBlockParameter endBlock) {
|
||||
EthFilter filter = new EthFilter(startBlock, endBlock, getContractAddress());
|
||||
filter.addSingleTopic(EventEncoder.encode(APPROVAL_EVENT));
|
||||
return approvalEventFlowable(filter);
|
||||
}
|
||||
|
||||
public RemoteFunctionCall<BigInteger> allowance(String owner, String spender) {
|
||||
final Function function = new Function(FUNC_ALLOWANCE,
|
||||
Arrays.asList(new Address(160, owner),
|
||||
new Address(160, spender)),
|
||||
List.of(new TypeReference<Uint256>() {
|
||||
}));
|
||||
return executeRemoteCallSingleValueReturn(function, BigInteger.class);
|
||||
}
|
||||
|
||||
public RemoteFunctionCall<TransactionReceipt> approve(String spender, BigInteger value) {
|
||||
final Function function = new Function(
|
||||
FUNC_APPROVE,
|
||||
Arrays.asList(new Address(160, spender),
|
||||
new Uint256(value)),
|
||||
Collections.emptyList());
|
||||
return executeRemoteCallTransaction(function);
|
||||
}
|
||||
|
||||
public RemoteFunctionCall<BigInteger> balanceOf(String account) {
|
||||
final Function function = new Function(FUNC_BALANCEOF,
|
||||
List.of(new Address(160, account)),
|
||||
List.of(new TypeReference<Uint256>() {
|
||||
}));
|
||||
return executeRemoteCallSingleValueReturn(function, BigInteger.class);
|
||||
}
|
||||
|
||||
public RemoteFunctionCall<BigInteger> decimals() {
|
||||
final Function function = new Function(FUNC_DECIMALS,
|
||||
List.of(),
|
||||
List.of(new TypeReference<Uint8>() {
|
||||
}));
|
||||
return executeRemoteCallSingleValueReturn(function, BigInteger.class);
|
||||
}
|
||||
|
||||
public RemoteFunctionCall<String> name() {
|
||||
final Function function = new Function(FUNC_NAME,
|
||||
List.of(),
|
||||
List.of(new TypeReference<Utf8String>() {
|
||||
}));
|
||||
return executeRemoteCallSingleValueReturn(function, String.class);
|
||||
}
|
||||
|
||||
public RemoteFunctionCall<String> symbol() {
|
||||
final Function function = new Function(FUNC_SYMBOL,
|
||||
List.of(),
|
||||
List.of(new TypeReference<Utf8String>() {
|
||||
}));
|
||||
return executeRemoteCallSingleValueReturn(function, String.class);
|
||||
}
|
||||
|
||||
public RemoteFunctionCall<BigInteger> totalSupply() {
|
||||
final Function function = new Function(FUNC_TOTALSUPPLY,
|
||||
List.of(),
|
||||
List.of(new TypeReference<Uint256>() {
|
||||
}));
|
||||
return executeRemoteCallSingleValueReturn(function, BigInteger.class);
|
||||
}
|
||||
|
||||
public RemoteFunctionCall<TransactionReceipt> transfer(String to, BigInteger value) {
|
||||
final Function function = new Function(
|
||||
FUNC_TRANSFER,
|
||||
Arrays.asList(new Address(160, to),
|
||||
new Uint256(value)),
|
||||
Collections.emptyList());
|
||||
return executeRemoteCallTransaction(function);
|
||||
}
|
||||
|
||||
public RemoteFunctionCall<TransactionReceipt> transferFrom(String from, String to, BigInteger value) {
|
||||
final Function function = new Function(
|
||||
FUNC_TRANSFERFROM,
|
||||
Arrays.asList(new Address(160, from),
|
||||
new Address(160, to),
|
||||
new Uint256(value)),
|
||||
Collections.emptyList());
|
||||
return executeRemoteCallTransaction(function);
|
||||
}
|
||||
|
||||
public static class ApprovalEventResponse extends BaseEventResponse {
|
||||
public String owner;
|
||||
|
||||
public String spender;
|
||||
|
||||
public BigInteger value;
|
||||
}
|
||||
|
||||
public static class TransferEventResponse extends BaseEventResponse {
|
||||
public String from;
|
||||
|
||||
public String to;
|
||||
|
||||
public BigInteger value;
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,160 @@
|
|||
package com.tangem.lib.visa
|
||||
|
||||
import arrow.fx.coroutines.parZip
|
||||
import com.tangem.lib.visa.model.VisaContractInfo
|
||||
import com.tangem.lib.visa.model.VisaContractInfo.*
|
||||
import com.tangem.lib.visa.utils.toBigDecimal
|
||||
import com.tangem.lib.visa.utils.toInstant
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import org.joda.time.Instant
|
||||
import org.web3j.protocol.Web3j
|
||||
import org.web3j.tx.TransactionManager
|
||||
import org.web3j.tx.gas.ContractGasProvider
|
||||
|
||||
internal class DefaultVisaContractInfoProvider(
|
||||
private val web3j: Web3j,
|
||||
private val transactionManager: TransactionManager,
|
||||
private val gasProvider: ContractGasProvider,
|
||||
private val bridgeProcessorAddress: String,
|
||||
private val paymentAccountRegistryAddress: String,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : VisaContractInfoProvider {
|
||||
|
||||
override suspend fun getContractInfo(walletAddress: String): VisaContractInfo {
|
||||
return parZip(
|
||||
dispatchers.io,
|
||||
{ loadPaymentAccount(walletAddress) },
|
||||
{ loadPaymentTokenInfo() },
|
||||
{ paymentAccount, paymentToken ->
|
||||
fetchBalancesAndLimits(paymentAccount, paymentToken)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadPaymentAccount(walletAddress: String): TangemPaymentAccount {
|
||||
val paymentAccountRegistry = TangemPaymentAccountRegistry.load(
|
||||
/* contractAddress = */ paymentAccountRegistryAddress,
|
||||
/* web3j = */ web3j,
|
||||
/* transactionManager = */ transactionManager,
|
||||
/* contractGasProvider = */ gasProvider,
|
||||
)
|
||||
val paymentAccountAddress = paymentAccountRegistry.paymentAccountByCard(walletAddress).send()
|
||||
|
||||
return TangemPaymentAccount.load(paymentAccountAddress, web3j, transactionManager, gasProvider)
|
||||
}
|
||||
|
||||
private fun loadPaymentTokenInfo(): PaymentTokenInfo {
|
||||
val tangemBridgeProcessor = TangemBridgeProcessor.load(
|
||||
/* contractAddress = */ bridgeProcessorAddress,
|
||||
/* web3j = */ web3j,
|
||||
/* transactionManager = */ transactionManager,
|
||||
/* contractGasProvider = */ gasProvider,
|
||||
)
|
||||
val paymentTokenContractAddress = tangemBridgeProcessor.paymentToken().send()
|
||||
val paymentTokenContract = ERC20.load(paymentTokenContractAddress, web3j, transactionManager, gasProvider)
|
||||
val paymentTokenDecimals = paymentTokenContract.decimals().send()
|
||||
|
||||
return PaymentTokenInfo(
|
||||
decimals = paymentTokenDecimals.toInt(),
|
||||
contract = paymentTokenContract,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun fetchBalancesAndLimits(
|
||||
paymentAccount: TangemPaymentAccount,
|
||||
paymentToken: PaymentTokenInfo,
|
||||
): VisaContractInfo = parZip(
|
||||
dispatchers.io,
|
||||
{ fetchToken(paymentAccount) },
|
||||
{ fetchBalances(paymentAccount, paymentToken) },
|
||||
{ fetchLimits(paymentAccount, paymentToken) },
|
||||
{ token, balances, (oldLimit, newLimit, changeDate) ->
|
||||
VisaContractInfo(token, balances, oldLimit, newLimit, changeDate)
|
||||
},
|
||||
)
|
||||
|
||||
private suspend fun fetchToken(paymentAccount: TangemPaymentAccount): Token {
|
||||
val paymentTokenContractAddress = paymentAccount.paymentToken().send()
|
||||
val paymentTokenContract = ERC20.load(paymentTokenContractAddress, web3j, transactionManager, gasProvider)
|
||||
|
||||
return parZip(
|
||||
dispatchers.io,
|
||||
{ paymentTokenContract.name().send() },
|
||||
{ paymentTokenContract.symbol().send() },
|
||||
{ paymentTokenContract.decimals().send() },
|
||||
) { name, symbol, decimals ->
|
||||
Token(
|
||||
name = name,
|
||||
symbol = symbol,
|
||||
decimals = decimals.toInt(),
|
||||
address = paymentTokenContractAddress,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchBalances(paymentAccount: TangemPaymentAccount, paymentToken: PaymentTokenInfo): Balances {
|
||||
return parZip(
|
||||
dispatchers.io,
|
||||
{ paymentToken.contract.balanceOf(paymentAccount.contractAddress).send() },
|
||||
{ paymentAccount.verifiedBalance().send() },
|
||||
{ paymentAccount.availableForPayment().send() },
|
||||
{ paymentAccount.availableForWithdrawal().send() },
|
||||
{ paymentAccount.availableForDebtPayment().send() },
|
||||
{ paymentAccount.blockedAmount().send() },
|
||||
{ paymentAccount.debtAmount().send() },
|
||||
{ paymentAccount.pendingRefundTotal().send() },
|
||||
) { total, verified, payment, withdrawal, debtPayment, blocked, debt, refund ->
|
||||
val decimals = paymentToken.decimals
|
||||
|
||||
Balances(
|
||||
total = total.toBigDecimal(decimals),
|
||||
verified = verified.toBigDecimal(decimals),
|
||||
available = Balances.Available(
|
||||
forPayment = payment.toBigDecimal(decimals),
|
||||
forWithdrawal = withdrawal.toBigDecimal(decimals),
|
||||
forDebtPayment = debtPayment.toBigDecimal(decimals),
|
||||
),
|
||||
blocked = blocked.toBigDecimal(decimals),
|
||||
debt = debt.toBigDecimal(decimals),
|
||||
pendingRefund = refund.toBigDecimal(decimals),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchLimits(
|
||||
paymentAccount: TangemPaymentAccount,
|
||||
paymentToken: PaymentTokenInfo,
|
||||
): Triple<Limits, Limits, Instant> {
|
||||
val (
|
||||
oldLimit,
|
||||
newLimit,
|
||||
changeDateSeconds,
|
||||
) = paymentAccount.limits().send()
|
||||
|
||||
return Triple(
|
||||
first = getLimits(oldLimit, paymentToken),
|
||||
second = getLimits(newLimit, paymentToken),
|
||||
third = changeDateSeconds.toInstant(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getLimits(limit: TangemPaymentAccount.Limits, paymentToken: PaymentTokenInfo): Limits = Limits(
|
||||
spendLimit = limit._01_spendLimit.toLimit(paymentToken.decimals),
|
||||
noOtpLimit = limit._02_noOtpSpendLimit.toLimit(paymentToken.decimals),
|
||||
singleTransactionLimit = limit._00_singleTransactionLimit.toBigDecimal(paymentToken.decimals),
|
||||
expirationDate = limit._03_spendLimitsTimer.expireTimestamp.toInstant(),
|
||||
spendPeriodSeconds = limit._04_spendLimitsPeriod,
|
||||
)
|
||||
|
||||
private fun TangemPaymentAccount.Limit.toLimit(decimals: Int): Limits.Limit {
|
||||
return Limits.Limit(
|
||||
limit = _00_limit.toBigDecimal(decimals),
|
||||
spent = _01_spent.toBigDecimal(decimals),
|
||||
)
|
||||
}
|
||||
|
||||
private data class PaymentTokenInfo(
|
||||
val decimals: Int,
|
||||
val contract: ERC20,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
package com.tangem.lib.visa
|
||||
|
||||
import com.ihsanbal.logging.Level
|
||||
import com.ihsanbal.logging.LoggingInterceptor
|
||||
import com.tangem.lib.visa.model.VisaContractInfo
|
||||
import com.tangem.lib.visa.utils.Constants
|
||||
import com.tangem.lib.visa.utils.Constants.NETWORK_LOGS_TAG
|
||||
import com.tangem.lib.visa.utils.toHexString
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import okhttp3.OkHttpClient
|
||||
import org.web3j.crypto.Credentials
|
||||
import org.web3j.protocol.Web3j
|
||||
import org.web3j.protocol.http.HttpService
|
||||
import org.web3j.tx.FastRawTransactionManager
|
||||
import org.web3j.tx.TransactionManager
|
||||
import org.web3j.tx.gas.ContractGasProvider
|
||||
import org.web3j.tx.gas.StaticEIP1559GasProvider
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
interface VisaContractInfoProvider {
|
||||
|
||||
suspend fun getContractInfo(walletAddress: String): VisaContractInfo
|
||||
|
||||
class Builder(
|
||||
private val useTestnetRpc: Boolean,
|
||||
private val bridgeProcessorAddress: String,
|
||||
private val paymentAccountRegistryAddress: String,
|
||||
private val isNetworkLoggingEnabled: Boolean,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val chainId: Long = Constants.CHAIN_ID,
|
||||
private val decimals: Int = Constants.DECIMALS,
|
||||
private val gasLimit: Long = Constants.GAS_LIMIT,
|
||||
private val networkTimeoutSeconds: Long = Constants.NETWORK_TIMEOUT_SECONDS,
|
||||
private val privateKey: String = ByteArray(Constants.PRIVATE_KEY_LENGTH).toHexString(),
|
||||
) {
|
||||
|
||||
fun build(): VisaContractInfoProvider {
|
||||
val web3j = createWeb3J()
|
||||
val gasProvider = createGasProvider()
|
||||
val transactionManager = createTransactionManager(web3j)
|
||||
|
||||
return DefaultVisaContractInfoProvider(
|
||||
web3j = web3j,
|
||||
transactionManager = transactionManager,
|
||||
gasProvider = gasProvider,
|
||||
bridgeProcessorAddress = bridgeProcessorAddress,
|
||||
paymentAccountRegistryAddress = paymentAccountRegistryAddress,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
private fun createWeb3J(): Web3j {
|
||||
val baseUrl: String = if (useTestnetRpc) Constants.TESTNET_RPC_URL else Constants.MAINNET_RPC_URL
|
||||
|
||||
val httpClient = OkHttpClient.Builder().apply {
|
||||
connectTimeout(networkTimeoutSeconds, TimeUnit.SECONDS)
|
||||
readTimeout(networkTimeoutSeconds, TimeUnit.SECONDS)
|
||||
writeTimeout(networkTimeoutSeconds, TimeUnit.SECONDS)
|
||||
|
||||
if (isNetworkLoggingEnabled) {
|
||||
addInterceptor(
|
||||
LoggingInterceptor.Builder()
|
||||
.setLevel(Level.BODY)
|
||||
.tag(NETWORK_LOGS_TAG)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
}.build()
|
||||
|
||||
val web3jService = HttpService(
|
||||
/* url = */ baseUrl,
|
||||
/* httpClient = */ httpClient,
|
||||
/* includeRawResponses = */ false,
|
||||
)
|
||||
|
||||
return Web3j.build(web3jService)
|
||||
}
|
||||
|
||||
private fun createGasProvider(): ContractGasProvider = StaticEIP1559GasProvider(
|
||||
/* chainId = */ chainId,
|
||||
/* maxFeePerGas = */ BigDecimal.ONE.movePointLeft(decimals).toBigInteger(),
|
||||
/* maxPriorityFeePerGas = */ BigDecimal.ONE.movePointLeft(decimals).toBigInteger(),
|
||||
/* gasLimit = */ BigInteger.valueOf(gasLimit),
|
||||
)
|
||||
|
||||
private fun createTransactionManager(web3j: Web3j): TransactionManager = FastRawTransactionManager(
|
||||
/* web3j = */ web3j,
|
||||
/* credentials = */ Credentials.create(privateKey),
|
||||
/* chainId = */ chainId,
|
||||
)
|
||||
}
|
||||
}
|
||||
18
libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApi.kt
Normal file
18
libs/visa/src/main/kotlin/com/tangem/lib/visa/api/VisaApi.kt
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.lib.visa.api
|
||||
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.lib.visa.model.VisaTxHistoryResponse
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Header
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface VisaApi {
|
||||
|
||||
@GET("transaction")
|
||||
suspend fun getTxHistory(
|
||||
@Header("Authorization") authorizationHeader: String,
|
||||
@Query("card_public_key") cardPublicKey: String,
|
||||
@Query("limit") limit: Int,
|
||||
@Query("offset") offset: Int,
|
||||
): ApiResponse<VisaTxHistoryResponse>
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package com.tangem.lib.visa.api
|
||||
|
||||
import android.util.Log
|
||||
import com.ihsanbal.logging.Level
|
||||
import com.ihsanbal.logging.LoggingInterceptor
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.api.common.response.ApiResponseCallAdapterFactory
|
||||
import com.tangem.lib.visa.utils.Constants
|
||||
import com.tangem.lib.visa.utils.Constants.NETWORK_LOGS_TAG
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class VisaApiBuilder(
|
||||
private val useDevApi: Boolean,
|
||||
private val isNetworkLoggingEnabled: Boolean,
|
||||
private val moshi: Moshi,
|
||||
private val headers: Map<String, String>,
|
||||
private val networkTimeoutSeconds: Long = Constants.NETWORK_TIMEOUT_SECONDS,
|
||||
) {
|
||||
|
||||
fun build(): VisaApi {
|
||||
val okHttpClient = createOkHttpClient()
|
||||
val retrofit = createRetrofit(okHttpClient)
|
||||
return retrofit.create(VisaApi::class.java)
|
||||
}
|
||||
|
||||
private fun createOkHttpClient(): OkHttpClient {
|
||||
val builder = OkHttpClient.Builder().apply {
|
||||
connectTimeout(networkTimeoutSeconds, TimeUnit.SECONDS)
|
||||
readTimeout(networkTimeoutSeconds, TimeUnit.SECONDS)
|
||||
writeTimeout(networkTimeoutSeconds, TimeUnit.SECONDS)
|
||||
|
||||
if (isNetworkLoggingEnabled) {
|
||||
addInterceptor(createNetworkLoggingInterceptor())
|
||||
}
|
||||
|
||||
if (headers.isNotEmpty()) {
|
||||
addInterceptor { chain ->
|
||||
val request = chain.request().newBuilder().apply {
|
||||
headers.forEach { (key, value) -> addHeader(key, value) }
|
||||
}.build()
|
||||
|
||||
chain.proceed(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build()
|
||||
}
|
||||
|
||||
private fun createRetrofit(okHttpClient: OkHttpClient): Retrofit {
|
||||
val baseUrl = if (useDevApi) Constants.VISA_API_DEV_URL else Constants.VISA_API_PROD_URL
|
||||
|
||||
return Retrofit.Builder()
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.addCallAdapterFactory(ApiResponseCallAdapterFactory.create())
|
||||
.baseUrl(baseUrl)
|
||||
.client(okHttpClient)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createNetworkLoggingInterceptor(): Interceptor {
|
||||
return LoggingInterceptor.Builder()
|
||||
.setLevel(Level.BODY)
|
||||
.log(Log.VERBOSE)
|
||||
.tag(NETWORK_LOGS_TAG)
|
||||
.build()
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package com.tangem.lib.visa.model
|
||||
|
||||
import org.joda.time.Instant
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
|
||||
data class VisaContractInfo(
|
||||
val token: Token,
|
||||
val balances: Balances,
|
||||
val oldLimits: Limits,
|
||||
val newLimits: Limits,
|
||||
val limitsChangeDate: Instant,
|
||||
) {
|
||||
|
||||
data class Token(
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val decimals: Int,
|
||||
val address: String,
|
||||
)
|
||||
|
||||
data class Balances(
|
||||
val total: BigDecimal,
|
||||
val verified: BigDecimal,
|
||||
val available: Available,
|
||||
val blocked: BigDecimal,
|
||||
val debt: BigDecimal,
|
||||
val pendingRefund: BigDecimal,
|
||||
) {
|
||||
|
||||
data class Available(
|
||||
val forPayment: BigDecimal,
|
||||
val forWithdrawal: BigDecimal,
|
||||
val forDebtPayment: BigDecimal,
|
||||
)
|
||||
}
|
||||
|
||||
data class Limits(
|
||||
val spendLimit: Limit,
|
||||
val noOtpLimit: Limit,
|
||||
val singleTransactionLimit: BigDecimal,
|
||||
val expirationDate: Instant,
|
||||
val spendPeriodSeconds: BigInteger,
|
||||
) {
|
||||
|
||||
data class Limit(
|
||||
val limit: BigDecimal,
|
||||
val spent: BigDecimal,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package com.tangem.lib.visa.model
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import org.joda.time.DateTime
|
||||
import java.math.BigDecimal
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class VisaTxHistoryResponse(
|
||||
@Json(name = "card_wallet_address")
|
||||
val cardWalletAddress: String,
|
||||
@Json(name = "transactions")
|
||||
val transactions: List<Transaction>,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Transaction(
|
||||
@Json(name = "auth_code")
|
||||
val authCode: String?,
|
||||
@Json(name = "billing_amount")
|
||||
val billingAmount: BigDecimal,
|
||||
@Json(name = "billing_currency_code")
|
||||
val billingCurrencyCode: Int,
|
||||
@Json(name = "blockchain_amount")
|
||||
val blockchainAmount: BigDecimal,
|
||||
@Json(name = "blockchain_coin_name")
|
||||
val blockchainCoinName: String,
|
||||
@Json(name = "blockchain_fee")
|
||||
val blockchainFee: BigDecimal,
|
||||
// @Json(name = "local_dt")
|
||||
// val localDate: DateTime?,
|
||||
@Json(name = "merchant_category_code")
|
||||
val merchantCategoryCode: String?,
|
||||
@Json(name = "merchant_city")
|
||||
val merchantCity: String?,
|
||||
@Json(name = "merchant_country_code")
|
||||
val merchantCountryCode: String?,
|
||||
@Json(name = "merchant_name")
|
||||
val merchantName: String?,
|
||||
@Json(name = "requests")
|
||||
val requests: List<Request>,
|
||||
@Json(name = "rrn")
|
||||
val rrn: String?,
|
||||
@Json(name = "transaction_amount")
|
||||
val transactionAmount: BigDecimal,
|
||||
@Json(name = "transaction_currency_code")
|
||||
val transactionCurrencyCode: Int,
|
||||
@Json(name = "transaction_dt")
|
||||
val transactionDt: DateTime,
|
||||
@Json(name = "transaction_id")
|
||||
val transactionId: Long,
|
||||
@Json(name = "transaction_status")
|
||||
val transactionStatus: String,
|
||||
@Json(name = "transaction_type")
|
||||
val transactionType: String,
|
||||
) {
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class Request(
|
||||
@Json(name = "billing_amount")
|
||||
val billingAmount: BigDecimal,
|
||||
@Json(name = "billing_currency_code")
|
||||
val billingCurrencyCode: Int,
|
||||
@Json(name = "blockchain_amount")
|
||||
val blockchainAmount: BigDecimal,
|
||||
@Json(name = "blockchain_fee")
|
||||
val blockchainFee: BigDecimal,
|
||||
@Json(name = "error_code")
|
||||
val errorCode: Int,
|
||||
@Json(name = "request_dt")
|
||||
val requestDt: DateTime,
|
||||
@Json(name = "request_status")
|
||||
val requestStatus: String,
|
||||
@Json(name = "request_type")
|
||||
val requestType: String,
|
||||
@Json(name = "transaction_amount")
|
||||
val transactionAmount: BigDecimal,
|
||||
@Json(name = "transaction_currency_code")
|
||||
val transactionCurrencyCode: Int,
|
||||
@Json(name = "transaction_request_id")
|
||||
val transactionRequestId: Long,
|
||||
@Json(name = "tx_hash")
|
||||
val txHash: String?,
|
||||
@Json(name = "tx_status")
|
||||
val txStatus: String?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.lib.visa.utils
|
||||
|
||||
import org.joda.time.Instant
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
|
||||
internal fun BigInteger.toBigDecimal(decimals: Int): BigDecimal {
|
||||
return this.toBigDecimal().movePointLeft(decimals)
|
||||
}
|
||||
|
||||
internal fun BigInteger.toInstant(): Instant {
|
||||
return Instant.ofEpochSecond(toLong())
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.tangem.lib.visa.utils
|
||||
|
||||
internal fun ByteArray.toHexString(): String = joinToString("") { "%02X".format(it) }
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.lib.visa.utils
|
||||
|
||||
internal object Constants {
|
||||
|
||||
const val MAINNET_RPC_URL = "https://polygon-rpc.com/"
|
||||
const val TESTNET_RPC_URL = "https://rpc-amoy.polygon.technology/"
|
||||
|
||||
const val CHAIN_ID = 80_001L
|
||||
const val DECIMALS = 9
|
||||
const val GAS_LIMIT = 500_000_000L
|
||||
const val PRIVATE_KEY_LENGTH = 32
|
||||
|
||||
const val VISA_API_PROD_URL = "https://payapi.tangem-tech.com/api/v1/"
|
||||
const val VISA_API_DEV_URL = "[REDACTED_ENV_URL]"
|
||||
|
||||
const val NETWORK_TIMEOUT_SECONDS = 65L
|
||||
const val NETWORK_LOGS_TAG = "VisaNetworkLogs"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue