Updated on 2026-08-14
This commit is contained in:
commit
438126dc82
665 changed files with 16174 additions and 6245 deletions
|
|
@ -70,8 +70,4 @@ internal class DefaultCardSdkConfigRepository(
|
|||
override fun setLinkedTerminal(isLinked: Boolean?) {
|
||||
sdk.config.linkedTerminal = isLinked
|
||||
}
|
||||
|
||||
override fun setTangemApiProdEnvFlag(flag: Boolean) {
|
||||
sdk.config.isTangemAttestationProdEnv = flag
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package com.tangem.data.common.currency
|
|||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
|
|
@ -20,7 +19,18 @@ interface CardCryptoCurrencyFactory {
|
|||
* @param network network
|
||||
*/
|
||||
@Throws
|
||||
suspend fun create(userWalletId: UserWalletId, network: Network): List<CryptoCurrency>
|
||||
suspend fun create(userWalletId: UserWalletId, network: Network): List<CryptoCurrency> {
|
||||
return create(userWalletId = userWalletId, networks = setOf(network))[network].orEmpty()
|
||||
}
|
||||
|
||||
/**
|
||||
* Universal method for creating list of [CryptoCurrency] in [networks] for any card
|
||||
*
|
||||
* @param userWalletId user wallet id that determines type of card
|
||||
* @param networks networks
|
||||
*/
|
||||
@Throws
|
||||
suspend fun create(userWalletId: UserWalletId, networks: Set<Network>): Map<Network, List<CryptoCurrency>>
|
||||
|
||||
/**
|
||||
* Universal method for creating list of [CryptoCurrency] in [network] for any card
|
||||
|
|
@ -46,23 +56,23 @@ interface CardCryptoCurrencyFactory {
|
|||
/**
|
||||
* Create default coins for multi currency card
|
||||
*
|
||||
* @param scanResponse scan response
|
||||
* @param userWallet user wallet
|
||||
*/
|
||||
fun createDefaultCoinsForMultiCurrencyCard(scanResponse: ScanResponse): List<CryptoCurrency.Coin>
|
||||
fun createDefaultCoinsForMultiCurrencyWallet(userWallet: UserWallet): List<CryptoCurrency.Coin>
|
||||
|
||||
/**
|
||||
* Create primary currency for single currency card
|
||||
*
|
||||
* @param scanResponse scan response
|
||||
* @param userWallet user wallet
|
||||
*/
|
||||
@Throws
|
||||
fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency
|
||||
fun createPrimaryCurrencyForSingleCurrencyCard(userWallet: UserWallet.Cold): CryptoCurrency
|
||||
|
||||
/**
|
||||
* Create currencies for single currency card with token (like, NODL)
|
||||
*
|
||||
* @param scanResponse scan response
|
||||
* @param userWallet user wallet
|
||||
*/
|
||||
@Throws
|
||||
fun createCurrenciesForSingleCurrencyCardWithToken(scanResponse: ScanResponse): List<CryptoCurrency>
|
||||
fun createCurrenciesForSingleCurrencyCardWithToken(userWallet: UserWallet.Cold): List<CryptoCurrency>
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import com.tangem.blockchainsdk.utils.toCoinId
|
|||
import com.tangem.data.common.network.NetworkFactory
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import timber.log.Timber
|
||||
import com.tangem.blockchain.common.Token as SdkToken
|
||||
|
||||
|
|
@ -46,7 +46,7 @@ class CryptoCurrencyFactory(
|
|||
sdkToken: SdkToken,
|
||||
blockchain: Blockchain,
|
||||
extraDerivationPath: String?,
|
||||
scanResponse: ScanResponse,
|
||||
userWallet: UserWallet,
|
||||
): CryptoCurrency.Token? {
|
||||
if (blockchain == Blockchain.Unknown) {
|
||||
Timber.e("Unable to map the SDK token to the domain token with Unknown blockchain")
|
||||
|
|
@ -56,7 +56,7 @@ class CryptoCurrencyFactory(
|
|||
val network = networkFactory.create(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = extraDerivationPath,
|
||||
scanResponse = scanResponse,
|
||||
userWallet = userWallet,
|
||||
) ?: return null
|
||||
|
||||
val id = getTokenId(network, sdkToken)
|
||||
|
|
@ -76,7 +76,7 @@ class CryptoCurrencyFactory(
|
|||
fun createCoin(
|
||||
blockchain: Blockchain,
|
||||
extraDerivationPath: String?,
|
||||
scanResponse: ScanResponse,
|
||||
userWallet: UserWallet,
|
||||
): CryptoCurrency.Coin? {
|
||||
if (blockchain == Blockchain.Unknown) {
|
||||
Timber.e("Unable to map the SDK token to the domain token with Unknown blockchain")
|
||||
|
|
@ -86,15 +86,15 @@ class CryptoCurrencyFactory(
|
|||
val network = networkFactory.create(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = extraDerivationPath,
|
||||
scanResponse = scanResponse,
|
||||
userWallet = userWallet,
|
||||
) ?: return null
|
||||
|
||||
return createCoin(network)
|
||||
}
|
||||
|
||||
fun createCoin(networkId: String, extraDerivationPath: String?, scanResponse: ScanResponse): CryptoCurrency.Coin? {
|
||||
fun createCoin(networkId: String, extraDerivationPath: String?, userWallet: UserWallet): CryptoCurrency.Coin? {
|
||||
val blockchain = Blockchain.fromNetworkId(networkId) ?: Blockchain.Unknown
|
||||
return createCoin(blockchain, extraDerivationPath, scanResponse)
|
||||
return createCoin(blockchain, extraDerivationPath, userWallet)
|
||||
}
|
||||
|
||||
fun createCoin(network: Network): CryptoCurrency.Coin {
|
||||
|
|
@ -115,7 +115,7 @@ class CryptoCurrencyFactory(
|
|||
token: Token,
|
||||
networkId: String,
|
||||
extraDerivationPath: String?,
|
||||
scanResponse: ScanResponse,
|
||||
userWallet: UserWallet,
|
||||
): CryptoCurrency.Token? {
|
||||
val sdkToken = SdkToken(
|
||||
name = token.name,
|
||||
|
|
@ -129,7 +129,7 @@ class CryptoCurrencyFactory(
|
|||
sdkToken = sdkToken,
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = extraDerivationPath,
|
||||
scanResponse = scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +141,8 @@ class CryptoCurrencyFactory(
|
|||
decimals = cryptoCurrency.decimals,
|
||||
id = cryptoCurrency.id.rawCurrencyId?.value,
|
||||
)
|
||||
val blockchain = Blockchain.fromNetworkId(cryptoCurrency.network.backendId) ?: Blockchain.Unknown
|
||||
val blockchain =
|
||||
Blockchain.fromNetworkId(cryptoCurrency.network.backendId) ?: Blockchain.Unknown
|
||||
val id = getTokenId(network, sdkToken)
|
||||
return CryptoCurrency.Token(
|
||||
id = id,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.data.common.currency
|
|||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
|
|
@ -12,11 +11,9 @@ import com.tangem.domain.common.util.cardTypesResolver
|
|||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.isMultiCurrency
|
||||
import com.tangem.domain.wallets.models.requireColdWallet
|
||||
|
||||
/**
|
||||
* Default implementation of factory for creating list of [CryptoCurrency] for selected card
|
||||
|
|
@ -36,40 +33,45 @@ internal class DefaultCardCryptoCurrencyFactory(
|
|||
|
||||
private val cryptoCurrencyFactory by lazy { CryptoCurrencyFactory(excludedBlockchains) }
|
||||
|
||||
override suspend fun create(userWalletId: UserWalletId, network: Network): List<CryptoCurrency> {
|
||||
override suspend fun create(
|
||||
userWalletId: UserWalletId,
|
||||
networks: Set<Network>,
|
||||
): Map<Network, List<CryptoCurrency>> {
|
||||
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
|
||||
|
||||
val blockchain = Blockchain.fromNetworkId(networkId = network.backendId)
|
||||
|
||||
// multi-currency wallet
|
||||
if (userWallet !is UserWallet.Cold || userWallet.isMultiCurrency) {
|
||||
return getMultiWalletCurrencies(userWallet = userWallet, networks = setOf(network))[network].orEmpty()
|
||||
return getMultiWalletCurrencies(userWallet = userWallet, networks = networks)
|
||||
}
|
||||
|
||||
// check if the blockchain of single-currency wallet is the same as network
|
||||
val cardBlockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain()
|
||||
if (cardBlockchain != blockchain) return emptyList()
|
||||
val cardNetworkId = userWallet.scanResponse.cardTypesResolver.getBlockchain().toNetworkId()
|
||||
val cardNetwork = networks.firstOrNull { it.backendId == cardNetworkId }
|
||||
|
||||
if (cardNetwork == null) return emptyMap()
|
||||
|
||||
// single-currency wallet with token (NODL)
|
||||
if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
return createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse)
|
||||
val currencies = createCurrenciesForSingleCurrencyCardWithToken(userWallet)
|
||||
return mapOf(cardNetwork to currencies)
|
||||
}
|
||||
|
||||
// single-currency wallet
|
||||
return createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse).let(::listOf)
|
||||
val primaryCurrency = createPrimaryCurrencyForSingleCurrencyCard(userWallet)
|
||||
return mapOf(cardNetwork to listOf(primaryCurrency))
|
||||
}
|
||||
|
||||
override suspend fun createByRawId(userWalletId: UserWalletId, networkRawId: Network.RawID): List<CryptoCurrency> {
|
||||
override suspend fun createByRawId(userWalletId: UserWalletId, network: Network.RawID): List<CryptoCurrency> {
|
||||
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
|
||||
|
||||
val blockchain = networkRawId.toBlockchain()
|
||||
val blockchain = network.toBlockchain()
|
||||
|
||||
// multi-currency wallet
|
||||
if (userWallet.isMultiCurrency || userWallet !is UserWallet.Cold) {
|
||||
return getMultiWalletCurrenciesByRawId(
|
||||
userWallet = userWallet,
|
||||
rawIds = setOf(networkRawId),
|
||||
)[networkRawId].orEmpty()
|
||||
rawIds = setOf(network),
|
||||
)[network].orEmpty()
|
||||
}
|
||||
|
||||
// check if the blockchain of single-currency wallet is the same as network
|
||||
|
|
@ -78,11 +80,11 @@ internal class DefaultCardCryptoCurrencyFactory(
|
|||
|
||||
// single-currency wallet with token (NODL)
|
||||
if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
return createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse)
|
||||
return createCurrenciesForSingleCurrencyCardWithToken(userWallet)
|
||||
}
|
||||
|
||||
// single-currency wallet
|
||||
return createPrimaryCurrencyForSingleCurrencyCard(userWallet.scanResponse).let(::listOf)
|
||||
return createPrimaryCurrencyForSingleCurrencyCard(userWallet).let(::listOf)
|
||||
}
|
||||
|
||||
override suspend fun createCurrenciesForMultiCurrencyCard(
|
||||
|
|
@ -94,44 +96,54 @@ internal class DefaultCardCryptoCurrencyFactory(
|
|||
return getMultiWalletCurrencies(userWallet = userWallet, networks = networks)
|
||||
}
|
||||
|
||||
override fun createDefaultCoinsForMultiCurrencyCard(scanResponse: ScanResponse): List<CryptoCurrency.Coin> {
|
||||
require(scanResponse.cardTypesResolver.isMultiwalletAllowed()) { "It isn't multi-currency wallet" }
|
||||
override fun createDefaultCoinsForMultiCurrencyWallet(userWallet: UserWallet): List<CryptoCurrency.Coin> {
|
||||
require(userWallet.isMultiCurrency) { "It isn't multi-currency wallet" }
|
||||
|
||||
val card = scanResponse.card
|
||||
val blockchains = when (userWallet) {
|
||||
is UserWallet.Cold -> {
|
||||
val card = userWallet.scanResponse.card
|
||||
|
||||
var blockchains = if (demoConfig.isDemoCardId(card.cardId)) {
|
||||
demoConfig.demoBlockchains
|
||||
} else {
|
||||
listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
}
|
||||
var blockchainsInternal = if (demoConfig.isDemoCardId(card.cardId)) {
|
||||
demoConfig.demoBlockchains
|
||||
} else {
|
||||
listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
}
|
||||
|
||||
if (card.isTestCard) {
|
||||
blockchains = blockchains.mapNotNull { it.getTestnetVersion() }
|
||||
if (card.isTestCard) {
|
||||
blockchainsInternal = blockchainsInternal.mapNotNull { it.getTestnetVersion() }
|
||||
}
|
||||
|
||||
blockchainsInternal
|
||||
}
|
||||
|
||||
is UserWallet.Hot -> listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
}
|
||||
|
||||
return blockchains.mapNotNull {
|
||||
cryptoCurrencyFactory.createCoin(
|
||||
blockchain = it,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun createPrimaryCurrencyForSingleCurrencyCard(scanResponse: ScanResponse): CryptoCurrency {
|
||||
require(scanResponse.cardTypesResolver.isSingleWallet()) { "It isn't single-currency wallet" }
|
||||
override fun createPrimaryCurrencyForSingleCurrencyCard(userWallet: UserWallet.Cold): CryptoCurrency {
|
||||
require(userWallet.scanResponse.cardTypesResolver.isSingleWallet()) {
|
||||
"It isn't single-currency wallet"
|
||||
}
|
||||
|
||||
return with(getSingleWalletCurrencies(scanResponse)) {
|
||||
return with(getSingleWalletCurrencies(userWallet)) {
|
||||
primaryToken ?: coin
|
||||
}
|
||||
}
|
||||
|
||||
override fun createCurrenciesForSingleCurrencyCardWithToken(scanResponse: ScanResponse): List<CryptoCurrency> {
|
||||
require(scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
override fun createCurrenciesForSingleCurrencyCardWithToken(userWallet: UserWallet.Cold): List<CryptoCurrency> {
|
||||
require(userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
"It isn't single-currency wallet with token"
|
||||
}
|
||||
|
||||
return with(getSingleWalletCurrencies(scanResponse)) {
|
||||
return with(getSingleWalletCurrencies(userWallet)) {
|
||||
listOfNotNull(coin, primaryToken)
|
||||
}
|
||||
}
|
||||
|
|
@ -143,13 +155,15 @@ internal class DefaultCardCryptoCurrencyFactory(
|
|||
val response = userTokensResponseStore.getSyncOrNull(userWalletId = userWallet.walletId)
|
||||
?: return emptyMap()
|
||||
|
||||
return responseCryptoCurrenciesFactory.createCurrencies(
|
||||
val existingNetworkWithCurrencies = responseCryptoCurrenciesFactory.createCurrencies(
|
||||
tokens = response.tokens.filter { token ->
|
||||
networks.any { it.backendId == token.networkId && it.derivationPath.value == token.derivationPath }
|
||||
},
|
||||
scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY]
|
||||
userWallet = userWallet,
|
||||
)
|
||||
.groupBy(CryptoCurrency::network)
|
||||
|
||||
return networks.associateWith { emptyList<CryptoCurrency>() } + existingNetworkWithCurrencies
|
||||
}
|
||||
|
||||
private suspend fun getMultiWalletCurrenciesByRawId(
|
||||
|
|
@ -163,19 +177,19 @@ internal class DefaultCardCryptoCurrencyFactory(
|
|||
|
||||
return responseCryptoCurrenciesFactory.createCurrencies(
|
||||
tokens = response.tokens.filter { token -> token.networkId in networkIds },
|
||||
scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY]
|
||||
userWallet = userWallet,
|
||||
)
|
||||
.groupBy { it.network.id.rawId }
|
||||
}
|
||||
|
||||
private fun getSingleWalletCurrencies(scanResponse: ScanResponse): SingleWalletCurrencies {
|
||||
val resolver = scanResponse.cardTypesResolver
|
||||
private fun getSingleWalletCurrencies(userWallet: UserWallet.Cold): SingleWalletCurrencies {
|
||||
val resolver = userWallet.cardTypesResolver
|
||||
val blockchain = resolver.getBlockchain()
|
||||
|
||||
val coin = cryptoCurrencyFactory.createCoin(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
|
||||
requireNotNull(coin) { "Coin for the single currency card cannot be null" }
|
||||
|
|
@ -185,12 +199,15 @@ internal class DefaultCardCryptoCurrencyFactory(
|
|||
sdkToken = token,
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
|
||||
return SingleWalletCurrencies(coin = coin, primaryToken = primaryToken)
|
||||
}
|
||||
|
||||
private data class SingleWalletCurrencies(val coin: CryptoCurrency, val primaryToken: CryptoCurrency?)
|
||||
private data class SingleWalletCurrencies(
|
||||
val coin: CryptoCurrency,
|
||||
val primaryToken: CryptoCurrency?,
|
||||
)
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ import com.tangem.data.common.network.NetworkFactory
|
|||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import com.tangem.blockchain.common.Token as SdkToken
|
||||
|
|
@ -17,41 +17,41 @@ class ResponseCryptoCurrenciesFactory @Inject constructor(
|
|||
private val networkFactory: NetworkFactory,
|
||||
) {
|
||||
|
||||
fun createCurrency(currencyId: String, response: UserTokensResponse, scanResponse: ScanResponse): CryptoCurrency {
|
||||
fun createCurrency(currencyId: String, response: UserTokensResponse, userWallet: UserWallet): CryptoCurrency {
|
||||
return response.tokens
|
||||
.asSequence()
|
||||
.mapNotNull { createCurrency(it, scanResponse) }
|
||||
.mapNotNull { createCurrency(it, userWallet) }
|
||||
.first { it.id.value == currencyId }
|
||||
}
|
||||
|
||||
fun createCurrencies(response: UserTokensResponse, scanResponse: ScanResponse): List<CryptoCurrency> {
|
||||
return createCurrencies(tokens = response.tokens, scanResponse = scanResponse)
|
||||
fun createCurrencies(response: UserTokensResponse, userWallet: UserWallet): List<CryptoCurrency> {
|
||||
return createCurrencies(tokens = response.tokens, userWallet = userWallet)
|
||||
}
|
||||
|
||||
fun createCurrencies(tokens: List<UserTokensResponse.Token>, scanResponse: ScanResponse): List<CryptoCurrency> {
|
||||
fun createCurrencies(tokens: List<UserTokensResponse.Token>, userWallet: UserWallet): List<CryptoCurrency> {
|
||||
return tokens
|
||||
.asSequence()
|
||||
.mapNotNull { createCurrency(it, scanResponse) }
|
||||
.mapNotNull { createCurrency(it, userWallet) }
|
||||
.distinctBy(CryptoCurrency::id)
|
||||
.toList()
|
||||
}
|
||||
|
||||
fun createCurrency(responseToken: UserTokensResponse.Token, scanResponse: ScanResponse): CryptoCurrency? {
|
||||
fun createCurrency(responseToken: UserTokensResponse.Token, userWallet: UserWallet): CryptoCurrency? {
|
||||
var blockchain = Blockchain.fromNetworkId(responseToken.networkId)
|
||||
if (blockchain == null || blockchain == Blockchain.Unknown) {
|
||||
Timber.e("Unable to find a blockchain with the network ID: ${responseToken.networkId}")
|
||||
return null
|
||||
}
|
||||
|
||||
if (scanResponse.cardTypesResolver.isTestCard()) {
|
||||
if (userWallet is UserWallet.Cold && userWallet.scanResponse.cardTypesResolver.isTestCard()) {
|
||||
blockchain = blockchain.getTestnetVersion() ?: blockchain
|
||||
}
|
||||
|
||||
val sdkToken = createSdkToken(responseToken)
|
||||
return if (sdkToken == null) {
|
||||
createCoin(blockchain, responseToken, scanResponse)
|
||||
createCoin(blockchain, responseToken, userWallet)
|
||||
} else {
|
||||
createToken(blockchain, sdkToken, responseToken.derivationPath, scanResponse)
|
||||
createToken(blockchain, sdkToken, responseToken.derivationPath, userWallet)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -70,12 +70,12 @@ class ResponseCryptoCurrenciesFactory @Inject constructor(
|
|||
private fun createCoin(
|
||||
blockchain: Blockchain,
|
||||
responseToken: UserTokensResponse.Token,
|
||||
scanResponse: ScanResponse,
|
||||
userWallet: UserWallet,
|
||||
): CryptoCurrency.Coin? {
|
||||
val network = networkFactory.create(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = responseToken.derivationPath,
|
||||
scanResponse = scanResponse,
|
||||
userWallet = userWallet,
|
||||
) ?: return null
|
||||
|
||||
return CryptoCurrency.Coin(
|
||||
|
|
@ -105,12 +105,12 @@ class ResponseCryptoCurrenciesFactory @Inject constructor(
|
|||
blockchain: Blockchain,
|
||||
sdkToken: Token,
|
||||
responseDerivationPath: String?,
|
||||
scanResponse: ScanResponse,
|
||||
userWallet: UserWallet,
|
||||
): CryptoCurrency.Token? {
|
||||
val network = networkFactory.create(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = responseDerivationPath,
|
||||
scanResponse = scanResponse,
|
||||
userWallet = userWallet,
|
||||
) ?: return null
|
||||
|
||||
val id = getTokenId(network, sdkToken)
|
||||
|
|
|
|||
|
|
@ -8,10 +8,9 @@ import com.tangem.blockchainsdk.utils.toBlockchain
|
|||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.domain.common.DerivationStyleProvider
|
||||
import com.tangem.domain.common.extensions.canHandleToken
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -31,19 +30,18 @@ class NetworkFactory @Inject constructor(
|
|||
*
|
||||
* @param blockchain blockchain
|
||||
* @param extraDerivationPath extra derivation path
|
||||
* @param scanResponse scan response
|
||||
* @param userWallet user wallet
|
||||
*/
|
||||
fun create(blockchain: Blockchain, extraDerivationPath: String?, scanResponse: ScanResponse): Network? {
|
||||
fun create(blockchain: Blockchain, extraDerivationPath: String?, userWallet: UserWallet): Network? {
|
||||
return create(
|
||||
blockchain = blockchain,
|
||||
derivationPath = createDerivationPath(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = extraDerivationPath,
|
||||
cardDerivationStyleProvider = scanResponse.derivationStyleProvider,
|
||||
cardDerivationStyleProvider = userWallet.derivationStyleProvider,
|
||||
),
|
||||
canHandleTokens = scanResponse.card.canHandleToken(
|
||||
canHandleTokens = userWallet.canHandleToken(
|
||||
blockchain = blockchain,
|
||||
cardTypesResolver = scanResponse.cardTypesResolver,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
),
|
||||
)
|
||||
|
|
@ -54,17 +52,16 @@ class NetworkFactory @Inject constructor(
|
|||
*
|
||||
* @param networkId network id
|
||||
* @param derivationPath derivation path
|
||||
* @param scanResponse scan response
|
||||
* @param userWallet user wallet
|
||||
*/
|
||||
fun create(networkId: Network.ID, derivationPath: Network.DerivationPath, scanResponse: ScanResponse): Network? {
|
||||
fun create(networkId: Network.ID, derivationPath: Network.DerivationPath, userWallet: UserWallet): Network? {
|
||||
val blockchain = networkId.toBlockchain()
|
||||
|
||||
return create(
|
||||
blockchain = blockchain,
|
||||
derivationPath = derivationPath,
|
||||
canHandleTokens = scanResponse.card.canHandleToken(
|
||||
canHandleTokens = userWallet.canHandleToken(
|
||||
blockchain = blockchain,
|
||||
cardTypesResolver = scanResponse.cardTypesResolver,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -266,7 +266,9 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
|
|||
CreateCurrenciesForMultiWalletModel(
|
||||
multiWallet = createMultiWallet(),
|
||||
userTokensResponse = createUserTokensResponse(),
|
||||
expected = Result.success(emptyMap()),
|
||||
expected = Result.success(
|
||||
setOf(ethereum.network, bitcoin.network).associateWith { emptyList() },
|
||||
),
|
||||
),
|
||||
CreateCurrenciesForMultiWalletModel(
|
||||
multiWallet = createMultiWallet(),
|
||||
|
|
@ -306,7 +308,7 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
|
|||
val multiWallet = model.multiWallet
|
||||
|
||||
// Act
|
||||
val actual = factory.createDefaultCoinsForMultiCurrencyCard(scanResponse = multiWallet.scanResponse)
|
||||
val actual = factory.createDefaultCoinsForMultiCurrencyWallet(multiWallet)
|
||||
|
||||
// Assert
|
||||
val expected = model.expected
|
||||
|
|
@ -353,7 +355,7 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
|
|||
|
||||
// Act
|
||||
val actual = runCatching {
|
||||
factory.createPrimaryCurrencyForSingleCurrencyCard(scanResponse = singleWallet.scanResponse)
|
||||
factory.createPrimaryCurrencyForSingleCurrencyCard(singleWallet)
|
||||
}
|
||||
|
||||
// Assert
|
||||
|
|
@ -403,7 +405,7 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
|
|||
|
||||
// Act
|
||||
val actual = runCatching {
|
||||
factory.createCurrenciesForSingleCurrencyCardWithToken(scanResponse = userWallet.scanResponse)
|
||||
factory.createCurrenciesForSingleCurrencyCardWithToken(userWallet)
|
||||
}
|
||||
|
||||
// Assert
|
||||
|
|
@ -552,7 +554,7 @@ internal class DefaultCardCryptoCurrencyFactoryTest {
|
|||
sdkToken = userWallet.scanResponse.cardTypesResolver.getPrimaryToken()!!,
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)!!
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.common.test.domain.card.MockScanResponseFactory
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.common.test.domain.wallet.MockUserWalletFactory
|
||||
import com.tangem.common.test.utils.ProvideTestModels
|
||||
import com.tangem.domain.common.DerivationStyleProvider
|
||||
import com.tangem.domain.common.configs.GenericCardConfig
|
||||
|
|
@ -12,6 +13,7 @@ import com.tangem.domain.common.configs.MultiWalletCardConfig
|
|||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.jupiter.api.Nested
|
||||
|
|
@ -43,7 +45,7 @@ class NetworkFactoryTest {
|
|||
val actual = networkFactory.create(
|
||||
blockchain = Blockchain.Ethereum,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = createMultiWalletScanResponse(),
|
||||
userWallet = createUserWallet(),
|
||||
)
|
||||
|
||||
// Assert
|
||||
|
|
@ -59,14 +61,14 @@ class NetworkFactoryTest {
|
|||
networkFactory.create(
|
||||
blockchain = model.blockchain,
|
||||
extraDerivationPath = model.extraDerivationPath,
|
||||
scanResponse = model.scanResponse,
|
||||
userWallet = model.userWallet,
|
||||
)
|
||||
}
|
||||
is CreateTestModel.Second -> {
|
||||
networkFactory.create(
|
||||
networkId = model.networkId,
|
||||
derivationPath = model.derivationPath,
|
||||
scanResponse = model.scanResponse,
|
||||
userWallet = model.userWallet,
|
||||
)
|
||||
}
|
||||
is CreateTestModel.Third -> {
|
||||
|
|
@ -89,27 +91,27 @@ class NetworkFactoryTest {
|
|||
CreateTestModel.First(
|
||||
blockchain = Blockchain.Unknown,
|
||||
extraDerivationPath = null, // never-mind
|
||||
scanResponse = createMultiWalletScanResponse(), // never-mind
|
||||
userWallet = createUserWallet(),
|
||||
expected = null,
|
||||
),
|
||||
createFirst(
|
||||
extraDerivationPath = null,
|
||||
scanResponse = createGenericScanResponse(), // default derivation path is null
|
||||
userWallet = createUserWallet(createGenericScanResponse()), // default derivation path is null
|
||||
expectedDerivationPath = Network.DerivationPath.None,
|
||||
),
|
||||
createFirst(
|
||||
extraDerivationPath = null,
|
||||
scanResponse = createMultiWalletScanResponse(),
|
||||
userWallet = createUserWallet(),
|
||||
expectedDerivationPath = Network.DerivationPath.Card(value = "m/44'/60'/0'/0/0"), // use default
|
||||
),
|
||||
createFirst(
|
||||
extraDerivationPath = "m/44'/60'/0'/0/0", // as default
|
||||
scanResponse = createMultiWalletScanResponse(),
|
||||
userWallet = createUserWallet(),
|
||||
expectedDerivationPath = Network.DerivationPath.Card(value = "m/44'/60'/0'/0/0"),
|
||||
),
|
||||
createFirst(
|
||||
extraDerivationPath = "m/84'/0'/0'/0/0",
|
||||
scanResponse = createMultiWalletScanResponse(),
|
||||
userWallet = createUserWallet(),
|
||||
expectedDerivationPath = Network.DerivationPath.Custom(value = "m/84'/0'/0'/0/0"),
|
||||
),
|
||||
// endregion
|
||||
|
|
@ -118,23 +120,23 @@ class NetworkFactoryTest {
|
|||
CreateTestModel.Second(
|
||||
networkId = Network.ID(value = "1", derivationPath = Network.DerivationPath.None),
|
||||
derivationPath = Network.DerivationPath.None, // never-mind
|
||||
scanResponse = createMultiWalletScanResponse(), // never-mind
|
||||
userWallet = createUserWallet(), // never-mind
|
||||
expected = null,
|
||||
),
|
||||
createSecond(
|
||||
scanResponse = createGenericScanResponse(), // default derivation path is null
|
||||
userWallet = createUserWallet(createGenericScanResponse()), // default derivation path is null
|
||||
derivationPath = Network.DerivationPath.None,
|
||||
),
|
||||
createSecond(
|
||||
scanResponse = createMultiWalletScanResponse(),
|
||||
userWallet = createUserWallet(),
|
||||
derivationPath = Network.DerivationPath.None,
|
||||
),
|
||||
createSecond(
|
||||
scanResponse = createMultiWalletScanResponse(),
|
||||
userWallet = createUserWallet(),
|
||||
derivationPath = Network.DerivationPath.Card(value = "m/44'/60'/0'/0/0"),
|
||||
),
|
||||
createSecond(
|
||||
scanResponse = createMultiWalletScanResponse(),
|
||||
userWallet = createUserWallet(),
|
||||
derivationPath = Network.DerivationPath.Custom(value = "m/84'/0'/0'/0/0"),
|
||||
),
|
||||
// endregion
|
||||
|
|
@ -176,13 +178,13 @@ class NetworkFactoryTest {
|
|||
|
||||
private fun createFirst(
|
||||
extraDerivationPath: String?,
|
||||
scanResponse: ScanResponse,
|
||||
userWallet: UserWallet,
|
||||
expectedDerivationPath: Network.DerivationPath,
|
||||
): CreateTestModel.First {
|
||||
return CreateTestModel.First(
|
||||
blockchain = Blockchain.Ethereum,
|
||||
extraDerivationPath = extraDerivationPath,
|
||||
scanResponse = scanResponse,
|
||||
userWallet = userWallet,
|
||||
expected = MockCryptoCurrencyFactory().ethereum.network.copy(
|
||||
id = Network.ID(
|
||||
value = Blockchain.Ethereum.id,
|
||||
|
|
@ -195,13 +197,13 @@ class NetworkFactoryTest {
|
|||
}
|
||||
|
||||
private fun createSecond(
|
||||
scanResponse: ScanResponse,
|
||||
userWallet: UserWallet,
|
||||
derivationPath: Network.DerivationPath,
|
||||
): CreateTestModel.Second {
|
||||
return CreateTestModel.Second(
|
||||
networkId = Network.ID(value = Blockchain.Ethereum.id, derivationPath = derivationPath),
|
||||
derivationPath = derivationPath,
|
||||
scanResponse = scanResponse,
|
||||
userWallet = userWallet,
|
||||
expected = MockCryptoCurrencyFactory().ethereum.network.copy(
|
||||
id = Network.ID(
|
||||
value = Blockchain.Ethereum.id,
|
||||
|
|
@ -232,6 +234,10 @@ class NetworkFactoryTest {
|
|||
)
|
||||
}
|
||||
|
||||
private fun createUserWallet(scanResponse: ScanResponse = createMultiWalletScanResponse()): UserWallet.Cold {
|
||||
return MockUserWalletFactory.create(scanResponse)
|
||||
}
|
||||
|
||||
private fun createMultiWalletScanResponse(): ScanResponse {
|
||||
return MockScanResponseFactory.create(
|
||||
cardConfig = MultiWalletCardConfig,
|
||||
|
|
@ -254,14 +260,14 @@ class NetworkFactoryTest {
|
|||
data class First(
|
||||
val blockchain: Blockchain,
|
||||
val extraDerivationPath: String?,
|
||||
val scanResponse: ScanResponse,
|
||||
val userWallet: UserWallet,
|
||||
override val expected: Network?,
|
||||
) : CreateTestModel
|
||||
|
||||
data class Second(
|
||||
val networkId: Network.ID,
|
||||
val derivationPath: Network.DerivationPath,
|
||||
val scanResponse: ScanResponse,
|
||||
val userWallet: UserWallet,
|
||||
override val expected: Network?,
|
||||
) : CreateTestModel
|
||||
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ internal class DefaultCustomTokensRepository(
|
|||
networkFactory.create(
|
||||
networkId = networkId,
|
||||
derivationPath = derivationPath,
|
||||
scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY]
|
||||
userWallet = userWallet,
|
||||
),
|
||||
) {
|
||||
"Network [$networkId] not found while finding token"
|
||||
|
|
@ -106,8 +106,8 @@ internal class DefaultCustomTokensRepository(
|
|||
symbol = null,
|
||||
)
|
||||
|
||||
val supportedTokenNetworkIds = userWallet.scanResponse.card
|
||||
.supportedBlockchains(userWallet.scanResponse.cardTypesResolver, excludedBlockchains)
|
||||
val supportedTokenNetworkIds = userWallet
|
||||
.supportedBlockchains(excludedBlockchains = excludedBlockchains)
|
||||
.filter(Blockchain::canHandleTokens)
|
||||
.map(Blockchain::toNetworkId)
|
||||
|
||||
|
|
@ -151,7 +151,7 @@ internal class DefaultCustomTokensRepository(
|
|||
networkFactory.create(
|
||||
networkId = networkId,
|
||||
derivationPath = derivationPath,
|
||||
scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY]
|
||||
userWallet = userWallet,
|
||||
),
|
||||
) {
|
||||
"Network [$networkId] not found while creating coin"
|
||||
|
|
@ -188,7 +188,7 @@ internal class DefaultCustomTokensRepository(
|
|||
networkFactory.create(
|
||||
networkId = networkId,
|
||||
derivationPath = derivationPath,
|
||||
scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY]
|
||||
userWallet = userWallet,
|
||||
),
|
||||
) {
|
||||
"Network [$networkId] not found while creating custom token"
|
||||
|
|
@ -261,7 +261,7 @@ internal class DefaultCustomTokensRepository(
|
|||
networkFactory.create(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ import com.tangem.domain.managetokens.repository.ManageTokensRepository
|
|||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.requireColdWallet
|
||||
import com.tangem.pagination.BatchFetchResult
|
||||
import com.tangem.pagination.BatchListSource
|
||||
import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher
|
||||
|
|
@ -142,13 +141,13 @@ internal class DefaultManageTokensRepository(
|
|||
managedCryptoCurrencyFactory.createWithCustomTokens(
|
||||
coinsResponse = updatedCoinsResponse,
|
||||
tokensResponse = tokensResponse,
|
||||
scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY]
|
||||
userWallet = userWallet,
|
||||
)
|
||||
} else {
|
||||
managedCryptoCurrencyFactory.create(
|
||||
coinsResponse = updatedCoinsResponse,
|
||||
tokensResponse = tokensResponse,
|
||||
scanResponse = userWallet?.requireColdWallet()?.scanResponse, // TODO [REDACTED_TASK_KEY]
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -178,7 +177,7 @@ internal class DefaultManageTokensRepository(
|
|||
testnetTokensConfig
|
||||
},
|
||||
tokensResponse = getSavedUserTokensResponseSync(userWallet.walletId),
|
||||
scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY]
|
||||
userWallet = userWallet,
|
||||
)
|
||||
|
||||
return BatchFetchResult.Success(
|
||||
|
|
@ -190,8 +189,8 @@ internal class DefaultManageTokensRepository(
|
|||
|
||||
private fun createDefaultUserTokensResponse(userWallet: UserWallet) =
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(
|
||||
userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY]
|
||||
currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(
|
||||
userWallet = userWallet,
|
||||
),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
|
|
|
|||
|
|
@ -15,13 +15,12 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
|||
import com.tangem.datasource.local.config.testnet.models.TestnetTokensConfig
|
||||
import com.tangem.domain.common.DerivationStyleProvider
|
||||
import com.tangem.domain.common.extensions.canHandleToken
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.common.util.derivationStyleProvider
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency
|
||||
import com.tangem.domain.managetokens.model.ManagedCryptoCurrency.SourceNetwork
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import timber.log.Timber
|
||||
|
||||
internal class ManagedCryptoCurrencyFactory(
|
||||
|
|
@ -32,20 +31,20 @@ internal class ManagedCryptoCurrencyFactory(
|
|||
fun create(
|
||||
coinsResponse: CoinsResponse,
|
||||
tokensResponse: UserTokensResponse?,
|
||||
scanResponse: ScanResponse?,
|
||||
userWallet: UserWallet?,
|
||||
): List<ManagedCryptoCurrency> {
|
||||
return coinsResponse.coins.mapNotNull { coin ->
|
||||
createToken(coin, tokensResponse, coinsResponse.imageHost, scanResponse)
|
||||
createToken(coin, tokensResponse, coinsResponse.imageHost, userWallet)
|
||||
}
|
||||
}
|
||||
|
||||
fun createWithCustomTokens(
|
||||
coinsResponse: CoinsResponse,
|
||||
tokensResponse: UserTokensResponse,
|
||||
scanResponse: ScanResponse,
|
||||
userWallet: UserWallet,
|
||||
): List<ManagedCryptoCurrency> {
|
||||
val customTokens = createCustomTokens(tokensResponse, scanResponse)
|
||||
val tokens = create(coinsResponse, tokensResponse, scanResponse)
|
||||
val customTokens = createCustomTokens(tokensResponse, userWallet)
|
||||
val tokens = create(coinsResponse, tokensResponse, userWallet)
|
||||
|
||||
return customTokens + tokens
|
||||
}
|
||||
|
|
@ -53,10 +52,10 @@ internal class ManagedCryptoCurrencyFactory(
|
|||
fun createTestnetWithCustomTokens(
|
||||
testnetTokensConfig: TestnetTokensConfig,
|
||||
tokensResponse: UserTokensResponse?,
|
||||
scanResponse: ScanResponse,
|
||||
userWallet: UserWallet,
|
||||
): List<ManagedCryptoCurrency> {
|
||||
val customTokens = tokensResponse
|
||||
?.let { createCustomTokens(it, scanResponse) }
|
||||
?.let { createCustomTokens(it, userWallet) }
|
||||
?: emptyList()
|
||||
val testnetTokens = testnetTokensConfig.tokens.map { testnetToken ->
|
||||
ManagedCryptoCurrency.Token(
|
||||
|
|
@ -69,10 +68,10 @@ internal class ManagedCryptoCurrencyFactory(
|
|||
networkId = network.id,
|
||||
contractAddress = network.address,
|
||||
decimals = network.decimalCount,
|
||||
scanResponse = scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
} ?: emptyList(),
|
||||
addedIn = findAddedInNetworks(testnetToken.id, tokensResponse, scanResponse),
|
||||
addedIn = findAddedInNetworks(testnetToken.id, tokensResponse, userWallet),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -81,28 +80,28 @@ internal class ManagedCryptoCurrencyFactory(
|
|||
|
||||
private fun createCustomTokens(
|
||||
tokensResponse: UserTokensResponse,
|
||||
scanResponse: ScanResponse,
|
||||
userWallet: UserWallet,
|
||||
): List<ManagedCryptoCurrency> = tokensResponse.tokens
|
||||
.mapNotNull { token ->
|
||||
maybeCreateCustomToken(token, scanResponse)
|
||||
maybeCreateCustomToken(token, userWallet)
|
||||
}
|
||||
|
||||
private fun maybeCreateCustomToken(
|
||||
token: UserTokensResponse.Token,
|
||||
scanResponse: ScanResponse,
|
||||
userWallet: UserWallet,
|
||||
): ManagedCryptoCurrency? {
|
||||
val blockchain = Blockchain.fromNetworkId(token.networkId)
|
||||
?.takeUnless { it in excludedBlockchains }
|
||||
?: return null
|
||||
|
||||
if (!checkIsCustomToken(token, blockchain, scanResponse.derivationStyleProvider)) {
|
||||
if (!checkIsCustomToken(token, blockchain, userWallet.derivationStyleProvider)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val network = networkFactory.create(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = token.derivationPath,
|
||||
scanResponse = scanResponse,
|
||||
userWallet = userWallet,
|
||||
) ?: return null
|
||||
val contractAddress = token.contractAddress
|
||||
|
||||
|
|
@ -135,7 +134,7 @@ internal class ManagedCryptoCurrencyFactory(
|
|||
coinResponse: CoinsResponse.Coin,
|
||||
tokensResponse: UserTokensResponse?,
|
||||
imageHost: String?,
|
||||
scanResponse: ScanResponse?,
|
||||
userWallet: UserWallet?,
|
||||
): ManagedCryptoCurrency? {
|
||||
if (coinResponse.networks.isEmpty() || !coinResponse.active) return null
|
||||
|
||||
|
|
@ -146,7 +145,7 @@ internal class ManagedCryptoCurrencyFactory(
|
|||
networkId = network.networkId,
|
||||
contractAddress = network.contractAddress,
|
||||
decimals = network.decimalCount?.toInt(),
|
||||
scanResponse = scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
.ifEmpty { return null }
|
||||
|
|
@ -157,7 +156,7 @@ internal class ManagedCryptoCurrencyFactory(
|
|||
symbol = coinResponse.symbol,
|
||||
iconUrl = getIconUrl(coinResponse.id, imageHost),
|
||||
availableNetworks = availableNetworks,
|
||||
addedIn = findAddedInNetworks(coinResponse.id, tokensResponse, scanResponse),
|
||||
addedIn = findAddedInNetworks(coinResponse.id, tokensResponse, userWallet),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -165,7 +164,7 @@ internal class ManagedCryptoCurrencyFactory(
|
|||
networkId: String,
|
||||
contractAddress: String?,
|
||||
decimals: Int?,
|
||||
scanResponse: ScanResponse?,
|
||||
userWallet: UserWallet?,
|
||||
extraDerivationPath: String? = null,
|
||||
): SourceNetwork? {
|
||||
val blockchain = Blockchain.fromNetworkId(networkId)
|
||||
|
|
@ -175,10 +174,8 @@ internal class ManagedCryptoCurrencyFactory(
|
|||
val network = networkFactory.create(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = extraDerivationPath,
|
||||
derivationStyleProvider = scanResponse?.derivationStyleProvider,
|
||||
canHandleTokens = scanResponse?.let {
|
||||
it.card.canHandleToken(blockchain, it.cardTypesResolver, excludedBlockchains)
|
||||
} ?: false, // use card specific check if available
|
||||
derivationStyleProvider = userWallet?.derivationStyleProvider,
|
||||
canHandleTokens = userWallet?.canHandleToken(blockchain, excludedBlockchains) ?: false,
|
||||
) ?: return null
|
||||
|
||||
return when {
|
||||
|
|
@ -207,7 +204,7 @@ internal class ManagedCryptoCurrencyFactory(
|
|||
private fun findAddedInNetworks(
|
||||
currencyId: String,
|
||||
tokensResponse: UserTokensResponse?,
|
||||
scanResponse: ScanResponse?,
|
||||
userWallet: UserWallet?,
|
||||
): Set<Network> {
|
||||
if (tokensResponse == null) return emptySet()
|
||||
|
||||
|
|
@ -220,10 +217,8 @@ internal class ManagedCryptoCurrencyFactory(
|
|||
networkFactory.create(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = token.derivationPath,
|
||||
derivationStyleProvider = scanResponse?.derivationStyleProvider,
|
||||
canHandleTokens = scanResponse?.let {
|
||||
it.card.canHandleToken(blockchain, it.cardTypesResolver, excludedBlockchains)
|
||||
} ?: true,
|
||||
derivationStyleProvider = userWallet?.derivationStyleProvider,
|
||||
canHandleTokens = userWallet?.canHandleToken(blockchain, excludedBlockchains) ?: true,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import com.tangem.domain.markets.*
|
|||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.requireColdWallet
|
||||
import com.tangem.pagination.*
|
||||
import com.tangem.pagination.fetcher.LimitOffsetBatchFetcher
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -259,13 +258,13 @@ internal class DefaultMarketsTokenRepository(
|
|||
cryptoCurrencyFactory.createCoin(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY]
|
||||
userWallet = userWallet,
|
||||
)
|
||||
} else {
|
||||
val currencyNetwork = networkFactory.create(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY]
|
||||
userWallet = userWallet,
|
||||
) ?: return null
|
||||
|
||||
cryptoCurrencyFactory.createToken(
|
||||
|
|
|
|||
|
|
@ -7,11 +7,10 @@ import com.tangem.data.networks.fetcher.CommonNetworkStatusFetcher
|
|||
import com.tangem.data.networks.store.NetworksStatusesStore
|
||||
import com.tangem.data.networks.store.setSourceAsCache
|
||||
import com.tangem.data.networks.store.setSourceAsOnlyCache
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.core.utils.eitherOn
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.domain.wallets.models.requireColdWallet
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
|
|
@ -22,7 +21,6 @@ import javax.inject.Inject
|
|||
* Default implementation of [MultiNetworkStatusFetcher]
|
||||
*
|
||||
* @property networksStatusesStore networks statuses store
|
||||
* @property userWalletsStore user wallets store
|
||||
* @property cardCryptoCurrencyFactory card crypto currency factory
|
||||
* @property commonNetworkStatusFetcher common network status fetcher
|
||||
* @property dispatchers dispatchers
|
||||
|
|
@ -32,7 +30,6 @@ import javax.inject.Inject
|
|||
@Suppress("LongParameterList")
|
||||
internal class DefaultMultiNetworkStatusFetcher @Inject constructor(
|
||||
private val networksStatusesStore: NetworksStatusesStore,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
private val commonNetworkStatusFetcher: CommonNetworkStatusFetcher,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
|
|
@ -41,8 +38,8 @@ internal class DefaultMultiNetworkStatusFetcher @Inject constructor(
|
|||
override suspend fun invoke(params: MultiNetworkStatusFetcher.Params) = eitherOn(dispatchers.default) {
|
||||
networksStatusesStore.setSourceAsCache(userWalletId = params.userWalletId, networks = params.networks)
|
||||
|
||||
val userWallet = catch(
|
||||
block = { userWalletsStore.getSyncStrict(key = params.userWalletId) },
|
||||
val networksCurrencies = catch(
|
||||
block = { createNetworksCurrenciesMap(params) },
|
||||
catch = {
|
||||
networksStatusesStore.setSourceAsOnlyCache(
|
||||
userWalletId = params.userWalletId,
|
||||
|
|
@ -53,31 +50,6 @@ internal class DefaultMultiNetworkStatusFetcher @Inject constructor(
|
|||
},
|
||||
)
|
||||
|
||||
val cardTypesResolver = userWallet.requireColdWallet().cardTypesResolver // TODO [REDACTED_TASK_KEY]
|
||||
val isWalletSupported = with(cardTypesResolver) {
|
||||
isMultiwalletAllowed() || isSingleWalletWithToken()
|
||||
}
|
||||
|
||||
ensure(isWalletSupported) {
|
||||
networksStatusesStore.setSourceAsOnlyCache(
|
||||
userWalletId = params.userWalletId,
|
||||
networks = params.networks,
|
||||
)
|
||||
IllegalStateException("User wallet is not multi-currency")
|
||||
}
|
||||
|
||||
val networksCurrencies = if (cardTypesResolver.isMultiwalletAllowed()) {
|
||||
cardCryptoCurrencyFactory.createCurrenciesForMultiCurrencyCard(
|
||||
userWallet = userWallet,
|
||||
networks = params.networks,
|
||||
)
|
||||
} else {
|
||||
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(
|
||||
scanResponse = userWallet.scanResponse,
|
||||
)
|
||||
.groupBy { it.network }
|
||||
}
|
||||
|
||||
val result = coroutineScope {
|
||||
params.networks
|
||||
.map { network ->
|
||||
|
|
@ -98,4 +70,13 @@ internal class DefaultMultiNetworkStatusFetcher @Inject constructor(
|
|||
IllegalStateException("Failed to fetch network statuses")
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun createNetworksCurrenciesMap(
|
||||
params: MultiNetworkStatusFetcher.Params,
|
||||
): Map<Network, List<CryptoCurrency>> {
|
||||
return cardCryptoCurrencyFactory.create(
|
||||
userWalletId = params.userWalletId,
|
||||
networks = params.networks,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import com.tangem.data.networks.store.NetworksStatusesStore
|
|||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.models.network.NetworkStatus
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusProducer
|
||||
import com.tangem.domain.wallets.models.requireColdWallet
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
|
@ -48,7 +47,7 @@ internal class DefaultMultiNetworkStatusProducer @AssistedInject constructor(
|
|||
val network = networkFactory.create(
|
||||
networkId = status.id,
|
||||
derivationPath = status.id.derivationPath,
|
||||
scanResponse = userWallet.requireColdWallet().scanResponse,
|
||||
userWallet = userWallet,
|
||||
) ?: return@mapNotNullTo null
|
||||
|
||||
NetworkStatus(network = network, value = status.value)
|
||||
|
|
|
|||
|
|
@ -1,47 +1,27 @@
|
|||
package com.tangem.data.networks.single
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.networks.fetcher.CommonNetworkStatusFetcher
|
||||
import com.tangem.data.networks.store.NetworksStatusesStore
|
||||
import com.tangem.data.networks.store.setSourceAsCache
|
||||
import com.tangem.domain.core.utils.catchOn
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Default implementation of [SingleNetworkStatusFetcher]
|
||||
*
|
||||
* @property commonNetworkStatusFetcher common network status fetcher
|
||||
* @property networksStatusesStore networks statuses store
|
||||
* @property cardCryptoCurrencyFactory card crypto currency factory
|
||||
* @property dispatchers dispatchers
|
||||
* @property multiNetworkStatusFetcher multi network status fetcher
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class DefaultSingleNetworkStatusFetcher @Inject constructor(
|
||||
private val commonNetworkStatusFetcher: CommonNetworkStatusFetcher,
|
||||
private val networksStatusesStore: NetworksStatusesStore,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
||||
) : SingleNetworkStatusFetcher {
|
||||
|
||||
override suspend fun invoke(params: SingleNetworkStatusFetcher.Params): Either<Throwable, Unit> {
|
||||
return Either.catchOn(dispatchers.default) {
|
||||
networksStatusesStore.setSourceAsCache(userWalletId = params.userWalletId, network = params.network)
|
||||
|
||||
val networkCurrencies = cardCryptoCurrencyFactory.create(
|
||||
return multiNetworkStatusFetcher(
|
||||
params = MultiNetworkStatusFetcher.Params(
|
||||
userWalletId = params.userWalletId,
|
||||
network = params.network,
|
||||
)
|
||||
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = params.network,
|
||||
networkCurrencies = networkCurrencies.toSet(),
|
||||
)
|
||||
.onLeft { throw it }
|
||||
}
|
||||
networks = setOf(params.network),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,15 @@
|
|||
package com.tangem.data.networks.multi
|
||||
|
||||
import arrow.core.Either
|
||||
import com.google.common.truth.Truth
|
||||
import arrow.core.left
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.common.test.domain.wallet.MockUserWalletFactory
|
||||
import com.tangem.common.test.utils.assertEither
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.networks.fetcher.CommonNetworkStatusFetcher
|
||||
import com.tangem.data.networks.store.NetworksStatusesStore
|
||||
import com.tangem.data.networks.store.setSourceAsCache
|
||||
import com.tangem.data.networks.store.setSourceAsOnlyCache
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.CardTypesResolver
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.*
|
||||
|
|
@ -29,13 +25,11 @@ import org.junit.jupiter.api.TestInstance
|
|||
internal class DefaultMultiNetworkStatusFetcherTest {
|
||||
|
||||
private val networksStatusesStore: NetworksStatusesStore = mockk(relaxUnitFun = true)
|
||||
private val userWalletsStore: UserWalletsStore = mockk()
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk()
|
||||
private val commonNetworkStatusFetcher: CommonNetworkStatusFetcher = mockk()
|
||||
|
||||
private val fetcher = DefaultMultiNetworkStatusFetcher(
|
||||
networksStatusesStore = networksStatusesStore,
|
||||
userWalletsStore = userWalletsStore,
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
commonNetworkStatusFetcher = commonNetworkStatusFetcher,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
|
|
@ -43,16 +37,17 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(networksStatusesStore, userWalletsStore, cardCryptoCurrencyFactory, commonNetworkStatusFetcher)
|
||||
clearMocks(networksStatusesStore, cardCryptoCurrencyFactory, commonNetworkStatusFetcher)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch successfully for multi-currency card`() = runTest {
|
||||
fun `fetch successfully`() = runTest {
|
||||
// Arrange
|
||||
val networks = setOf(ethereum.network, cardano.network)
|
||||
val params = MultiNetworkStatusFetcher.Params(userWalletId = userWalletId, networks = networks)
|
||||
val userWallet = MockUserWalletFactory.create()
|
||||
val cardTypesResolver = mockk<CardTypesResolver>()
|
||||
val params = MultiNetworkStatusFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
networks = setOf(ethereum.network, cardano.network),
|
||||
)
|
||||
|
||||
val networksCurrencies = mapOf(
|
||||
ethereum.network to listOf(ethereum),
|
||||
cardano.network to listOf(cardano),
|
||||
|
|
@ -60,16 +55,7 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
val ethereumFetcherResult = Either.Right(Unit)
|
||||
val cardanoFetcherResult = Either.Right(Unit)
|
||||
|
||||
coEvery { networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks) } returns Unit
|
||||
every { userWalletsStore.getSyncStrict(key = userWalletId) } returns userWallet
|
||||
|
||||
mockkStatic(UserWallet.Cold::cardTypesResolver)
|
||||
every { userWallet.cardTypesResolver } returns cardTypesResolver
|
||||
coEvery { cardTypesResolver.isMultiwalletAllowed() } returns true
|
||||
|
||||
coEvery {
|
||||
cardCryptoCurrencyFactory.createCurrenciesForMultiCurrencyCard(userWallet, params.networks)
|
||||
} returns networksCurrencies
|
||||
coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.networks) } returns networksCurrencies
|
||||
|
||||
coEvery {
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
|
|
@ -92,14 +78,11 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
|
||||
// Assert
|
||||
val expected = Either.Right(Unit)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
assertEither(actual, expected)
|
||||
|
||||
coVerifyOrder {
|
||||
networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks)
|
||||
userWalletsStore.getSyncStrict(key = userWalletId)
|
||||
cardTypesResolver.isMultiwalletAllowed()
|
||||
cardCryptoCurrencyFactory.createCurrenciesForMultiCurrencyCard(userWallet, params.networks)
|
||||
cardCryptoCurrencyFactory.create(params.userWalletId, params.networks)
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
|
|
@ -114,75 +97,17 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
|
||||
coVerify(inverse = true) {
|
||||
networksStatusesStore.setSourceAsOnlyCache(userWalletId = any(), networks = any())
|
||||
cardTypesResolver.isSingleWalletWithToken()
|
||||
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(scanResponse = any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch successfully for single-currency card with token`() = runTest {
|
||||
// Arrange
|
||||
val networks = setOf(ethereum.network)
|
||||
val params = MultiNetworkStatusFetcher.Params(userWalletId = userWalletId, networks = networks)
|
||||
val userWallet = MockUserWalletFactory.create()
|
||||
val cardTypesResolver = mockk<CardTypesResolver>()
|
||||
val networksCurrencies = listOf(ethereum)
|
||||
val ethereumFetcherResult = Either.Right(Unit)
|
||||
|
||||
coEvery { networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks) } returns Unit
|
||||
every { userWalletsStore.getSyncStrict(key = userWalletId) } returns userWallet
|
||||
|
||||
mockkStatic(UserWallet.Cold::cardTypesResolver)
|
||||
every { userWallet.cardTypesResolver } returns cardTypesResolver
|
||||
coEvery { cardTypesResolver.isMultiwalletAllowed() } returns false
|
||||
coEvery { cardTypesResolver.isSingleWalletWithToken() } returns true
|
||||
|
||||
coEvery {
|
||||
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse)
|
||||
} returns networksCurrencies
|
||||
|
||||
coEvery {
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
)
|
||||
} returns ethereumFetcherResult
|
||||
|
||||
// Act
|
||||
val actual = fetcher(params)
|
||||
|
||||
// Assert
|
||||
val expected = Either.Right(Unit)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks)
|
||||
userWalletsStore.getSyncStrict(key = userWalletId)
|
||||
cardTypesResolver.isMultiwalletAllowed()
|
||||
cardTypesResolver.isSingleWalletWithToken()
|
||||
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet.scanResponse)
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
networksStatusesStore.setSourceAsOnlyCache(userWalletId = any(), networks = any())
|
||||
cardCryptoCurrencyFactory.createCurrenciesForMultiCurrencyCard(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch failure if one of them fails`() = runTest {
|
||||
// Arrange
|
||||
val networks = setOf(ethereum.network, cardano.network)
|
||||
val params = MultiNetworkStatusFetcher.Params(userWalletId = userWalletId, networks = networks)
|
||||
val userWallet = MockUserWalletFactory.create()
|
||||
val cardTypesResolver = mockk<CardTypesResolver>()
|
||||
val params = MultiNetworkStatusFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
networks = setOf(ethereum.network, cardano.network),
|
||||
)
|
||||
|
||||
val networksCurrencies = mapOf(
|
||||
ethereum.network to listOf(ethereum),
|
||||
cardano.network to listOf(cardano),
|
||||
|
|
@ -190,16 +115,7 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
val ethereumFetcherResult = Either.Left(IllegalStateException())
|
||||
val cardanoFetcherResult = Either.Right(Unit)
|
||||
|
||||
coEvery { networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks) } returns Unit
|
||||
every { userWalletsStore.getSyncStrict(key = userWalletId) } returns userWallet
|
||||
|
||||
mockkStatic(UserWallet.Cold::cardTypesResolver)
|
||||
every { userWallet.cardTypesResolver } returns cardTypesResolver
|
||||
coEvery { cardTypesResolver.isMultiwalletAllowed() } returns true
|
||||
|
||||
coEvery {
|
||||
cardCryptoCurrencyFactory.createCurrenciesForMultiCurrencyCard(userWallet, params.networks)
|
||||
} returns networksCurrencies
|
||||
coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.networks) } returns networksCurrencies
|
||||
|
||||
coEvery {
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
|
|
@ -222,15 +138,11 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
|
||||
// Assert
|
||||
val expected = Either.Left(IllegalStateException("Failed to fetch network statuses"))
|
||||
Truth.assertThat(actual.isLeft()).isTrue()
|
||||
Truth.assertThat(actual.leftOrNull()).isInstanceOf(expected.leftOrNull()!!::class.java)
|
||||
Truth.assertThat(actual.leftOrNull()).hasMessageThat().isEqualTo(expected.leftOrNull()!!.message)
|
||||
assertEither(actual, expected)
|
||||
|
||||
coVerifyOrder {
|
||||
networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks)
|
||||
userWalletsStore.getSyncStrict(key = userWalletId)
|
||||
cardTypesResolver.isMultiwalletAllowed()
|
||||
cardCryptoCurrencyFactory.createCurrenciesForMultiCurrencyCard(userWallet, params.networks)
|
||||
cardCryptoCurrencyFactory.create(params.userWalletId, params.networks)
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
|
|
@ -245,18 +157,17 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
|
||||
coVerify(inverse = true) {
|
||||
networksStatusesStore.setSourceAsOnlyCache(userWalletId = any(), networks = any())
|
||||
cardTypesResolver.isSingleWalletWithToken()
|
||||
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(scanResponse = any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch failure if all of them fails`() = runTest {
|
||||
// Arrange
|
||||
val networks = setOf(ethereum.network, cardano.network)
|
||||
val params = MultiNetworkStatusFetcher.Params(userWalletId = userWalletId, networks = networks)
|
||||
val userWallet = MockUserWalletFactory.create()
|
||||
val cardTypesResolver = mockk<CardTypesResolver>()
|
||||
val params = MultiNetworkStatusFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
networks = setOf(ethereum.network, cardano.network),
|
||||
)
|
||||
|
||||
val networksCurrencies = mapOf(
|
||||
ethereum.network to listOf(ethereum),
|
||||
cardano.network to listOf(cardano),
|
||||
|
|
@ -264,16 +175,7 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
val ethereumFetcherResult = Either.Left(IllegalStateException("ethereum"))
|
||||
val cardanoFetcherResult = Either.Left(IllegalStateException("cardano"))
|
||||
|
||||
coEvery { networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks) } returns Unit
|
||||
every { userWalletsStore.getSyncStrict(key = userWalletId) } returns userWallet
|
||||
|
||||
mockkStatic(UserWallet.Cold::cardTypesResolver)
|
||||
every { userWallet.cardTypesResolver } returns cardTypesResolver
|
||||
coEvery { cardTypesResolver.isMultiwalletAllowed() } returns true
|
||||
|
||||
coEvery {
|
||||
cardCryptoCurrencyFactory.createCurrenciesForMultiCurrencyCard(userWallet, params.networks)
|
||||
} returns networksCurrencies
|
||||
coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.networks) } returns networksCurrencies
|
||||
|
||||
coEvery {
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
|
|
@ -296,15 +198,11 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
|
||||
// Assert
|
||||
val expected = Either.Left(IllegalStateException("Failed to fetch network statuses"))
|
||||
Truth.assertThat(actual.isLeft()).isTrue()
|
||||
Truth.assertThat(actual.leftOrNull()).isInstanceOf(expected.leftOrNull()!!::class.java)
|
||||
Truth.assertThat(actual.leftOrNull()).hasMessageThat().isEqualTo(expected.leftOrNull()!!.message)
|
||||
assertEither(actual, expected)
|
||||
|
||||
coVerifyOrder {
|
||||
networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks)
|
||||
userWalletsStore.getSyncStrict(key = userWalletId)
|
||||
cardTypesResolver.isMultiwalletAllowed()
|
||||
cardCryptoCurrencyFactory.createCurrenciesForMultiCurrencyCard(userWallet, params.networks)
|
||||
cardCryptoCurrencyFactory.create(params.userWalletId, params.networks)
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = ethereum.network,
|
||||
|
|
@ -319,85 +217,35 @@ internal class DefaultMultiNetworkStatusFetcherTest {
|
|||
|
||||
coVerify(inverse = true) {
|
||||
networksStatusesStore.setSourceAsOnlyCache(userWalletId = any(), networks = any())
|
||||
cardTypesResolver.isSingleWalletWithToken()
|
||||
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(scanResponse = any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch failure if userWalletsStore throws exception`() = runTest {
|
||||
fun `fetch failure if cardCryptoCurrencyFactory throws exception`() = runTest {
|
||||
// Arrange
|
||||
val networks = setOf(ethereum.network, cardano.network)
|
||||
val params = MultiNetworkStatusFetcher.Params(userWalletId = userWalletId, networks = networks)
|
||||
val userWalletStoreException = IllegalStateException()
|
||||
val params = MultiNetworkStatusFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
networks = setOf(ethereum.network, cardano.network),
|
||||
)
|
||||
|
||||
coEvery { networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks) } returns Unit
|
||||
every { userWalletsStore.getSyncStrict(key = userWalletId) } throws userWalletStoreException
|
||||
coEvery { networksStatusesStore.setSourceAsOnlyCache(params.userWalletId, params.networks) } returns Unit
|
||||
val factoryException = IllegalStateException()
|
||||
|
||||
coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.networks) } throws factoryException
|
||||
|
||||
// Act
|
||||
val actual = fetcher(params)
|
||||
|
||||
// Assert
|
||||
val expected = Either.Left(userWalletStoreException)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
val expected = factoryException.left()
|
||||
assertEither(actual, expected)
|
||||
|
||||
coVerifyOrder {
|
||||
networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks)
|
||||
userWalletsStore.getSyncStrict(key = userWalletId)
|
||||
cardCryptoCurrencyFactory.create(params.userWalletId, params.networks)
|
||||
networksStatusesStore.setSourceAsOnlyCache(params.userWalletId, params.networks)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
cardCryptoCurrencyFactory.createCurrenciesForMultiCurrencyCard(any(), any())
|
||||
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(scanResponse = any())
|
||||
commonNetworkStatusFetcher.fetch(any(), any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch failure if card is single-currency`() = runTest {
|
||||
// Arrange
|
||||
val networks = setOf(ethereum.network, cardano.network)
|
||||
val params = MultiNetworkStatusFetcher.Params(userWalletId = userWalletId, networks = networks)
|
||||
val userWallet = MockUserWalletFactory.create()
|
||||
val cardTypesResolver = mockk<CardTypesResolver>()
|
||||
|
||||
coEvery { networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks) } returns Unit
|
||||
every { userWalletsStore.getSyncStrict(key = userWalletId) } returns userWallet
|
||||
|
||||
mockkStatic(UserWallet.Cold::cardTypesResolver)
|
||||
every { userWallet.cardTypesResolver } returns cardTypesResolver
|
||||
coEvery { cardTypesResolver.isMultiwalletAllowed() } returns false
|
||||
coEvery { cardTypesResolver.isSingleWalletWithToken() } returns false
|
||||
|
||||
coEvery { networksStatusesStore.setSourceAsOnlyCache(params.userWalletId, params.networks) } returns Unit
|
||||
|
||||
// Act
|
||||
val actual = fetcher(params)
|
||||
|
||||
// Assert
|
||||
val expected = Either.Left(IllegalStateException("User wallet is not multi-currency"))
|
||||
|
||||
Truth.assertThat(actual.isLeft()).isTrue()
|
||||
Truth.assertThat(actual.leftOrNull()).isInstanceOf(expected.leftOrNull()!!::class.java)
|
||||
Truth.assertThat(actual.leftOrNull()).hasMessageThat().isEqualTo(expected.leftOrNull()!!.message)
|
||||
|
||||
coVerifyOrder {
|
||||
networksStatusesStore.setSourceAsCache(params.userWalletId, params.networks)
|
||||
userWalletsStore.getSyncStrict(key = userWalletId)
|
||||
cardTypesResolver.isMultiwalletAllowed()
|
||||
cardTypesResolver.isSingleWalletWithToken()
|
||||
networksStatusesStore.setSourceAsOnlyCache(params.userWalletId, params.networks)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
networksStatusesStore.setSourceAsOnlyCache(userWalletId = any(), networks = any())
|
||||
cardCryptoCurrencyFactory.createCurrenciesForMultiCurrencyCard(any(), any())
|
||||
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(scanResponse = any())
|
||||
commonNetworkStatusFetcher.fetch(any(), any(), any())
|
||||
}
|
||||
coVerify(inverse = true) { commonNetworkStatusFetcher.fetch(any(), any(), any()) }
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ internal class DefaultMultiNetworkStatusProducerTest {
|
|||
networkFactory.create(
|
||||
networkId = simpleStatuses.first().id,
|
||||
derivationPath = simpleStatuses.first().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
} returns statuses.first().network
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ internal class DefaultMultiNetworkStatusProducerTest {
|
|||
networkFactory.create(
|
||||
networkId = simpleStatuses.last().id,
|
||||
derivationPath = simpleStatuses.last().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
} returns statuses.last().network
|
||||
|
||||
|
|
@ -94,12 +94,12 @@ internal class DefaultMultiNetworkStatusProducerTest {
|
|||
networkFactory.create(
|
||||
networkId = simpleStatuses.first().id,
|
||||
derivationPath = simpleStatuses.first().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
networkFactory.create(
|
||||
networkId = simpleStatuses.last().id,
|
||||
derivationPath = simpleStatuses.last().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -130,7 +130,7 @@ internal class DefaultMultiNetworkStatusProducerTest {
|
|||
networkFactory.create(
|
||||
networkId = simpleStatuses.first().id,
|
||||
derivationPath = simpleStatuses.first().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
} returns statuses.first().network
|
||||
|
||||
|
|
@ -138,7 +138,7 @@ internal class DefaultMultiNetworkStatusProducerTest {
|
|||
networkFactory.create(
|
||||
networkId = simpleStatuses.last().id,
|
||||
derivationPath = simpleStatuses.last().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
} returns statuses.last().network
|
||||
|
||||
|
|
@ -146,7 +146,7 @@ internal class DefaultMultiNetworkStatusProducerTest {
|
|||
networkFactory.create(
|
||||
networkId = updatedSimpleStatuses.first().id,
|
||||
derivationPath = updatedSimpleStatuses.first().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
} returns updatedStatuses.first().network
|
||||
|
||||
|
|
@ -154,7 +154,7 @@ internal class DefaultMultiNetworkStatusProducerTest {
|
|||
networkFactory.create(
|
||||
networkId = updatedSimpleStatuses.last().id,
|
||||
derivationPath = updatedSimpleStatuses.last().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
} returns updatedStatuses.last().network
|
||||
// endregion
|
||||
|
|
@ -177,12 +177,12 @@ internal class DefaultMultiNetworkStatusProducerTest {
|
|||
networkFactory.create(
|
||||
networkId = simpleStatuses.first().id,
|
||||
derivationPath = simpleStatuses.first().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
networkFactory.create(
|
||||
networkId = simpleStatuses.last().id,
|
||||
derivationPath = simpleStatuses.last().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -202,12 +202,12 @@ internal class DefaultMultiNetworkStatusProducerTest {
|
|||
networkFactory.create(
|
||||
networkId = updatedSimpleStatuses.first().id,
|
||||
derivationPath = updatedSimpleStatuses.first().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
networkFactory.create(
|
||||
networkId = updatedSimpleStatuses.last().id,
|
||||
derivationPath = updatedSimpleStatuses.last().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -232,7 +232,7 @@ internal class DefaultMultiNetworkStatusProducerTest {
|
|||
networkFactory.create(
|
||||
networkId = simpleStatuses.first().id,
|
||||
derivationPath = simpleStatuses.first().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
} returns statuses.first().network
|
||||
|
||||
|
|
@ -240,7 +240,7 @@ internal class DefaultMultiNetworkStatusProducerTest {
|
|||
networkFactory.create(
|
||||
networkId = simpleStatuses.last().id,
|
||||
derivationPath = simpleStatuses.last().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
} returns statuses.last().network
|
||||
// endregion
|
||||
|
|
@ -263,12 +263,12 @@ internal class DefaultMultiNetworkStatusProducerTest {
|
|||
networkFactory.create(
|
||||
networkId = simpleStatuses.first().id,
|
||||
derivationPath = simpleStatuses.first().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
networkFactory.create(
|
||||
networkId = simpleStatuses.last().id,
|
||||
derivationPath = simpleStatuses.last().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -312,14 +312,14 @@ internal class DefaultMultiNetworkStatusProducerTest {
|
|||
networkFactory.create(
|
||||
networkId = simpleStatuses.first().id,
|
||||
derivationPath = simpleStatuses.first().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
} returns statuses.first().network
|
||||
every {
|
||||
networkFactory.create(
|
||||
networkId = simpleStatuses.last().id,
|
||||
derivationPath = simpleStatuses.last().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
} returns statuses.last().network
|
||||
// endregion
|
||||
|
|
@ -336,7 +336,7 @@ internal class DefaultMultiNetworkStatusProducerTest {
|
|||
|
||||
verifyOrder(inverse = true) {
|
||||
userWalletsStore.getSyncOrNull(any())
|
||||
networkFactory.create(networkId = any(), derivationPath = any(), scanResponse = any())
|
||||
networkFactory.create(networkId = any(), derivationPath = any(), userWallet = any())
|
||||
}
|
||||
|
||||
// Act 2 (emit)
|
||||
|
|
@ -353,12 +353,12 @@ internal class DefaultMultiNetworkStatusProducerTest {
|
|||
networkFactory.create(
|
||||
networkId = simpleStatuses.first().id,
|
||||
derivationPath = simpleStatuses.first().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
networkFactory.create(
|
||||
networkId = simpleStatuses.last().id,
|
||||
derivationPath = simpleStatuses.last().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -410,12 +410,12 @@ internal class DefaultMultiNetworkStatusProducerTest {
|
|||
networkFactory.create(
|
||||
networkId = simpleStatuses.first().id,
|
||||
derivationPath = simpleStatuses.first().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
networkFactory.create(
|
||||
networkId = simpleStatuses.last().id,
|
||||
derivationPath = simpleStatuses.last().id.derivationPath,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -429,10 +429,10 @@ internal class DefaultMultiNetworkStatusProducerTest {
|
|||
|
||||
val userWallet = MockUserWalletFactory.create(scanResponse = scanResponse)
|
||||
|
||||
val ethNetwork = MockCryptoCurrencyFactory(scanResponse = scanResponse).ethereum.network.copy(
|
||||
val ethNetwork = MockCryptoCurrencyFactory(userWallet = userWallet).ethereum.network.copy(
|
||||
canHandleTokens = true,
|
||||
)
|
||||
|
||||
val cardanoNetwork = MockCryptoCurrencyFactory(scanResponse = scanResponse).cardano.network
|
||||
val cardanoNetwork = MockCryptoCurrencyFactory(userWallet = userWallet).cardano.network
|
||||
}
|
||||
}
|
||||
|
|
@ -2,16 +2,15 @@ package com.tangem.data.networks.single
|
|||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.common.test.domain.token.MockCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.networks.fetcher.CommonNetworkStatusFetcher
|
||||
import com.tangem.data.networks.store.NetworksStatusesStore
|
||||
import com.tangem.data.networks.store.setSourceAsCache
|
||||
import com.tangem.common.test.utils.assertEither
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusFetcher
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.*
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
|
@ -23,114 +22,51 @@ import org.junit.jupiter.api.TestInstance
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class DefaultSingleNetworkStatusFetcherTest {
|
||||
|
||||
private val commonNetworkStatusFetcher: CommonNetworkStatusFetcher = mockk()
|
||||
private val networksStatusesStore: NetworksStatusesStore = mockk(relaxUnitFun = true)
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory = mockk()
|
||||
private val multiNetworkStatusFetcher: MultiNetworkStatusFetcher = mockk()
|
||||
|
||||
private val fetcher = DefaultSingleNetworkStatusFetcher(
|
||||
commonNetworkStatusFetcher = commonNetworkStatusFetcher,
|
||||
networksStatusesStore = networksStatusesStore,
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
private val fetcher = DefaultSingleNetworkStatusFetcher(multiNetworkStatusFetcher = multiNetworkStatusFetcher)
|
||||
|
||||
@BeforeEach
|
||||
fun resetMocks() {
|
||||
clearMocks(commonNetworkStatusFetcher, networksStatusesStore, cardCryptoCurrencyFactory)
|
||||
clearMocks(multiNetworkStatusFetcher)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch successfully`() = runTest {
|
||||
// Arrange
|
||||
val params = SingleNetworkStatusFetcher.Params(userWalletId = userWalletId, network = ethereum.network)
|
||||
val networkCurrencies = listOf(ethereum)
|
||||
val commonFetcherResult = Either.Right(Unit)
|
||||
val multiParams = MultiNetworkStatusFetcher.Params(userWalletId, setOf(params.network))
|
||||
val multiFetcherResult = Either.Right(Unit)
|
||||
|
||||
coEvery { networksStatusesStore.setSourceAsCache(params.userWalletId, params.network) } returns Unit
|
||||
coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.network) } returns networkCurrencies
|
||||
coEvery {
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = params.network,
|
||||
networkCurrencies = networkCurrencies.toSet(),
|
||||
)
|
||||
} returns commonFetcherResult
|
||||
coEvery { multiNetworkStatusFetcher(params = multiParams) } returns multiFetcherResult
|
||||
|
||||
// Act
|
||||
val actual = fetcher(params)
|
||||
|
||||
// Assert
|
||||
val expected = commonFetcherResult
|
||||
val expected = multiFetcherResult
|
||||
assertEither(actual, expected)
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
networksStatusesStore.setSourceAsCache(params.userWalletId, params.network)
|
||||
cardCryptoCurrencyFactory.create(params.userWalletId, params.network)
|
||||
commonNetworkStatusFetcher.fetch(params.userWalletId, params.network, setOf(ethereum))
|
||||
}
|
||||
coVerify(exactly = 1) { multiNetworkStatusFetcher(params = multiParams) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch failure if cardCryptoCurrencyFactory throws exception`() = runTest {
|
||||
fun `fetch failure`() = runTest {
|
||||
// Arrange
|
||||
val params = SingleNetworkStatusFetcher.Params(userWalletId = userWalletId, network = ethereum.network)
|
||||
val factoryException = IllegalStateException()
|
||||
val multiParams = MultiNetworkStatusFetcher.Params(userWalletId, setOf(params.network))
|
||||
val multiFetcherResult = IllegalStateException("Error").left()
|
||||
|
||||
coEvery { networksStatusesStore.setSourceAsCache(params.userWalletId, params.network) } returns Unit
|
||||
coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.network) } throws factoryException
|
||||
coEvery { multiNetworkStatusFetcher(params = multiParams) } returns multiFetcherResult
|
||||
|
||||
// Act
|
||||
val actual = fetcher(params)
|
||||
|
||||
// Arrange
|
||||
val expected = Either.Left(factoryException)
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
val expected = multiFetcherResult
|
||||
assertEither(actual, expected)
|
||||
|
||||
coVerifyOrder {
|
||||
networksStatusesStore.setSourceAsCache(userWalletId = params.userWalletId, network = params.network)
|
||||
cardCryptoCurrencyFactory.create(userWalletId = params.userWalletId, network = params.network)
|
||||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
commonNetworkStatusFetcher.fetch(userWalletId = any(), network = any(), networkCurrencies = any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `fetch failure if commonNetworkStatusFetcher returns exception`() = runTest {
|
||||
// Arrange
|
||||
val params = SingleNetworkStatusFetcher.Params(userWalletId = userWalletId, network = ethereum.network)
|
||||
val networkCurrencies = listOf(ethereum)
|
||||
val commonFetcherResult = IllegalStateException().left()
|
||||
|
||||
coEvery { networksStatusesStore.setSourceAsCache(params.userWalletId, params.network) } returns Unit
|
||||
coEvery { cardCryptoCurrencyFactory.create(params.userWalletId, params.network) } returns networkCurrencies
|
||||
coEvery {
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = params.network,
|
||||
networkCurrencies = networkCurrencies.toSet(),
|
||||
)
|
||||
} returns commonFetcherResult
|
||||
|
||||
// Act
|
||||
val actual = fetcher(params)
|
||||
|
||||
// Arrange
|
||||
val expected = commonFetcherResult
|
||||
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
coVerifyOrder {
|
||||
networksStatusesStore.setSourceAsCache(userWalletId = params.userWalletId, network = params.network)
|
||||
cardCryptoCurrencyFactory.create(userWalletId = params.userWalletId, network = params.network)
|
||||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = params.network,
|
||||
networkCurrencies = setOf(ethereum),
|
||||
)
|
||||
}
|
||||
coVerify(exactly = 1) { multiNetworkStatusFetcher(params = multiParams) }
|
||||
}
|
||||
|
||||
private companion object {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,6 @@ import com.tangem.domain.nft.repository.NFTRepository
|
|||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.requireColdWallet
|
||||
import com.tangem.features.nft.NFTFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
|
|
@ -50,7 +49,6 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
private val nftFeatureToggles: NFTFeatureToggles,
|
||||
private val networkFactory: NetworkFactory,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
resources: Resources,
|
||||
|
|
@ -210,7 +208,7 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
networkFactory.create(
|
||||
blockchain = it,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY]
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
.filter { it.canHandleNFTs(userWalletId) }
|
||||
|
|
@ -545,13 +543,9 @@ internal class DefaultNFTRepository @Inject constructor(
|
|||
|
||||
private fun Network.canHandleNFTs(userWalletId: UserWalletId): Boolean {
|
||||
val scanResponse = userWalletsStore.getSyncStrict(userWalletId).requireColdWallet().scanResponse
|
||||
val blockchain = Blockchain.fromNetworkId(backendId)
|
||||
return when {
|
||||
blockchain == null -> false
|
||||
blockchain.isEvm() && !nftFeatureToggles.isNFTEVMEnabled -> false
|
||||
blockchain == Blockchain.Solana && !nftFeatureToggles.isNFTSolanaEnabled -> false
|
||||
else -> blockchain.canHandleNFTs() &&
|
||||
scanResponse.card.canHandleToken(blockchain, scanResponse.cardTypesResolver, excludedBlockchains)
|
||||
}
|
||||
val blockchain = Blockchain.fromNetworkId(backendId) ?: return false
|
||||
|
||||
return blockchain.canHandleNFTs() &&
|
||||
scanResponse.card.canHandleToken(blockchain, scanResponse.cardTypesResolver, excludedBlockchains)
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,6 @@ import com.tangem.datasource.local.nft.NFTRuntimeStoreFactory
|
|||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.nft.repository.NFTRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.features.nft.NFTFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -32,7 +31,6 @@ internal object NFTDataModule {
|
|||
dispatchers: CoroutineDispatcherProvider,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
userWalletsStore: UserWalletsStore,
|
||||
nftFeatureToggles: NFTFeatureToggles,
|
||||
networkFactory: NetworkFactory,
|
||||
): NFTRepository = DefaultNFTRepository(
|
||||
nftPersistenceStoreFactory = nftPersistenceStoreFactory,
|
||||
|
|
@ -41,7 +39,6 @@ internal object NFTDataModule {
|
|||
dispatchers = dispatchers,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
userWalletsStore = userWalletsStore,
|
||||
nftFeatureToggles = nftFeatureToggles,
|
||||
networkFactory = networkFactory,
|
||||
resources = context.resources,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ dependencies {
|
|||
/** Core modules */
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.deepLinks.global)
|
||||
implementation(projects.core.analytics)
|
||||
|
||||
/** Common modules */
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ import com.tangem.domain.onramp.model.HotCryptoCurrency
|
|||
import com.tangem.domain.onramp.repositories.HotCryptoRepository
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.requireColdWallet
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.runCatching
|
||||
|
|
@ -93,7 +92,7 @@ internal class DefaultHotCryptoRepository(
|
|||
?: error("UserWalletId [$userWalletId] not found")
|
||||
|
||||
HotCryptoCurrencyConverter(
|
||||
scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY]
|
||||
userWallet = userWallet,
|
||||
imageHost = it.imageHost,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,22 +10,22 @@ import com.tangem.domain.models.StatusSource
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.quote.QuoteStatus
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.onramp.model.HotCryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.utils.converter.Converter
|
||||
import java.math.BigDecimal
|
||||
|
||||
/**
|
||||
* Converter from [HotCryptoResponse.Token] to [HotCryptoCurrency]
|
||||
*
|
||||
* @property scanResponse scan response
|
||||
* @property userWallet scan response
|
||||
* @property imageHost image host
|
||||
* @param excludedBlockchains excluded blockchains
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
internal class HotCryptoCurrencyConverter(
|
||||
private val scanResponse: ScanResponse,
|
||||
private val userWallet: UserWallet,
|
||||
private val imageHost: String?,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
) : Converter<HotCryptoResponse.Token, HotCryptoCurrency?> {
|
||||
|
|
@ -81,7 +81,7 @@ internal class HotCryptoCurrencyConverter(
|
|||
return networkFactory.create(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.blockchainsdk.utils.toBlockchain
|
|||
import com.tangem.core.ui.utils.parseBigDecimalOrNull
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.qrscanning.models.QrResult
|
||||
import com.tangem.domain.qrscanning.models.RawQrResult
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.domain.qrscanning.repository.QrScanningEventsRepository
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
|
@ -17,17 +18,17 @@ import java.net.URLDecoder
|
|||
|
||||
internal class DefaultQrScanningEventsRepository : QrScanningEventsRepository {
|
||||
|
||||
private data class QrScanningEvent(val type: SourceType, val qrCode: String)
|
||||
private data class QrScanningEvent(val qrCode: RawQrResult)
|
||||
|
||||
private val scannedEvents = MutableSharedFlow<QrScanningEvent>(replay = 1)
|
||||
|
||||
override suspend fun emitResult(type: SourceType, qrCode: String) {
|
||||
scannedEvents.emit(QrScanningEvent(type, qrCode))
|
||||
override suspend fun emitResult(qrCode: RawQrResult) {
|
||||
scannedEvents.emit(QrScanningEvent(qrCode))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun subscribeToScanningResults(type: SourceType) = scannedEvents
|
||||
.filter { it.type == type }
|
||||
.filter { it.qrCode.requestSource == type }
|
||||
.map { it.qrCode }
|
||||
.onEach {
|
||||
yield() // if we have more than one sub, we must allow them to collect emitted value
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.tangem.data.staking
|
|||
|
||||
import android.util.Base64
|
||||
import arrow.core.getOrElse
|
||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
|
|
@ -15,8 +14,6 @@ import com.tangem.blockchainsdk.utils.toCoinId
|
|||
import com.tangem.blockchainsdk.utils.toMigratedCoinId
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toCompressedPublicKey
|
||||
import com.tangem.data.common.api.safeApiCall
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.staking.converters.YieldBalanceListConverter
|
||||
import com.tangem.data.staking.converters.YieldConverter
|
||||
import com.tangem.data.staking.converters.action.ActionStatusConverter
|
||||
|
|
@ -27,16 +24,13 @@ import com.tangem.data.staking.converters.transaction.StakingTransactionStatusCo
|
|||
import com.tangem.data.staking.converters.transaction.StakingTransactionTypeConverter
|
||||
import com.tangem.data.staking.store.YieldsBalancesStore
|
||||
import com.tangem.data.staking.utils.StakingIdFactory
|
||||
import com.tangem.data.staking.utils.StakingIdFactory.Companion.integrationIdMap
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.stakekit.StakeKitApi
|
||||
import com.tangem.datasource.api.stakekit.models.request.*
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.NetworkTypeDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.YieldBalanceWrapperDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.action.StakingActionStatusDTO
|
||||
import com.tangem.datasource.api.stakekit.models.response.model.transaction.tron.TronStakeKitTransaction
|
||||
import com.tangem.datasource.local.token.StakingBalanceStore
|
||||
import com.tangem.datasource.local.token.StakingYieldsStore
|
||||
import com.tangem.datasource.local.token.converter.StakingNetworkTypeConverter
|
||||
import com.tangem.datasource.local.token.converter.TokenConverter
|
||||
|
|
@ -68,9 +62,7 @@ import com.tangem.lib.crypto.BlockchainUtils.isSolana
|
|||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.orZero
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
import timber.log.Timber
|
||||
import java.math.BigDecimal
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
|
@ -79,9 +71,7 @@ import kotlin.time.Duration.Companion.seconds
|
|||
internal class DefaultStakingRepository(
|
||||
private val stakeKitApi: StakeKitApi,
|
||||
private val stakingYieldsStore: StakingYieldsStore,
|
||||
private val stakingBalanceStore: StakingBalanceStore,
|
||||
private val stakingBalanceStoreV2: YieldsBalancesStore,
|
||||
private val cacheRegistry: CacheRegistry,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val getUserWalletUseCase: GetUserWalletUseCase,
|
||||
|
|
@ -107,12 +97,8 @@ internal class DefaultStakingRepository(
|
|||
private val networkTypeAdapter by lazy { moshi.adapter(NetworkTypeDTO::class.java) }
|
||||
private val stakingActionStatusAdapter by lazy { moshi.adapter(StakingActionStatusDTO::class.java) }
|
||||
|
||||
override fun getIntegrationKey(cryptoCurrencyId: CryptoCurrency.ID): String = with(cryptoCurrencyId) {
|
||||
rawNetworkId.plus(rawCurrencyId)
|
||||
}
|
||||
|
||||
override fun getSupportedIntegrationId(cryptoCurrencyId: CryptoCurrency.ID): String? {
|
||||
return integrationIdMap.getOrDefault(getIntegrationKey(cryptoCurrencyId), null)
|
||||
return stakingIdFactory.createIntegrationId(currencyId = cryptoCurrencyId)
|
||||
}
|
||||
|
||||
override suspend fun fetchEnabledYields() {
|
||||
|
|
@ -398,107 +384,6 @@ internal class DefaultStakingRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun fetchSingleYieldBalance(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
refresh: Boolean,
|
||||
) = withContext(dispatchers.io) {
|
||||
cacheRegistry.invokeOnExpire(
|
||||
key = getYieldBalancesKey(userWalletId),
|
||||
skipCache = refresh,
|
||||
block = {
|
||||
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
|
||||
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network)
|
||||
|
||||
if (integrationId == null || address.isNullOrBlank()) {
|
||||
cacheRegistry.invalidate(getYieldBalancesKey(userWalletId))
|
||||
Timber.w(
|
||||
"IntegrationId or address is null fetching ${cryptoCurrency.name} staking balance",
|
||||
)
|
||||
return@invokeOnExpire
|
||||
}
|
||||
|
||||
val requestBody = getBalanceRequestData(address, integrationId)
|
||||
|
||||
safeApiCall(
|
||||
call = {
|
||||
val result = stakeKitApi.getSingleYieldBalance(
|
||||
integrationId = requestBody.integrationId,
|
||||
body = requestBody,
|
||||
).bind()
|
||||
|
||||
stakingBalanceStore.store(
|
||||
userWalletId = userWalletId,
|
||||
stakingID = StakingBalanceStore.StakingID(
|
||||
integrationId = requestBody.integrationId,
|
||||
address = address,
|
||||
),
|
||||
item = YieldBalanceWrapperDTO(
|
||||
balances = result,
|
||||
integrationId = requestBody.integrationId,
|
||||
addresses = requestBody.addresses,
|
||||
),
|
||||
)
|
||||
},
|
||||
onError = {
|
||||
stakingBalanceStore.storeSingleYieldBalance(
|
||||
userWalletId = userWalletId,
|
||||
item = YieldBalance.Error(integrationId = requestBody.integrationId, address = address),
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
override fun getSingleYieldBalanceFlow(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): Flow<YieldBalance> = channelFlow {
|
||||
launch(dispatchers.io) {
|
||||
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty()
|
||||
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
|
||||
?: error("Could not get integrationId")
|
||||
|
||||
stakingBalanceStore.get(
|
||||
userWalletId = userWalletId,
|
||||
stakingID = StakingBalanceStore.StakingID(integrationId = integrationId, address = address),
|
||||
)
|
||||
.distinctUntilChanged()
|
||||
.collectLatest {
|
||||
if (it != null) {
|
||||
send(it)
|
||||
} else {
|
||||
FirebaseCrashlytics.getInstance()
|
||||
.log("No yield balance available for currency ${cryptoCurrency.id.value}")
|
||||
send(YieldBalance.Error(integrationId, address))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
withContext(dispatchers.io) {
|
||||
fetchSingleYieldBalance(userWalletId = userWalletId, cryptoCurrency = cryptoCurrency)
|
||||
}
|
||||
}.cancellable()
|
||||
|
||||
override suspend fun getSingleYieldBalanceSyncLegacy(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
): YieldBalance = withContext(dispatchers.io) {
|
||||
fetchSingleYieldBalance(userWalletId, cryptoCurrency)
|
||||
|
||||
val address = walletManagersFacade.getDefaultAddress(userWalletId, cryptoCurrency.network).orEmpty()
|
||||
|
||||
val integrationId = integrationIdMap[getIntegrationKey(cryptoCurrency.id)]
|
||||
?: error("Could not get integrationId")
|
||||
|
||||
stakingBalanceStore.getSyncOrNull(
|
||||
userWalletId = userWalletId,
|
||||
stakingID = StakingBalanceStore.StakingID(integrationId = integrationId, address = address),
|
||||
)
|
||||
?: YieldBalance.Error(integrationId, address)
|
||||
}
|
||||
|
||||
override suspend fun getSingleYieldBalanceSync(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrency: CryptoCurrency,
|
||||
|
|
@ -513,129 +398,6 @@ internal class DefaultStakingRepository(
|
|||
?: YieldBalance.Error(integrationId = stakingId.integrationId, address = stakingId.address)
|
||||
}
|
||||
|
||||
@Suppress("LongMethod")
|
||||
override suspend fun fetchMultiYieldBalance(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
refresh: Boolean,
|
||||
) = withContext(dispatchers.io) {
|
||||
if (refresh) {
|
||||
stakingBalanceStore.refresh(
|
||||
userWalletId = userWalletId,
|
||||
stakingIds = cryptoCurrencies.mapStakingId(userWalletId),
|
||||
)
|
||||
}
|
||||
|
||||
val yieldDTOs = withTimeoutOrNull(YIELDS_WATITING_TIMEOUT) {
|
||||
runCatching { stakingYieldsStore.get().firstOrNull() }.getOrNull()
|
||||
}
|
||||
|
||||
if (yieldDTOs == null) {
|
||||
Timber.i("No enabled yields for $userWalletId")
|
||||
stakingBalanceStore.store(userWalletId, emptySet())
|
||||
|
||||
return@withContext
|
||||
}
|
||||
|
||||
cacheRegistry.invokeOnExpire(
|
||||
key = getYieldBalancesKey(userWalletId),
|
||||
skipCache = refresh,
|
||||
block = {
|
||||
val yields = YieldConverter.convertListIgnoreErrors(
|
||||
input = yieldDTOs,
|
||||
onError = { Timber.e("Error converting one of the items in enabled yields: $it") },
|
||||
)
|
||||
|
||||
val availableCurrencies = cryptoCurrencies
|
||||
.mapNotNull { currency ->
|
||||
val addresses = walletManagersFacade.getAddresses(userWalletId, currency.network)
|
||||
val integrationId = integrationIdMap[getIntegrationKey(currency.id)]
|
||||
|
||||
if (integrationId != null && yields.any { it.id == integrationId }) {
|
||||
addresses to integrationId
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
.flatMap { (addresses, integrationId) ->
|
||||
addresses.map { address -> address to integrationId }
|
||||
}
|
||||
.map { getBalanceRequestData(it.first.value, it.second) }
|
||||
.ifEmpty {
|
||||
stakingBalanceStore.store(userWalletId, emptySet())
|
||||
|
||||
cacheRegistry.invalidate(getYieldBalancesKey(userWalletId))
|
||||
|
||||
return@invokeOnExpire
|
||||
}
|
||||
|
||||
val yieldBalances = safeApiCall(
|
||||
call = {
|
||||
stakeKitApi
|
||||
.getMultipleYieldBalances(availableCurrencies)
|
||||
.bind()
|
||||
},
|
||||
onError = {
|
||||
Timber.e(it, "Unable to fetch yield balances")
|
||||
cacheRegistry.invalidate(getYieldBalancesKey(userWalletId))
|
||||
emptySet()
|
||||
},
|
||||
)
|
||||
|
||||
stakingBalanceStore.store(userWalletId, yieldBalances)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun List<CryptoCurrency>.mapStakingId(
|
||||
userWalletId: UserWalletId,
|
||||
): List<StakingBalanceStore.StakingID> {
|
||||
return this
|
||||
.mapNotNull { currency ->
|
||||
val addresses = walletManagersFacade.getAddresses(userWalletId, currency.network)
|
||||
val integrationId = integrationIdMap[getIntegrationKey(currency.id)]
|
||||
|
||||
if (integrationId != null) {
|
||||
addresses to integrationId
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
.flatMap { (addresses, integrationId) ->
|
||||
addresses.map { address ->
|
||||
StakingBalanceStore.StakingID(
|
||||
integrationId = integrationId,
|
||||
address = address.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getMultiYieldBalanceUpdates(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): Flow<YieldBalanceList> {
|
||||
return flow {
|
||||
stakingBalanceStore.get(
|
||||
userWalletId = userWalletId,
|
||||
stakingIds = cryptoCurrencies.mapStakingId(userWalletId),
|
||||
)
|
||||
.map(YieldBalanceListConverter::convert)
|
||||
.collect { emit(it) }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getMultiYieldBalanceSyncLegacy(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
): YieldBalanceList = withContext(dispatchers.io) {
|
||||
fetchMultiYieldBalance(userWalletId, cryptoCurrencies)
|
||||
|
||||
stakingBalanceStore.getSyncOrNull(userWalletId, cryptoCurrencies.mapStakingId(userWalletId))
|
||||
?.let(YieldBalanceListConverter::convert)
|
||||
?: YieldBalanceList.Error
|
||||
}
|
||||
|
||||
override suspend fun getMultiYieldBalanceSync(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencies: List<CryptoCurrency>,
|
||||
|
|
@ -724,7 +486,9 @@ internal class DefaultStakingRepository(
|
|||
}
|
||||
|
||||
override fun getStakingApproval(cryptoCurrency: CryptoCurrency): StakingApproval {
|
||||
return when (getIntegrationKey(cryptoCurrency.id)) {
|
||||
val integrationId = stakingIdFactory.createIntegrationId(currencyId = cryptoCurrency.id)
|
||||
|
||||
return when (integrationId) {
|
||||
Blockchain.Ethereum.id + Blockchain.Polygon.toCoinId(),
|
||||
Blockchain.Ethereum.id + Blockchain.Polygon.toMigratedCoinId(),
|
||||
-> StakingApproval.Needed(ETHEREUM_POLYGON_APPROVE_SPENDER)
|
||||
|
|
@ -773,22 +537,6 @@ internal class DefaultStakingRepository(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getBalanceRequestData(address: String, integrationId: String): YieldBalanceRequestBody {
|
||||
return YieldBalanceRequestBody(
|
||||
addresses = Address(
|
||||
address = address,
|
||||
additionalAddresses = null, // todo fill additional addresses metadata if needed
|
||||
explorerUrl = "", // todo fill exporer url [REDACTED_JIRA]
|
||||
),
|
||||
args = YieldBalanceRequestBody.YieldBalanceRequestArgs(
|
||||
validatorAddresses = listOf(), // todo add validators [REDACTED_JIRA]
|
||||
),
|
||||
integrationId = integrationId,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getYieldBalancesKey(userWalletId: UserWalletId) = "yield_balance_${userWalletId.stringValue}"
|
||||
|
||||
private fun getTronResource(network: Network): TronResource? {
|
||||
val blockchain = Blockchain.fromNetworkId(network.backendId)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.data.staking.di
|
|||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.staking.DefaultStakingActionRepository
|
||||
import com.tangem.data.staking.DefaultStakingErrorResolver
|
||||
import com.tangem.data.staking.DefaultStakingRepository
|
||||
|
|
@ -17,7 +16,6 @@ import com.tangem.datasource.api.stakekit.models.response.model.error.StakeKitEr
|
|||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.token.StakingActionsStore
|
||||
import com.tangem.datasource.local.token.StakingBalanceStore
|
||||
import com.tangem.datasource.local.token.StakingYieldsStore
|
||||
import com.tangem.domain.staking.repositories.StakingActionRepository
|
||||
import com.tangem.domain.staking.repositories.StakingErrorResolver
|
||||
|
|
@ -42,9 +40,7 @@ internal object StakingDataModule {
|
|||
fun provideStakingRepository(
|
||||
stakeKitApi: StakeKitApi,
|
||||
stakingYieldsStore: StakingYieldsStore,
|
||||
stakingBalanceStore: StakingBalanceStore,
|
||||
yieldsBalancesStore: YieldsBalancesStore,
|
||||
cacheRegistry: CacheRegistry,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
getUserWalletUseCase: GetUserWalletUseCase,
|
||||
|
|
@ -55,9 +51,7 @@ internal object StakingDataModule {
|
|||
return DefaultStakingRepository(
|
||||
stakeKitApi = stakeKitApi,
|
||||
stakingYieldsStore = stakingYieldsStore,
|
||||
stakingBalanceStore = stakingBalanceStore,
|
||||
stakingBalanceStoreV2 = yieldsBalancesStore,
|
||||
cacheRegistry = cacheRegistry,
|
||||
dispatchers = dispatchers,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
getUserWalletUseCase = getUserWalletUseCase,
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ internal class StakingIdFactory @Inject constructor(
|
|||
private const val CARDANO_INTEGRATION_ID = "cardano-ada-native-staking"
|
||||
|
||||
// uncomment items as implementation is ready
|
||||
val integrationIdMap = mapOf(
|
||||
private val integrationIdMap = mapOf(
|
||||
Blockchain.TON.toDefaultKey() to TON_INTEGRATION_ID,
|
||||
Blockchain.Solana.toDefaultKey() to SOLANA_INTEGRATION_ID,
|
||||
Blockchain.Cosmos.toDefaultKey() to COSMOS_INTEGRATION_ID,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ internal class DefaultSwapErrorResolver(
|
|||
is ApiResponseError.HttpException -> {
|
||||
expressErrorConverter.convert(throwable.errorBody.orEmpty())
|
||||
}
|
||||
is ExpressError -> throwable
|
||||
else -> ExpressError.UnknownError
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,28 @@
|
|||
package com.tangem.data.swap
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.data.common.api.safeApiCall
|
||||
import com.tangem.data.swap.converter.SwapDataConverter
|
||||
import com.tangem.data.swap.converter.SwapStatusConverter
|
||||
import com.tangem.data.swap.converter.TokenInfoConverter
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.express.models.request.ExchangeSentRequestBody
|
||||
import com.tangem.datasource.api.express.models.request.PairsRequestBody
|
||||
import com.tangem.datasource.api.express.models.response.ExchangeDataResponseWithTxDetails
|
||||
import com.tangem.datasource.api.express.models.response.TxDetails
|
||||
import com.tangem.datasource.crypto.DataSignatureVerifier
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.exchangeservice.swap.ExpressUtils
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.express.ExpressRepository
|
||||
import com.tangem.domain.express.models.ExpressError
|
||||
import com.tangem.domain.express.models.ExpressProvider
|
||||
import com.tangem.domain.express.models.ExpressProviderType
|
||||
import com.tangem.domain.express.models.ExpressRateType
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.swap.SwapRepositoryV2
|
||||
import com.tangem.domain.swap.models.SwapPairModel
|
||||
import com.tangem.domain.swap.models.SwapQuoteModel
|
||||
import com.tangem.domain.swap.models.*
|
||||
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
|
||||
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
|
@ -24,19 +33,26 @@ import kotlinx.coroutines.async
|
|||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
import java.math.BigDecimal
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
internal class DefaultSwapRepositoryV2 @Inject constructor(
|
||||
private val tangemExpressApi: TangemExpressApi,
|
||||
private val expressRepository: ExpressRepository,
|
||||
private val coroutineDispatcher: CoroutineDispatcherProvider,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val currencyStatusOperations: BaseCurrencyStatusOperations,
|
||||
private val dataSignatureVerifier: DataSignatureVerifier,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
) : SwapRepositoryV2 {
|
||||
|
||||
private val swapDataConverter = SwapDataConverter()
|
||||
private val tokenInfoConverter = TokenInfoConverter()
|
||||
private val exchangeStatusConverter = SwapStatusConverter()
|
||||
private val txDetailsMoshiAdapter = moshi.adapter(TxDetails::class.java)
|
||||
|
||||
override suspend fun getPairs(
|
||||
userWallet: UserWallet,
|
||||
|
|
@ -153,6 +169,115 @@ internal class DefaultSwapRepositoryV2 @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
override suspend fun getSwapData(
|
||||
userWallet: UserWallet,
|
||||
fromCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
toCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
fromAmount: String,
|
||||
toAddress: String?,
|
||||
expressProvider: ExpressProvider,
|
||||
rateType: ExpressRateType,
|
||||
): SwapDataModel = withContext(coroutineDispatcher.io) {
|
||||
val requestId = UUID.randomUUID().toString()
|
||||
val fromCryptoCurrency = fromCryptoCurrencyStatus.currency
|
||||
val toCryptoCurrency = toCryptoCurrencyStatus.currency
|
||||
|
||||
val refundData = when (expressProvider.type) {
|
||||
ExpressProviderType.CEX,
|
||||
ExpressProviderType.DEX_BRIDGE,
|
||||
ExpressProviderType.DEX,
|
||||
-> SwapRefundData(
|
||||
refundAddress = fromCryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value,
|
||||
refundExtraId = null, // currently always null
|
||||
)
|
||||
else -> null
|
||||
}
|
||||
|
||||
val response = tangemExpressApi.getExchangeData(
|
||||
fromContractAddress = fromCryptoCurrency.getContractAddress(),
|
||||
toContractAddress = toCryptoCurrency.getContractAddress(),
|
||||
fromNetwork = fromCryptoCurrency.network.backendId,
|
||||
toNetwork = toCryptoCurrency.network.backendId,
|
||||
fromAddress = fromCryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value.orEmpty(),
|
||||
toAddress = toAddress ?: toCryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value.orEmpty(),
|
||||
fromDecimals = fromCryptoCurrency.decimals,
|
||||
toDecimals = toCryptoCurrency.decimals,
|
||||
fromAmount = fromAmount,
|
||||
providerId = expressProvider.providerId,
|
||||
rateType = rateType.name.lowercase(),
|
||||
requestId = requestId,
|
||||
refundAddress = refundData?.refundAddress,
|
||||
refundExtraId = refundData?.refundExtraId,
|
||||
userWalletId = userWallet.walletId.stringValue,
|
||||
refCode = ExpressUtils.getRefCode(
|
||||
userWallet = userWallet,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
),
|
||||
).getOrThrow()
|
||||
|
||||
if (dataSignatureVerifier.verifySignature(response.signature, response.txDetailsJson)) {
|
||||
val txDetails = parseTxDetails(response.txDetailsJson)
|
||||
?: throw ExpressError.UnknownError
|
||||
if (txDetails.requestId != requestId) {
|
||||
throw ExpressError.InvalidRequestIdError()
|
||||
}
|
||||
if (!toAddress.equals(txDetails.payoutAddress, ignoreCase = true)) {
|
||||
throw ExpressError.InvalidPayoutAddressError()
|
||||
}
|
||||
swapDataConverter.convert(
|
||||
ExchangeDataResponseWithTxDetails(
|
||||
dataResponse = response,
|
||||
txDetails = txDetails,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
throw ExpressError.InvalidSignatureError()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun swapTransactionSent(
|
||||
userWallet: UserWallet,
|
||||
fromCryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
toAddress: String,
|
||||
txId: String,
|
||||
txHash: String,
|
||||
txExtraId: String?,
|
||||
) {
|
||||
withContext(coroutineDispatcher.io) {
|
||||
tangemExpressApi.exchangeSent(
|
||||
userWalletId = userWallet.walletId.stringValue,
|
||||
refCode = ExpressUtils.getRefCode(
|
||||
userWallet = userWallet,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
),
|
||||
body = ExchangeSentRequestBody(
|
||||
txId = txId,
|
||||
fromNetwork = fromCryptoCurrencyStatus.currency.network.backendId,
|
||||
fromAddress = fromCryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value.orEmpty(),
|
||||
payinAddress = toAddress,
|
||||
payinExtraId = txExtraId,
|
||||
txHash = txHash,
|
||||
),
|
||||
).getOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getExchangeStatus(userWallet: UserWallet, txId: String): SwapStatusModel =
|
||||
withContext(coroutineDispatcher.io) {
|
||||
exchangeStatusConverter.convert(
|
||||
tangemExpressApi
|
||||
.getExchangeStatus(
|
||||
userWalletId = userWallet.walletId.stringValue,
|
||||
refCode = ExpressUtils.getRefCode(
|
||||
userWallet = userWallet,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
),
|
||||
txId = txId,
|
||||
)
|
||||
.getOrThrow(),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun CoroutineScope.getPairsInternal(
|
||||
userWallet: UserWallet,
|
||||
initialCurrency: CryptoCurrency,
|
||||
|
|
@ -200,7 +325,7 @@ internal class DefaultSwapRepositoryV2 @Inject constructor(
|
|||
},
|
||||
)
|
||||
|
||||
private suspend fun CoroutineScope.createPairModelOnly(
|
||||
private suspend fun createPairModelOnly(
|
||||
currencyFrom: CryptoCurrency?,
|
||||
currencyTo: CryptoCurrency?,
|
||||
userWalletId: UserWalletId,
|
||||
|
|
@ -229,6 +354,15 @@ internal class DefaultSwapRepositoryV2 @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun parseTxDetails(txDetailsJson: String): TxDetails? {
|
||||
return try {
|
||||
txDetailsMoshiAdapter.fromJson(txDetailsJson)
|
||||
} catch (e: IOException) {
|
||||
Timber.e(e, "error parsing txDetailsJson")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun CryptoCurrency.getContractAddress(): String {
|
||||
return when (this) {
|
||||
is CryptoCurrency.Token -> this.contractAddress
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import com.tangem.domain.swap.models.SwapTransactionListModel
|
|||
import com.tangem.domain.swap.models.SwapTransactionModel
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.requireColdWallet
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
|
@ -119,7 +118,7 @@ internal class DefaultSwapTransactionRepository(
|
|||
currencyTxs?.mapNotNull {
|
||||
listConverter.convertBack(
|
||||
value = it,
|
||||
scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY]
|
||||
userWallet = userWallet,
|
||||
txStatuses = txStatuses,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
package com.tangem.data.swap.converter
|
||||
|
||||
import com.tangem.datasource.api.express.models.response.ExchangeDataResponse
|
||||
import com.tangem.datasource.api.express.models.response.ExchangeDataResponseWithTxDetails
|
||||
import com.tangem.datasource.api.express.models.response.TxDetails
|
||||
import com.tangem.datasource.api.express.models.response.TxType
|
||||
import com.tangem.domain.swap.models.SwapDataModel
|
||||
import com.tangem.domain.swap.models.SwapDataTransactionModel
|
||||
import com.tangem.utils.converter.Converter
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class SwapDataConverter : Converter<ExchangeDataResponseWithTxDetails, SwapDataModel> {
|
||||
|
||||
override fun convert(value: ExchangeDataResponseWithTxDetails): SwapDataModel {
|
||||
val data = value.dataResponse
|
||||
return SwapDataModel(
|
||||
toTokenAmount = requireNotNull(data.toAmount.toBigDecimalOrNull()?.movePointLeft(data.toDecimals)),
|
||||
transaction = convertTransaction(value.txDetails, data),
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertTransaction(
|
||||
transactionDto: TxDetails,
|
||||
dataResponse: ExchangeDataResponse,
|
||||
): SwapDataTransactionModel {
|
||||
val fromAmount = requireNotNull(
|
||||
dataResponse.fromAmount.toBigDecimalOrNull()?.movePointLeft(dataResponse.fromDecimals),
|
||||
)
|
||||
val toAmount = requireNotNull(
|
||||
dataResponse.toAmount.toBigDecimalOrNull()?.movePointLeft(dataResponse.toDecimals),
|
||||
)
|
||||
|
||||
return if (transactionDto.txType == TxType.SWAP) {
|
||||
val otherNativeFeeWei = transactionDto.otherNativeFee?.let {
|
||||
if (it == "0") {
|
||||
BigDecimal.ZERO
|
||||
} else {
|
||||
requireNotNull(it.toBigDecimalOrNull()) { "wrong amount format, use only digits" }
|
||||
}
|
||||
}
|
||||
SwapDataTransactionModel.DEX(
|
||||
fromAmount = fromAmount,
|
||||
toAmount = toAmount,
|
||||
txValue = transactionDto.txValue,
|
||||
txId = dataResponse.txId,
|
||||
txTo = transactionDto.txTo,
|
||||
txFrom = requireNotNull(transactionDto.txFrom),
|
||||
txData = requireNotNull(transactionDto.txData),
|
||||
txExtraId = transactionDto.txExtraId,
|
||||
otherNativeFeeWei = otherNativeFeeWei,
|
||||
gas = transactionDto.gas?.toBigIntegerOrNull() ?: error("gas is empty"),
|
||||
)
|
||||
} else {
|
||||
SwapDataTransactionModel.CEX(
|
||||
fromAmount = fromAmount,
|
||||
toAmount = toAmount,
|
||||
txValue = transactionDto.txValue,
|
||||
txId = dataResponse.txId,
|
||||
txTo = transactionDto.txTo,
|
||||
externalTxId = requireNotNull(transactionDto.externalTxId),
|
||||
externalTxUrl = requireNotNull(transactionDto.externalTxUrl),
|
||||
txExtraIdName = transactionDto.txExtraIdName,
|
||||
txExtraId = transactionDto.txExtraId,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,8 +3,8 @@ package com.tangem.data.swap.converter.transaction
|
|||
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
|
||||
import com.tangem.data.swap.models.SwapStatusDTO
|
||||
import com.tangem.data.swap.models.SwapTransactionDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.swap.models.SwapTransactionModel
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.utils.converter.TwoWayConverter
|
||||
|
||||
internal class SavedSwapTransactionConverter(
|
||||
|
|
@ -35,14 +35,14 @@ internal class SavedSwapTransactionConverter(
|
|||
|
||||
fun convertBack(
|
||||
value: SwapTransactionDTO,
|
||||
scanResponse: ScanResponse,
|
||||
userWallet: UserWallet,
|
||||
txStatuses: Map<String, SwapStatusDTO>,
|
||||
): SwapTransactionModel {
|
||||
val status = txStatuses[value.txId]
|
||||
val refundCurrency = status?.refundTokensResponse?.let { id ->
|
||||
responseCryptoCurrenciesFactory.createCurrency(
|
||||
responseToken = id,
|
||||
scanResponse = scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
val statusWithRefundCurrency = status?.copy(refundCurrency = refundCurrency)
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import com.tangem.data.swap.models.SwapStatusDTO
|
|||
import com.tangem.data.swap.models.SwapTransactionDTO
|
||||
import com.tangem.data.swap.models.SwapTransactionListDTO
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.swap.models.SwapTransactionListModel
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
|
|
@ -35,7 +35,7 @@ internal class SavedSwapTransactionListConverter(
|
|||
|
||||
fun convertBack(
|
||||
value: SwapTransactionListDTO,
|
||||
scanResponse: ScanResponse,
|
||||
userWallet: UserWallet,
|
||||
txStatuses: Map<String, SwapStatusDTO>,
|
||||
): SwapTransactionListModel? {
|
||||
val fromToken = value.fromTokensResponse
|
||||
|
|
@ -45,16 +45,16 @@ internal class SavedSwapTransactionListConverter(
|
|||
} else {
|
||||
val fromCryptoCurrency = responseCryptoCurrenciesFactory.createCurrency(
|
||||
responseToken = fromToken,
|
||||
scanResponse = scanResponse,
|
||||
userWallet = userWallet,
|
||||
) ?: return null
|
||||
val toCryptoCurrency = responseCryptoCurrenciesFactory.createCurrency(
|
||||
responseToken = toToken,
|
||||
scanResponse = scanResponse,
|
||||
userWallet = userWallet,
|
||||
) ?: return null
|
||||
|
||||
return SwapTransactionListModel(
|
||||
transactions = value.transactions.map { tx ->
|
||||
savedSwapTransactionConverter.convertBack(tx, scanResponse, txStatuses)
|
||||
savedSwapTransactionConverter.convertBack(tx, userWallet, txStatuses)
|
||||
},
|
||||
userWalletId = value.userWalletId,
|
||||
fromCryptoCurrencyId = value.fromCryptoCurrencyId,
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import com.tangem.data.swap.DefaultSwapRepositoryV2
|
|||
import com.tangem.data.swap.DefaultSwapTransactionRepository
|
||||
import com.tangem.datasource.api.express.TangemExpressApi
|
||||
import com.tangem.datasource.api.express.models.response.ExpressErrorResponse
|
||||
import com.tangem.datasource.crypto.DataSignatureVerifier
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.express.ExpressRepository
|
||||
|
|
@ -43,6 +44,8 @@ internal object SwapDataModule {
|
|||
coroutineDispatcher: CoroutineDispatcherProvider,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
currencyStatusOperations: BaseCurrencyStatusOperations,
|
||||
dataSignatureVerifier: DataSignatureVerifier,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
): SwapRepositoryV2 {
|
||||
return DefaultSwapRepositoryV2(
|
||||
tangemExpressApi = tangemExpressApi,
|
||||
|
|
@ -50,6 +53,8 @@ internal object SwapDataModule {
|
|||
coroutineDispatcher = coroutineDispatcher,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
currencyStatusOperations = currencyStatusOperations,
|
||||
dataSignatureVerifier = dataSignatureVerifier,
|
||||
moshi = moshi,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import com.tangem.datasource.local.token.UserTokensResponseStore
|
|||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.core.utils.catchOn
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesFetcher.Params
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
|
@ -60,7 +59,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcher(
|
|||
if (!userWallet.isMultiCurrency) error("${this::class.simpleName} supports only multi-currency wallet")
|
||||
|
||||
val response = if (userWallet is UserWallet.Cold && userWallet.isDemoWalletWithoutSavedTokens()) {
|
||||
createDefaultUserTokensResponse(scanResponse = userWallet.scanResponse)
|
||||
createDefaultUserTokensResponse(userWallet = userWallet)
|
||||
} else {
|
||||
safeApiCall(
|
||||
call = {
|
||||
|
|
@ -99,7 +98,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcher(
|
|||
val userWalletId = userWallet.walletId
|
||||
|
||||
val response = userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId)
|
||||
?: createDefaultUserTokensResponse(scanResponse = userWallet.requireColdWallet().scanResponse)
|
||||
?: createDefaultUserTokensResponse(userWallet = userWallet)
|
||||
|
||||
if (error is ApiResponseError.HttpException && error.code == ApiResponseError.HttpException.Code.NOT_FOUND) {
|
||||
Timber.w(error, "Requested currencies could not be found in the remote store for: $userWalletId")
|
||||
|
|
@ -121,9 +120,9 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcher(
|
|||
expressServiceLoader.update(userWallet = userWallet, userTokens = tokens)
|
||||
}
|
||||
|
||||
private fun createDefaultUserTokensResponse(scanResponse: ScanResponse): UserTokensResponse {
|
||||
private fun createDefaultUserTokensResponse(userWallet: UserWallet): UserTokensResponse {
|
||||
return userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(scanResponse),
|
||||
currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(userWallet),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducer @AssistedInject constr
|
|||
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = response,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
).toSet()
|
||||
}
|
||||
.onEmpty { emit(fallback) }
|
||||
|
|
|
|||
|
|
@ -239,7 +239,8 @@ internal class DefaultCurrenciesRepository(
|
|||
)
|
||||
|
||||
val token = userTokensResponseFactory.createResponseToken(currency)
|
||||
val updatedResponse = savedCurrencies.copy(tokens = savedCurrencies.tokens.filterNot { it == token })
|
||||
val updatedResponse =
|
||||
savedCurrencies.copy(tokens = savedCurrencies.tokens.filterNot { it == token })
|
||||
userTokensSaver.storeAndPush(
|
||||
userWalletId = userWalletId,
|
||||
response = updatedResponse,
|
||||
|
|
@ -284,10 +285,11 @@ internal class DefaultCurrenciesRepository(
|
|||
): CryptoCurrency {
|
||||
return withContext(dispatchers.io) {
|
||||
val userWallet = userWalletsStore.getSyncStrict(userWalletId)
|
||||
userWallet.requireColdWallet()
|
||||
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false)
|
||||
|
||||
val currency = cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(
|
||||
userWallet.requireColdWallet().scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
|
||||
fetchExpressAssetsByNetworkIds(
|
||||
|
|
@ -309,11 +311,12 @@ internal class DefaultCurrenciesRepository(
|
|||
val scanResponse = userWallet.requireColdWallet().scanResponse
|
||||
|
||||
val currencies = if (scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(scanResponse = scanResponse)
|
||||
cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(userWallet = userWallet)
|
||||
} else {
|
||||
cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(scanResponse = scanResponse).run {
|
||||
listOf(this)
|
||||
}
|
||||
cardCryptoCurrencyFactory.createPrimaryCurrencyForSingleCurrencyCard(userWallet = userWallet)
|
||||
.run {
|
||||
listOf(this)
|
||||
}
|
||||
}
|
||||
|
||||
fetchExpressAssetsByNetworkIds(
|
||||
|
|
@ -332,10 +335,11 @@ internal class DefaultCurrenciesRepository(
|
|||
): CryptoCurrency {
|
||||
return withContext(dispatchers.io) {
|
||||
val userWallet = userWalletsStore.getSyncStrict(userWalletId)
|
||||
userWallet.requireColdWallet()
|
||||
ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = false)
|
||||
|
||||
val currency = cardCryptoCurrencyFactory.createCurrenciesForSingleCurrencyCardWithToken(
|
||||
scanResponse = userWallet.requireColdWallet().scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
.find { it.id == id }
|
||||
requireNotNull(currency) { "Unable to find currency with provided ID: $id" }
|
||||
|
|
@ -375,7 +379,10 @@ internal class DefaultCurrenciesRepository(
|
|||
},
|
||||
)
|
||||
|
||||
responseCryptoCurrenciesFactory.createCurrencies(storedTokens, userWallet.requireColdWallet().scanResponse)
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
storedTokens,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getMultiCurrencyWalletCachedCurrenciesSync(userWalletId: UserWalletId) =
|
||||
|
|
@ -390,7 +397,10 @@ internal class DefaultCurrenciesRepository(
|
|||
},
|
||||
)
|
||||
|
||||
responseCryptoCurrenciesFactory.createCurrencies(storedTokens, userWallet.requireColdWallet().scanResponse)
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
storedTokens,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getMultiCurrencyWalletCurrency(
|
||||
|
|
@ -415,7 +425,7 @@ internal class DefaultCurrenciesRepository(
|
|||
responseCryptoCurrenciesFactory.createCurrency(
|
||||
currencyId = id,
|
||||
response = response,
|
||||
scanResponse = userWallet.requireColdWallet().scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -449,7 +459,7 @@ internal class DefaultCurrenciesRepository(
|
|||
|
||||
val coin = responseCryptoCurrenciesFactory.createCurrency(
|
||||
responseToken = storedCoin,
|
||||
scanResponse = userWallet.requireColdWallet().scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
|
||||
coin as? CryptoCurrency.Coin ?: error("Unable to create currency")
|
||||
|
|
@ -493,12 +503,15 @@ internal class DefaultCurrenciesRepository(
|
|||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
): Boolean {
|
||||
val blockchain = cryptoCurrencyStatus.currency.network.toBlockchain()
|
||||
val isBitcoinBlockchain = blockchain == Blockchain.Bitcoin || blockchain == Blockchain.BitcoinTestnet
|
||||
val isBitcoinBlockchain =
|
||||
blockchain == Blockchain.Bitcoin || blockchain == Blockchain.BitcoinTestnet
|
||||
return when {
|
||||
cryptoCurrencyStatus.currency is CryptoCurrency.Coin && isBitcoinBlockchain -> {
|
||||
val outgoingTransactions = cryptoCurrencyStatus.value.pendingTransactions.filter { it.isOutgoing }
|
||||
val outgoingTransactions =
|
||||
cryptoCurrencyStatus.value.pendingTransactions.filter { it.isOutgoing }
|
||||
outgoingTransactions.isNotEmpty()
|
||||
}
|
||||
|
||||
blockchain.isEvm() -> false
|
||||
blockchain == Blockchain.Tron || blockchain == Blockchain.TronTestnet -> false
|
||||
else -> {
|
||||
|
|
@ -536,6 +549,7 @@ internal class DefaultCurrenciesRepository(
|
|||
balance = balance,
|
||||
)
|
||||
}
|
||||
|
||||
is FeePaidSdkCurrency.FeeResource -> FeePaidCurrency.FeeResource(currency = feePaidCurrency.currency)
|
||||
}
|
||||
}
|
||||
|
|
@ -563,7 +577,8 @@ internal class DefaultCurrenciesRepository(
|
|||
.coins
|
||||
.firstOrNull()
|
||||
?: error("Token not found")
|
||||
val network = foundToken.networks.firstOrNull { it.networkId == networkId } ?: error("Network not found")
|
||||
val network = foundToken.networks.firstOrNull { it.networkId == networkId }
|
||||
?: error("Network not found")
|
||||
CryptoCurrencyFactory.Token(
|
||||
symbol = foundToken.symbol,
|
||||
name = foundToken.name,
|
||||
|
|
@ -576,7 +591,7 @@ internal class DefaultCurrenciesRepository(
|
|||
token = token,
|
||||
networkId = networkId,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY]
|
||||
userWallet = userWallet,
|
||||
) ?: error("Unable to create token")
|
||||
}
|
||||
|
||||
|
|
@ -604,7 +619,6 @@ internal class DefaultCurrenciesRepository(
|
|||
userWallet: UserWallet,
|
||||
currencyRawId: CryptoCurrency.RawID,
|
||||
): Flow<List<CryptoCurrency>> {
|
||||
userWallet.requireColdWallet() // TODO [REDACTED_TASK_KEY]
|
||||
return when {
|
||||
userWallet.isMultiCurrency -> {
|
||||
getSavedUserTokensResponse(userWallet.walletId).map { storedTokens ->
|
||||
|
|
@ -614,23 +628,25 @@ internal class DefaultCurrenciesRepository(
|
|||
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = storedTokens.copy(tokens = filterResponse),
|
||||
scanResponse = userWallet.requireColdWallet().scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
val currencies = if (userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
getSingleCurrencyWalletWithCardCurrencies(userWallet.walletId)
|
||||
} else {
|
||||
val currency =
|
||||
getSingleCurrencyWalletPrimaryCurrency(userWalletId = userWallet.walletId)
|
||||
|
||||
if (currency.id.rawCurrencyId == currencyRawId) {
|
||||
listOf(currency)
|
||||
else -> {
|
||||
val currencies =
|
||||
if (userWallet.requireColdWallet().scanResponse.cardTypesResolver.isSingleWalletWithToken()) {
|
||||
getSingleCurrencyWalletWithCardCurrencies(userWallet.walletId)
|
||||
} else {
|
||||
emptyList()
|
||||
val currency =
|
||||
getSingleCurrencyWalletPrimaryCurrency(userWalletId = userWallet.walletId)
|
||||
|
||||
if (currency.id.rawCurrencyId == currencyRawId) {
|
||||
listOf(currency)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
}
|
||||
flow {
|
||||
emit(currencies)
|
||||
}
|
||||
|
|
@ -656,15 +672,15 @@ internal class DefaultCurrenciesRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override fun getCardTypesResolver(userWalletId: UserWalletId): CardTypesResolver {
|
||||
return userWalletsStore.getSyncStrict(userWalletId).requireColdWallet().cardTypesResolver // TODO [REDACTED_TASK_KEY]
|
||||
override fun getCardTypesResolver(userWalletId: UserWalletId): CardTypesResolver? {
|
||||
return (userWalletsStore.getSyncStrict(userWalletId) as? UserWallet.Cold)?.cardTypesResolver
|
||||
}
|
||||
|
||||
private fun getMultiCurrencyWalletCurrencies(userWallet: UserWallet): Flow<List<CryptoCurrency>> {
|
||||
return getSavedUserTokensResponse(userWallet.walletId).map { storedTokens ->
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = storedTokens,
|
||||
scanResponse = userWallet.requireColdWallet().scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -754,7 +770,10 @@ internal class DefaultCurrenciesRepository(
|
|||
?: createDefaultUserTokensResponse(userWallet = userWallet)
|
||||
|
||||
if (e is ApiResponseError.HttpException && e.code == ApiResponseError.HttpException.Code.NOT_FOUND) {
|
||||
Timber.w(e, "Requested currencies could not be found in the remote store for: $userWalletId")
|
||||
Timber.w(
|
||||
e,
|
||||
"Requested currencies could not be found in the remote store for: $userWalletId",
|
||||
)
|
||||
|
||||
userTokensSaver.push(userWalletId, response)
|
||||
} else {
|
||||
|
|
@ -766,8 +785,8 @@ internal class DefaultCurrenciesRepository(
|
|||
|
||||
private fun createDefaultUserTokensResponse(userWallet: UserWallet) =
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(
|
||||
userWallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY]
|
||||
currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(
|
||||
userWallet = userWallet,
|
||||
),
|
||||
isGroupedByNetwork = false,
|
||||
isSortedByBalance = false,
|
||||
|
|
@ -786,9 +805,11 @@ internal class DefaultCurrenciesRepository(
|
|||
!userWallet.isMultiCurrency && isMultiCurrencyWalletExpected -> {
|
||||
"Multi currency wallet expected, but single currency wallet was found: $userWalletId"
|
||||
}
|
||||
|
||||
userWallet.isMultiCurrency && !isMultiCurrencyWalletExpected -> {
|
||||
"Single currency wallet expected, but multi currency wallet was found: $userWalletId"
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,9 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
|
|||
val actual = fetcher(params)
|
||||
|
||||
// Assert
|
||||
val expected = IllegalStateException("${this::class.simpleName} supports only multi-currency wallet").left()
|
||||
val expected = IllegalStateException(
|
||||
"${DefaultMultiWalletCryptoCurrenciesFetcher::class.simpleName} supports only multi-currency wallet",
|
||||
).left()
|
||||
assertEither(actual, expected)
|
||||
|
||||
verifyOrder { userWalletsStore.getSyncStrict(key = params.userWalletId) }
|
||||
|
|
@ -123,7 +125,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
|
|||
every { userWalletsStore.getSyncStrict(params.userWalletId) } returns mockUserWallet
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(userWalletId = params.userWalletId) } returns null
|
||||
every {
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(mockUserWallet.scanResponse)
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(mockUserWallet)
|
||||
} returns defaultCoins
|
||||
coEvery {
|
||||
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse)
|
||||
|
|
@ -139,7 +141,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
|
|||
coVerifyOrder {
|
||||
userWalletsStore.getSyncStrict(key = params.userWalletId)
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId = params.userWalletId)
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(scanResponse = mockUserWallet.scanResponse)
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(mockUserWallet)
|
||||
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse)
|
||||
userTokensSaver.store(userWalletId = params.userWalletId, response = userTokensResponse)
|
||||
expressServiceLoader.update(userWallet = mockUserWallet, userTokens = userTokensResponse.toLeastTokens())
|
||||
|
|
@ -185,7 +187,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
|
|||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(scanResponse = any())
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(any())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -227,7 +229,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
|
|||
|
||||
coVerify(inverse = true) {
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId = any())
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(scanResponse = any())
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(any())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -265,7 +267,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
|
|||
coEvery { tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue) } returns apiResponse
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId) } returns null
|
||||
coEvery {
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(mockUserWallet.scanResponse)
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(mockUserWallet)
|
||||
} returns defaultCoins
|
||||
coEvery {
|
||||
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse)
|
||||
|
|
@ -333,7 +335,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
|
|||
|
||||
coVerify(inverse = true) {
|
||||
userTokensSaver.push(userWalletId = any(), response = any())
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(scanResponse = any())
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(any())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -375,7 +377,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
|
|||
coEvery { tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue) } returns apiResponse
|
||||
coEvery { userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId) } returns null
|
||||
coEvery {
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(mockUserWallet.scanResponse)
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(mockUserWallet)
|
||||
} returns defaultCoins
|
||||
coEvery {
|
||||
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse)
|
||||
|
|
@ -392,7 +394,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
|
|||
userWalletsStore.getSyncStrict(key = params.userWalletId)
|
||||
tangemTechApi.getUserTokens(userId = params.userWalletId.stringValue)
|
||||
userTokensResponseStore.getSyncOrNull(userWalletId = userWalletId)
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(mockUserWallet.scanResponse)
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(mockUserWallet)
|
||||
userTokensSaver.push(userWalletId = params.userWalletId, response = userTokensResponse)
|
||||
customTokensMerger.mergeIfPresented(userWalletId = params.userWalletId, response = userTokensResponse)
|
||||
userTokensSaver.store(userWalletId = params.userWalletId, response = userTokensResponse)
|
||||
|
|
@ -445,7 +447,7 @@ internal class DefaultMultiWalletCryptoCurrenciesFetcherTest {
|
|||
}
|
||||
|
||||
coVerify(inverse = true) {
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(scanResponse = any())
|
||||
cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyWallet(any())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
|
|||
}
|
||||
|
||||
verify(inverse = true) {
|
||||
responseCryptoCurrenciesFactory.createCurrencies(response = any(), scanResponse = any())
|
||||
responseCryptoCurrenciesFactory.createCurrencies(response = any(), userWallet = any())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -114,14 +114,14 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
|
|||
every {
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = userTokensResponse,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
} returns cryptoCurrencies.toList()
|
||||
|
||||
every {
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = updatedUserTokensResponse,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
} returns updatedCryptoCurrencies.toList()
|
||||
|
||||
|
|
@ -143,7 +143,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
|
|||
userTokensResponseStore.get(params.userWalletId)
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = userTokensResponse,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -161,7 +161,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
|
|||
verifyOrder {
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = updatedUserTokensResponse,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -185,7 +185,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
|
|||
every {
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = userTokensResponse,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
} returns cryptoCurrencies.toList()
|
||||
|
||||
|
|
@ -207,7 +207,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
|
|||
userTokensResponseStore.get(params.userWalletId)
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = userTokensResponse,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -251,7 +251,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
|
|||
every {
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = userTokensResponse,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
} returns cryptoCurrencies.toList()
|
||||
|
||||
|
|
@ -282,7 +282,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
|
|||
verifyOrder {
|
||||
responseCryptoCurrenciesFactory.createCurrencies(
|
||||
response = userTokensResponse,
|
||||
scanResponse = userWallet.scanResponse,
|
||||
userWallet = userWallet,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -307,7 +307,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
|
|||
}
|
||||
|
||||
verify(inverse = true) {
|
||||
responseCryptoCurrenciesFactory.createCurrencies(response = any(), scanResponse = any())
|
||||
responseCryptoCurrenciesFactory.createCurrencies(response = any(), userWallet = any())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -335,7 +335,7 @@ internal class DefaultMultiWalletCryptoCurrenciesProducerTest {
|
|||
|
||||
verify(inverse = true) {
|
||||
userTokensResponseStore.get(any())
|
||||
responseCryptoCurrenciesFactory.createCurrencies(response = any(), scanResponse = any())
|
||||
responseCryptoCurrenciesFactory.createCurrencies(response = any(), userWallet = any())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import java.math.BigInteger
|
|||
internal class DefaultTransactionRepository(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val walletManagersStore: WalletManagersStore,
|
||||
private val coroutineDispatcherProvider: CoroutineDispatcherProvider,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : TransactionRepository {
|
||||
|
||||
override suspend fun createTransaction(
|
||||
|
|
@ -47,7 +47,7 @@ internal class DefaultTransactionRepository(
|
|||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
txExtras: TransactionExtras?,
|
||||
): TransactionData.Uncompiled = withContext(coroutineDispatcherProvider.io) {
|
||||
): TransactionData.Uncompiled = withContext(dispatchers.io) {
|
||||
val blockchain = network.toBlockchain()
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
|
|
@ -84,7 +84,7 @@ internal class DefaultTransactionRepository(
|
|||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
nonce: BigInteger?,
|
||||
): TransactionData.Uncompiled = withContext(coroutineDispatcherProvider.io) {
|
||||
): TransactionData.Uncompiled = withContext(dispatchers.io) {
|
||||
val blockchain = network.toBlockchain()
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
|
|
@ -138,7 +138,7 @@ internal class DefaultTransactionRepository(
|
|||
spenderAddress: String,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): TransactionData.Uncompiled = withContext(coroutineDispatcherProvider.io) {
|
||||
): TransactionData.Uncompiled = withContext(dispatchers.io) {
|
||||
val blockchain = network.toBlockchain()
|
||||
|
||||
val extras = createTransactionDataExtras(
|
||||
|
|
@ -171,7 +171,7 @@ internal class DefaultTransactionRepository(
|
|||
destinationAddress: String,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): TransactionData.Uncompiled = withContext(coroutineDispatcherProvider.io) {
|
||||
): TransactionData.Uncompiled = withContext(dispatchers.io) {
|
||||
val blockchain = network.toBlockchain()
|
||||
|
||||
// For now transfer one nft asset at a time
|
||||
|
|
@ -229,7 +229,7 @@ internal class DefaultTransactionRepository(
|
|||
destination: String,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): Result<Unit> = withContext(coroutineDispatcherProvider.io) {
|
||||
): Result<Unit> = withContext(dispatchers.io) {
|
||||
val blockchain = network.toBlockchain()
|
||||
val walletManager = walletManagersStore.getSyncOrNull(
|
||||
userWalletId = userWalletId,
|
||||
|
|
@ -260,7 +260,7 @@ internal class DefaultTransactionRepository(
|
|||
signer: TransactionSigner,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
) = withContext(coroutineDispatcherProvider.io) {
|
||||
) = withContext(dispatchers.io) {
|
||||
val blockchain = network.toBlockchain()
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
|
|
@ -276,7 +276,7 @@ internal class DefaultTransactionRepository(
|
|||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
sendMode: TransactionSender.MultipleTransactionSendMode,
|
||||
) = withContext(coroutineDispatcherProvider.io) {
|
||||
) = withContext(dispatchers.io) {
|
||||
val blockchain = network.toBlockchain()
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
|
|
@ -366,13 +366,9 @@ internal class DefaultTransactionRepository(
|
|||
signer: TransactionSigner,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): Result<ByteArray> = withContext(coroutineDispatcherProvider.io) {
|
||||
) = withContext(dispatchers.io) {
|
||||
val preparer = getPreparer(network, userWalletId)
|
||||
|
||||
when (val prepareForSend = preparer.prepareForSend(transactionData, signer)) {
|
||||
is com.tangem.blockchain.extensions.Result.Failure -> Result.failure(prepareForSend.error)
|
||||
is com.tangem.blockchain.extensions.Result.Success -> Result.success(prepareForSend.data)
|
||||
}
|
||||
preparer.prepareForSend(transactionData, signer)
|
||||
}
|
||||
|
||||
override suspend fun prepareForSendMultiple(
|
||||
|
|
@ -380,13 +376,9 @@ internal class DefaultTransactionRepository(
|
|||
signer: TransactionSigner,
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
): Result<List<ByteArray>> = withContext(coroutineDispatcherProvider.io) {
|
||||
) = withContext(dispatchers.io) {
|
||||
val preparer = getPreparer(network, userWalletId)
|
||||
|
||||
when (val prepareForSend = preparer.prepareForSendMultiple(transactionData, signer)) {
|
||||
is com.tangem.blockchain.extensions.Result.Failure -> Result.failure(prepareForSend.error)
|
||||
is com.tangem.blockchain.extensions.Result.Success -> Result.success(prepareForSend.data)
|
||||
}
|
||||
preparer.prepareForSendMultiple(transactionData, signer)
|
||||
}
|
||||
|
||||
private suspend fun getPreparer(network: Network, userWalletId: UserWalletId): TransactionPreparer {
|
||||
|
|
|
|||
|
|
@ -24,12 +24,12 @@ internal object TransactionDataModule {
|
|||
fun providesTransactionRepository(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
walletManagersStore: WalletManagersStore,
|
||||
coroutineDispatcherProvider: CoroutineDispatcherProvider,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): TransactionRepository {
|
||||
return DefaultTransactionRepository(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
walletManagersStore = walletManagersStore,
|
||||
coroutineDispatcherProvider = coroutineDispatcherProvider,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -166,7 +166,9 @@ internal class DefaultVisaActivationRepository @AssistedInject constructor(
|
|||
val rsaPublicKey = visaLibLoader.getOrCreateConfig().rsaPublicKey
|
||||
return when (env) {
|
||||
ApiEnvironment.DEV,
|
||||
ApiEnvironment.DEV_2,
|
||||
ApiEnvironment.STAGE,
|
||||
ApiEnvironment.MOCK,
|
||||
-> rsaPublicKey.dev
|
||||
ApiEnvironment.PROD -> rsaPublicKey.prod
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ dependencies {
|
|||
/* Other */
|
||||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.jodatime)
|
||||
implementation(projects.domain.blockaid)
|
||||
implementation(projects.domain.blockaid.models)
|
||||
implementation(projects.libs.crypto)
|
||||
|
|
|
|||
|
|
@ -16,9 +16,12 @@ import com.tangem.data.walletconnect.respond.DefaultWcRespondService
|
|||
import com.tangem.data.walletconnect.respond.WcRespondService
|
||||
import com.tangem.data.walletconnect.sessions.DefaultWcSessionsManager
|
||||
import com.tangem.data.walletconnect.utils.WcNamespaceConverter
|
||||
import com.tangem.data.walletconnect.utils.WcNetworksConverter
|
||||
import com.tangem.datasource.di.SdkMoshi
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.datasource.local.walletconnect.WalletConnectStore
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.walletconnect.WcPairService
|
||||
import com.tangem.domain.walletconnect.WcRequestService
|
||||
|
|
@ -70,7 +73,9 @@ internal object WalletConnectDataModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun defaultWcPairUseCase(): WcPairService = DefaultWcPairService()
|
||||
fun defaultWcPairUseCase(sessionsManager: DefaultWcSessionsManager): WcPairService = DefaultWcPairService(
|
||||
sessionsManager,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
|
|
@ -87,7 +92,8 @@ internal object WalletConnectDataModule {
|
|||
dispatchers: CoroutineDispatcherProvider,
|
||||
legacyStore: WalletConnectSessionsRepository,
|
||||
getWallets: GetWalletsUseCase,
|
||||
associateNetworks: AssociateNetworksDelegate,
|
||||
wcNetworksConverter: WcNetworksConverter,
|
||||
analytics: AnalyticsEventHandler,
|
||||
): DefaultWcSessionsManager {
|
||||
val scope = CoroutineScope(SupervisorJob() + dispatchers.io)
|
||||
return DefaultWcSessionsManager(
|
||||
|
|
@ -95,7 +101,8 @@ internal object WalletConnectDataModule {
|
|||
dispatchers = dispatchers,
|
||||
legacyStore = legacyStore,
|
||||
getWallets = getWallets,
|
||||
associateNetworks = associateNetworks,
|
||||
wcNetworksConverter = wcNetworksConverter,
|
||||
analytics = analytics,
|
||||
scope = scope,
|
||||
)
|
||||
}
|
||||
|
|
@ -127,11 +134,11 @@ internal object WalletConnectDataModule {
|
|||
@SdkMoshi moshi: Moshi,
|
||||
sessionsManager: WcSessionsManager,
|
||||
factories: WcEthNetwork.Factories,
|
||||
namespaceConverter: WcEthNetwork.NamespaceConverter,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
wcNetworksConverter: WcNetworksConverter,
|
||||
): WcEthNetwork = WcEthNetwork(
|
||||
moshi = moshi,
|
||||
namespaceConverter = namespaceConverter,
|
||||
networksConverter = wcNetworksConverter,
|
||||
sessionsManager = sessionsManager,
|
||||
factories = factories,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
|
|
@ -141,7 +148,7 @@ internal object WalletConnectDataModule {
|
|||
@Singleton
|
||||
fun wcSolanaNetwork(
|
||||
@SdkMoshi moshi: Moshi,
|
||||
namespaceConverter: WcSolanaNetwork.NamespaceConverter,
|
||||
wcNetworksConverter: WcNetworksConverter,
|
||||
sessionsManager: WcSessionsManager,
|
||||
factories: WcSolanaNetwork.Factories,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
|
|
@ -149,7 +156,7 @@ internal object WalletConnectDataModule {
|
|||
moshi = moshi,
|
||||
sessionsManager = sessionsManager,
|
||||
factories = factories,
|
||||
namespaceConverter = namespaceConverter,
|
||||
networksConverter = wcNetworksConverter,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
)
|
||||
|
||||
|
|
@ -158,9 +165,27 @@ internal object WalletConnectDataModule {
|
|||
fun caipNamespaceDelegate(
|
||||
namespaceConverters: Set<@JvmSuppressWildcards WcNamespaceConverter>,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
wcNetworksConverter: WcNetworksConverter,
|
||||
): CaipNamespaceDelegate = CaipNamespaceDelegate(
|
||||
namespaceConverters = namespaceConverters,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
wcNetworksConverter = wcNetworksConverter,
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun wcNetworksConverter(
|
||||
namespaceConverters: Set<@JvmSuppressWildcards WcNamespaceConverter>,
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): WcNetworksConverter = WcNetworksConverter(
|
||||
namespaceConverters = namespaceConverters,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
currenciesRepository = currenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
@ -169,10 +194,14 @@ internal object WalletConnectDataModule {
|
|||
namespaceConverters: Set<@JvmSuppressWildcards WcNamespaceConverter>,
|
||||
getWallets: GetWalletsUseCase,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles: TokensFeatureToggles,
|
||||
): AssociateNetworksDelegate = AssociateNetworksDelegate(
|
||||
namespaceConverters = namespaceConverters,
|
||||
getWallets = getWallets,
|
||||
currenciesRepository = currenciesRepository,
|
||||
multiWalletCryptoCurrenciesSupplier = multiWalletCryptoCurrenciesSupplier,
|
||||
tokensFeatureToggles = tokensFeatureToggles,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ internal class DefaultWcInitializeUseCase(
|
|||
application = application,
|
||||
metaData = appMetaData,
|
||||
) { error ->
|
||||
Timber.e("Error while initializing client: $error")
|
||||
Timber.tag(WC_TAG).e("Error while initializing client: $error")
|
||||
}
|
||||
|
||||
WalletKit.initialize(
|
||||
|
|
@ -60,7 +60,7 @@ internal class DefaultWcInitializeUseCase(
|
|||
Timber.tag(WC_TAG).i("onWcSdkInit")
|
||||
},
|
||||
onError = { error ->
|
||||
Timber.e("Error while initializing Web3Wallet: $error")
|
||||
Timber.tag(WC_TAG).e("Error while initializing Web3Wallet: $error")
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.tangem.data.walletconnect.respond.WcRespondService
|
|||
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.walletconnect.model.WcEthMethod
|
||||
import com.tangem.domain.walletconnect.model.WcRequestError
|
||||
import com.tangem.domain.walletconnect.model.WcSession
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcAddNetworkUseCase
|
||||
|
|
@ -27,7 +28,7 @@ internal class WcEthAddNetworkUseCase @AssistedInject constructor(
|
|||
override val walletAddress: String
|
||||
get() = context.accountAddress
|
||||
|
||||
override suspend fun approve(): Either<Throwable, Unit> {
|
||||
override suspend fun approve(): Either<WcRequestError, String> {
|
||||
return respondService.respond(rawSdkRequest, "")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
|
|||
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
|
||||
import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate
|
||||
import com.tangem.domain.transaction.usecase.SignUseCase
|
||||
import com.tangem.domain.walletconnect.error.parseTangemSdkError
|
||||
import com.tangem.domain.walletconnect.model.WcEthMethod
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcMessageSignUseCase
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcSignState
|
||||
|
|
@ -59,7 +60,7 @@ internal class WcEthMessageSignUseCase @AssistedInject constructor(
|
|||
?: return
|
||||
|
||||
val signedHash = signUseCase(hashToSign, userWallet, network)
|
||||
.onLeft { emit(state.toResult(it.left())) }
|
||||
.onLeft { emit(state.toResult(parseTangemSdkError(it).left())) }
|
||||
.getOrNull() ?: return
|
||||
|
||||
val respond = prepareToSendMessageData(signedHash, hashToSign, walletManager)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
package com.tangem.data.walletconnect.network.ethereum
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
|
|
@ -10,6 +14,7 @@ import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter
|
|||
import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter.Companion.fromJson
|
||||
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
|
||||
import com.tangem.data.walletconnect.utils.WcNamespaceConverter
|
||||
import com.tangem.data.walletconnect.utils.WcNetworksConverter
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.walletconnect.model.*
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
|
|
@ -23,7 +28,7 @@ internal class WcEthNetwork(
|
|||
private val moshi: Moshi,
|
||||
private val sessionsManager: WcSessionsManager,
|
||||
private val factories: Factories,
|
||||
private val namespaceConverter: NamespaceConverter,
|
||||
private val networksConverter: WcNetworksConverter,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
) : WcRequestToUseCaseConverter {
|
||||
|
||||
|
|
@ -34,23 +39,43 @@ internal class WcEthNetwork(
|
|||
}
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
override suspend fun toUseCase(request: WcSdkSessionRequest): WcMethodUseCase? {
|
||||
val name = toWcMethodName(request) ?: return null
|
||||
val session = sessionsManager.findSessionByTopic(request.topic) ?: return null
|
||||
val method: WcEthMethod = name.toMethod(request, session.wallet) ?: return null
|
||||
val network = namespaceConverter.toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null
|
||||
val walletManagerAddress = walletManagersFacade.getDefaultAddress(session.wallet.walletId, network).orEmpty()
|
||||
override suspend fun toUseCase(
|
||||
request: WcSdkSessionRequest,
|
||||
): Either<WcRequestError.HandleMethodError, WcMethodUseCase> {
|
||||
fun error(message: String) = WcRequestError.HandleMethodError(message).left()
|
||||
val name = toWcMethodName(request) ?: return error("Unknown method name")
|
||||
val session = sessionsManager.findSessionByTopic(request.topic)
|
||||
?: return error("Failed to find session for topic ${request.topic}")
|
||||
val wallet = session.wallet
|
||||
val chainId = request.chainId.orEmpty()
|
||||
val method: WcEthMethod = name.toMethod(request, wallet)
|
||||
.getOrElse { return error(it.message.orEmpty()) }
|
||||
?: return error("Failed to parse $name")
|
||||
suspend fun anyExistNetwork() = networksConverter.mainOrAnyWalletNetworkForRequest(chainId, wallet)
|
||||
|
||||
val accountAddress = when (method) {
|
||||
is WcEthMethod.MessageSign -> method.account
|
||||
is WcEthMethod.SendTransaction -> method.transaction.from
|
||||
is WcEthMethod.SignTransaction -> method.transaction.from
|
||||
is WcEthMethod.SignTypedData -> method.account
|
||||
is WcEthMethod.AddEthereumChain -> walletManagerAddress
|
||||
is WcEthMethod.AddEthereumChain ->
|
||||
anyExistNetwork()
|
||||
?.let { network -> walletManagersFacade.getDefaultAddress(wallet.walletId, network).orEmpty() }
|
||||
.orEmpty()
|
||||
}
|
||||
val walletNetwork = when (method) {
|
||||
is WcEthMethod.SignTypedData,
|
||||
is WcEthMethod.MessageSign,
|
||||
is WcEthMethod.SendTransaction,
|
||||
is WcEthMethod.SignTransaction,
|
||||
-> networksConverter.findWalletNetworkForRequest(request, session, accountAddress)
|
||||
is WcEthMethod.AddEthereumChain -> anyExistNetwork()
|
||||
} ?: return error("Failed to find walletNetwork for accountAddress $accountAddress")
|
||||
|
||||
val context = WcMethodUseCaseContext(
|
||||
session = session,
|
||||
rawSdkRequest = request,
|
||||
network = network,
|
||||
network = walletNetwork,
|
||||
accountAddress = accountAddress,
|
||||
)
|
||||
return when (method) {
|
||||
|
|
@ -59,10 +84,13 @@ internal class WcEthNetwork(
|
|||
is WcEthMethod.SignTransaction -> factories.signTransaction.create(context, method)
|
||||
is WcEthMethod.SignTypedData -> factories.signTypedData.create(context, method)
|
||||
is WcEthMethod.AddEthereumChain -> factories.addNetwork.create(context, method)
|
||||
}
|
||||
}.right()
|
||||
}
|
||||
|
||||
private fun WcEthMethodName.toMethod(request: WcSdkSessionRequest, wallet: UserWallet): WcEthMethod? {
|
||||
private suspend fun WcEthMethodName.toMethod(
|
||||
request: WcSdkSessionRequest,
|
||||
wallet: UserWallet,
|
||||
): Either<Throwable, WcEthMethod?> {
|
||||
val rawParams = request.request.params
|
||||
return when (this) {
|
||||
WcEthMethodName.EthSign,
|
||||
|
|
@ -74,39 +102,48 @@ internal class WcEthNetwork(
|
|||
WcEthMethodName.SignTransaction,
|
||||
WcEthMethodName.SendTransaction,
|
||||
-> moshi.fromJson<List<WcEthTransactionParams>>(rawParams)
|
||||
.getOrElse { return it.left() }
|
||||
?.firstOrNull()
|
||||
?.let {
|
||||
if (this == WcEthMethodName.SignTransaction) {
|
||||
WcEthMethod.SignTransaction(transaction = it)
|
||||
WcEthMethod.SignTransaction(transaction = it).right()
|
||||
} else {
|
||||
WcEthMethod.SendTransaction(transaction = it)
|
||||
WcEthMethod.SendTransaction(transaction = it).right()
|
||||
}
|
||||
}
|
||||
?: return null.right()
|
||||
WcEthMethodName.AddEthereumChain -> moshi.fromJson<List<WcEthAddChain>>(rawParams)
|
||||
.getOrElse { return it.left() }
|
||||
?.firstOrNull()
|
||||
?.let {
|
||||
val newNetwork = namespaceConverter
|
||||
.toNetwork(it.chainId, wallet) ?: return null
|
||||
WcEthMethod.AddEthereumChain(rawChain = it, network = newNetwork)
|
||||
val newNetwork = networksConverter
|
||||
.mainOrAnyWalletNetworkForRequest(it.chainId, wallet)
|
||||
?: return null.right()
|
||||
WcEthMethod.AddEthereumChain(rawChain = it, network = newNetwork).right()
|
||||
}
|
||||
?: null.right()
|
||||
}
|
||||
}
|
||||
|
||||
private fun WcEthMethodName.parseMessageSign(rawParams: String): WcEthMethod.MessageSign? {
|
||||
val list = moshi.fromJson<List<String>>(rawParams) ?: return null
|
||||
private fun WcEthMethodName.parseMessageSign(rawParams: String): Either<Throwable, WcEthMethod.MessageSign?> {
|
||||
val list = moshi.fromJson<List<String>>(rawParams)
|
||||
.getOrElse { return it.left() }
|
||||
?: return null.right()
|
||||
val accountIndex = if (this == WcEthMethodName.EthSign) 0 else 1
|
||||
val messageIndex = if (this == WcEthMethodName.EthSign) 1 else 0
|
||||
val account = list.getOrNull(accountIndex) ?: return null
|
||||
val message = list.getOrNull(messageIndex) ?: return null
|
||||
val account = list.getOrNull(accountIndex) ?: return null.right()
|
||||
val message = list.getOrNull(messageIndex) ?: return null.right()
|
||||
val humanMsg = LegacySdkHelper.hexToAscii(message).orEmpty()
|
||||
return WcEthMethod.MessageSign(account = account, rawMessage = message, humanMsg = humanMsg)
|
||||
return WcEthMethod.MessageSign(account = account, rawMessage = message, humanMsg = humanMsg).right()
|
||||
}
|
||||
|
||||
private fun parseTypeData(params: String): WcEthMethod.SignTypedData? {
|
||||
private fun parseTypeData(params: String): Either<Throwable, WcEthMethod.SignTypedData?> {
|
||||
val account = params.substring(params.indexOf("\"") + 1, params.indexOf("\"", startIndex = 2))
|
||||
val data = params.substring(params.indexOfFirst { it == '{' }, params.indexOfLast { it == '}' } + 1)
|
||||
val parsedParams = moshi.fromJson<WcEthSignTypedDataParams>(data) ?: return null
|
||||
return WcEthMethod.SignTypedData(params = parsedParams, account = account, dataForSign = data)
|
||||
val parsedParams = moshi.fromJson<WcEthSignTypedDataParams>(data)
|
||||
.getOrElse { return it.left() }
|
||||
?: return null.right()
|
||||
return WcEthMethod.SignTypedData(params = parsedParams, account = account, dataForSign = data).right()
|
||||
}
|
||||
|
||||
internal class NamespaceConverter(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.data.walletconnect.network.ethereum
|
|||
import arrow.core.left
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.ethereum.tokenmethods.ApprovalERC20TokenCallData
|
||||
import com.tangem.blockchain.common.Amount as BlockchainAmount
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.extensions.formatHex
|
||||
|
|
@ -17,6 +16,7 @@ import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate
|
|||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.tokens.model.Amount
|
||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.walletconnect.error.parseSendError
|
||||
import com.tangem.domain.walletconnect.model.WcApprovedAmount
|
||||
import com.tangem.domain.walletconnect.model.WcEthMethod
|
||||
import com.tangem.domain.walletconnect.usecase.method.*
|
||||
|
|
@ -24,13 +24,16 @@ import dagger.assisted.Assisted
|
|||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.*
|
||||
import com.tangem.blockchain.common.Amount as BlockchainAmount
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class WcEthSendTransactionUseCase @AssistedInject constructor(
|
||||
@Assisted override val context: WcMethodUseCaseContext,
|
||||
@Assisted override val method: WcEthMethod.SendTransaction,
|
||||
override val respondService: WcRespondService,
|
||||
override val analytics: AnalyticsEventHandler,
|
||||
private val sendTransaction: SendTransactionUseCase,
|
||||
private val ethTxHelper: WcEthTxHelper,
|
||||
blockAidDelegate: BlockAidVerificationDelegate,
|
||||
) : BaseWcSignUseCase<WcEthTxAction, TransactionData>(),
|
||||
WcTransactionUseCase,
|
||||
|
|
@ -38,10 +41,7 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor(
|
|||
WcMutableFee {
|
||||
|
||||
private var approvalAmount: WcApprovedAmount? = null
|
||||
private var dAppFee = WcEthTxHelper.getDAppFee(
|
||||
network = context.network,
|
||||
txParams = method.transaction,
|
||||
)
|
||||
private var dAppFee: Fee? = null
|
||||
|
||||
override val securityStatus: LceFlow<Throwable, BlockAidTransactionCheck.Result> =
|
||||
blockAidDelegate.getSecurityStatus(
|
||||
|
|
@ -52,7 +52,7 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor(
|
|||
accountAddress = context.accountAddress,
|
||||
).map { lce ->
|
||||
lce.map { result ->
|
||||
val amount = WcEthTxHelper.getApprovedAmount(method.transaction.data, result)
|
||||
val amount = ethTxHelper.getApprovedAmount(method.transaction.data, result)
|
||||
?: return@map BlockAidTransactionCheck.Result.Plain(result)
|
||||
val tokenInfo = amount.tokenInfo
|
||||
this@WcEthSendTransactionUseCase.approvalAmount = WcApprovedAmount(
|
||||
|
|
@ -80,8 +80,7 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor(
|
|||
override suspend fun SignCollector<TransactionData>.onSign(state: WcSignState<TransactionData>) {
|
||||
val hash = sendTransaction(state.signModel, wallet, network)
|
||||
.onLeft { error ->
|
||||
val sendError = IllegalArgumentException(error.toString()) // todo(wc) use domain error
|
||||
emit(state.toResult(sendError.left()))
|
||||
emit(state.toResult(parseSendError(error).left()))
|
||||
}
|
||||
.getOrNull() ?: return
|
||||
val respondResult = respondService.respond(rawSdkRequest, hash.formatHex())
|
||||
|
|
@ -112,7 +111,7 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor(
|
|||
emit(newState)
|
||||
}
|
||||
|
||||
override fun dAppFee(): Fee.Ethereum.Legacy? {
|
||||
override fun dAppFee(): Fee? {
|
||||
return dAppFee
|
||||
}
|
||||
|
||||
|
|
@ -121,8 +120,9 @@ internal class WcEthSendTransactionUseCase @AssistedInject constructor(
|
|||
}
|
||||
|
||||
override fun invoke(): Flow<WcSignState<TransactionData>> = flow {
|
||||
val transactionData = WcEthTxHelper.createTransactionData(
|
||||
dAppFee = dAppFee(),
|
||||
dAppFee = ethTxHelper.getDAppFee(method.transaction, wallet, network)
|
||||
val transactionData = ethTxHelper.createTransactionData(
|
||||
dAppFee = dAppFee,
|
||||
network = context.network,
|
||||
txParams = method.transaction,
|
||||
) ?: return@flow
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.data.walletconnect.network.ethereum
|
|||
import arrow.core.left
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.ethereum.tokenmethods.ApprovalERC20TokenCallData
|
||||
import com.tangem.blockchain.common.Amount as BlockchainAmount
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.extensions.formatHex
|
||||
|
|
@ -17,22 +16,22 @@ import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
|
|||
import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate
|
||||
import com.tangem.domain.tokens.model.Amount
|
||||
import com.tangem.domain.transaction.usecase.PrepareForSendUseCase
|
||||
import com.tangem.domain.walletconnect.error.parseSendError
|
||||
import com.tangem.domain.walletconnect.model.WcApprovedAmount
|
||||
import com.tangem.domain.walletconnect.model.WcEthMethod
|
||||
import com.tangem.domain.walletconnect.usecase.method.*
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.FlowCollector
|
||||
import kotlinx.coroutines.flow.emitAll
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.*
|
||||
import com.tangem.blockchain.common.Amount as BlockchainAmount
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class WcEthSignTransactionUseCase @AssistedInject constructor(
|
||||
override val respondService: WcRespondService,
|
||||
override val analytics: AnalyticsEventHandler,
|
||||
private val prepareForSend: PrepareForSendUseCase,
|
||||
private val ethTxHelper: WcEthTxHelper,
|
||||
@Assisted override val context: WcMethodUseCaseContext,
|
||||
@Assisted override val method: WcEthMethod.SignTransaction,
|
||||
blockAidDelegate: BlockAidVerificationDelegate,
|
||||
|
|
@ -42,10 +41,7 @@ internal class WcEthSignTransactionUseCase @AssistedInject constructor(
|
|||
WcMutableFee {
|
||||
|
||||
private var approvalAmount: WcApprovedAmount? = null
|
||||
private var dAppFee = WcEthTxHelper.getDAppFee(
|
||||
network = context.network,
|
||||
txParams = method.transaction,
|
||||
)
|
||||
private var dAppFee: Fee? = null
|
||||
|
||||
override val securityStatus = blockAidDelegate.getSecurityStatus(
|
||||
network = network,
|
||||
|
|
@ -55,7 +51,7 @@ internal class WcEthSignTransactionUseCase @AssistedInject constructor(
|
|||
accountAddress = context.accountAddress,
|
||||
).map { lce ->
|
||||
lce.map { result ->
|
||||
val amount = WcEthTxHelper.getApprovedAmount(method.transaction.data, result)
|
||||
val amount = ethTxHelper.getApprovedAmount(method.transaction.data, result)
|
||||
?: return@map BlockAidTransactionCheck.Result.Plain(result)
|
||||
val tokenInfo = amount.tokenInfo
|
||||
if (!amount.isUnlimited) {
|
||||
|
|
@ -82,7 +78,7 @@ internal class WcEthSignTransactionUseCase @AssistedInject constructor(
|
|||
val hash = prepareForSend(state.signModel, wallet, network)
|
||||
.map { it.toHexString().formatHex() }
|
||||
.onLeft { error ->
|
||||
emit(state.toResult(error.left()))
|
||||
emit(state.toResult(parseSendError(error).left()))
|
||||
}
|
||||
.getOrNull()
|
||||
?: return
|
||||
|
|
@ -119,15 +115,16 @@ internal class WcEthSignTransactionUseCase @AssistedInject constructor(
|
|||
}
|
||||
|
||||
override fun invoke(): Flow<WcSignState<TransactionData>> = flow {
|
||||
val transactionData = WcEthTxHelper.createTransactionData(
|
||||
dAppFee = dAppFee(),
|
||||
dAppFee = ethTxHelper.getDAppFee(method.transaction, wallet, network)
|
||||
val transactionData = ethTxHelper.createTransactionData(
|
||||
dAppFee = dAppFee,
|
||||
network = context.network,
|
||||
txParams = method.transaction,
|
||||
) ?: return@flow
|
||||
emitAll(delegate.invoke(transactionData))
|
||||
}
|
||||
|
||||
override fun dAppFee(): Fee.Ethereum.Legacy? {
|
||||
override fun dAppFee(): Fee? {
|
||||
return dAppFee
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
|
|||
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
|
||||
import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate
|
||||
import com.tangem.domain.transaction.usecase.SignUseCase
|
||||
import com.tangem.domain.walletconnect.error.parseTangemSdkError
|
||||
import com.tangem.domain.walletconnect.model.WcEthMethod
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcMessageSignUseCase
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcSignState
|
||||
|
|
@ -51,7 +52,7 @@ internal class WcEthSignTypedDataUseCase @AssistedInject constructor(
|
|||
?: return
|
||||
|
||||
val signedHash = signUseCase(hashToSign, userWallet, network)
|
||||
.onLeft { emit(state.toResult(it.left())) }
|
||||
.onLeft { emit(state.toResult(parseTangemSdkError(it).left())) }
|
||||
.getOrNull() ?: return
|
||||
|
||||
val respond = prepareToSendMessageData(signedHash, hashToSign, walletManager)
|
||||
|
|
|
|||
|
|
@ -7,40 +7,43 @@ import com.domain.blockaid.models.transaction.simultation.SimulationData
|
|||
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
|
||||
import com.tangem.blockchain.blockchains.ethereum.tokenmethods.ApprovalERC20TokenCallData
|
||||
import com.tangem.blockchain.common.Amount
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.HEX_PREFIX
|
||||
import com.tangem.blockchain.common.TransactionData
|
||||
import com.tangem.blockchain.common.smartcontract.CompiledSmartContractCallData
|
||||
import com.tangem.blockchain.common.transaction.Fee
|
||||
import com.tangem.blockchain.extensions.hexToBigDecimal
|
||||
import com.tangem.blockchain.extensions.hexToBigInteger
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.blockchainsdk.utils.toCoinId
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.data.common.currency.getCoinId
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.transaction.usecase.GetEthSpecificFeeUseCase
|
||||
import com.tangem.domain.walletconnect.model.WcApprovedAmount
|
||||
import com.tangem.domain.walletconnect.model.WcEthTransactionParams
|
||||
import java.math.BigDecimal
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import javax.inject.Inject
|
||||
|
||||
internal object WcEthTxHelper {
|
||||
private val MANTLE_FEE_ESTIMATE_MULTIPLIER = BigDecimal("1.8")
|
||||
internal class WcEthTxHelper @Inject constructor(
|
||||
private val getSingleCryptoCurrency: GetSingleCryptoCurrencyStatusUseCase,
|
||||
private val ethSpecificFee: GetEthSpecificFeeUseCase,
|
||||
) {
|
||||
|
||||
fun getDAppFee(network: Network, txParams: WcEthTransactionParams): Fee.Ethereum.Legacy? {
|
||||
val gasLimit = txParams.gas?.hexToBigDecimal() ?: return null
|
||||
val gasPrice = txParams.gasPrice?.hexToBigDecimal() ?: return null
|
||||
|
||||
val blockchain = network.toBlockchain()
|
||||
|
||||
var feeDecimal = (gasLimit * gasPrice)
|
||||
.movePointLeft(blockchain.decimals())
|
||||
if (blockchain == Blockchain.Mantle) {
|
||||
feeDecimal = feeDecimal.multiply(MANTLE_FEE_ESTIMATE_MULTIPLIER)
|
||||
}
|
||||
|
||||
val feeAmount = Amount(feeDecimal, blockchain)
|
||||
return Fee.Ethereum.Legacy(feeAmount, gasLimit.toBigInteger(), gasPrice.toBigInteger())
|
||||
suspend fun getDAppFee(txParams: WcEthTransactionParams, userWallet: UserWallet, network: Network): Fee? {
|
||||
val gasLimit = txParams.gas?.hexToBigInteger() ?: return null
|
||||
val gasPrice = txParams.gasPrice?.hexToBigInteger()
|
||||
val coinId = getCoinId(network, network.toBlockchain().toCoinId())
|
||||
val currency = getSingleCryptoCurrency.invokeMultiWalletSync(userWallet.walletId, coinId)
|
||||
.map { it.currency }
|
||||
.getOrNull() ?: return null
|
||||
return ethSpecificFee(userWallet, currency, gasLimit, gasPrice)
|
||||
.map { it.minimum }
|
||||
.getOrNull()
|
||||
}
|
||||
|
||||
fun createTransactionData(
|
||||
dAppFee: Fee.Ethereum.Legacy?,
|
||||
dAppFee: Fee?,
|
||||
network: Network,
|
||||
txParams: WcEthTransactionParams,
|
||||
): TransactionData.Uncompiled? {
|
||||
|
|
|
|||
|
|
@ -16,4 +16,7 @@ internal data class WcSolanaSignMessageRequest(
|
|||
internal data class WcSolanaSignTransactionRequest(
|
||||
@Json(name = "transaction")
|
||||
val transaction: String,
|
||||
|
||||
@Json(name = "feePayer")
|
||||
val feePayer: String?,
|
||||
)
|
||||
|
|
@ -12,6 +12,7 @@ import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
|
|||
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
import com.tangem.domain.transaction.usecase.SignUseCase
|
||||
import com.tangem.domain.walletconnect.error.parseTangemSdkError
|
||||
import com.tangem.domain.walletconnect.model.WcSolanaMethod
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcMessageSignUseCase
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcSignState
|
||||
|
|
@ -40,7 +41,7 @@ internal class WcSolanaMessageSignUseCase @AssistedInject constructor(
|
|||
val userWallet = session.wallet
|
||||
|
||||
val signedHash = signUseCase(hashToSign, userWallet, network)
|
||||
.onLeft { emit(state.toResult(it.left())) }
|
||||
.onLeft { emit(state.toResult(parseTangemSdkError(it).left())) }
|
||||
.getOrNull() ?: return
|
||||
|
||||
val respond = "{ signature: \"${signedHash.encodeBase58()}\" }"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
package com.tangem.data.walletconnect.network.solana
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.extensions.decodeBase58
|
||||
|
|
@ -12,8 +16,9 @@ import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter
|
|||
import com.tangem.data.walletconnect.request.WcRequestToUseCaseConverter.Companion.fromJson
|
||||
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
|
||||
import com.tangem.data.walletconnect.utils.WcNamespaceConverter
|
||||
import com.tangem.data.walletconnect.utils.WcNetworksConverter
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.walletconnect.model.WcSession
|
||||
import com.tangem.domain.walletconnect.model.WcRequestError
|
||||
import com.tangem.domain.walletconnect.model.WcSolanaMethod
|
||||
import com.tangem.domain.walletconnect.model.WcSolanaMethodName
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
|
|
@ -21,13 +26,12 @@ import com.tangem.domain.walletconnect.repository.WcSessionsManager
|
|||
import com.tangem.domain.walletconnect.usecase.method.WcMethodUseCase
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import jakarta.inject.Inject
|
||||
import timber.log.Timber
|
||||
|
||||
internal class WcSolanaNetwork(
|
||||
private val moshi: Moshi,
|
||||
private val sessionsManager: WcSessionsManager,
|
||||
private val factories: Factories,
|
||||
private val namespaceConverter: NamespaceConverter,
|
||||
private val networksConverter: WcNetworksConverter,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
) : WcRequestToUseCaseConverter {
|
||||
|
||||
|
|
@ -37,32 +41,45 @@ internal class WcSolanaNetwork(
|
|||
return name
|
||||
}
|
||||
|
||||
override suspend fun toUseCase(request: WcSdkSessionRequest): WcMethodUseCase? {
|
||||
val name = toWcMethodName(request) ?: return null
|
||||
val method: WcSolanaMethod = name.toMethod(request) ?: return null
|
||||
val session = sessionsManager.findSessionByTopic(request.topic) ?: return null
|
||||
val network = namespaceConverter.toNetwork(request.chainId.orEmpty(), session.wallet) ?: return null
|
||||
val accountAddress = getAccountAddress(session, network)
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
override suspend fun toUseCase(
|
||||
request: WcSdkSessionRequest,
|
||||
): Either<WcRequestError.HandleMethodError, WcMethodUseCase> {
|
||||
fun error(message: String) = WcRequestError.HandleMethodError(message).left()
|
||||
val name = toWcMethodName(request) ?: return error("Unknown method name")
|
||||
val method: WcSolanaMethod = name.toMethod(request)
|
||||
.getOrElse { return error(it.message.orEmpty()) }
|
||||
?: return error("Failed to parse $name")
|
||||
val session = sessionsManager.findSessionByTopic(request.topic)
|
||||
?: return error("Failed to find session for topic ${request.topic}")
|
||||
val wallet = session.wallet
|
||||
val chainId = request.chainId.orEmpty()
|
||||
suspend fun anyExistNetwork() = networksConverter.mainOrAnyWalletNetworkForRequest(chainId, wallet)
|
||||
suspend fun anyAddress() = anyExistNetwork()
|
||||
?.let { network -> walletManagersFacade.getDefaultAddress(wallet.walletId, network).orEmpty() }
|
||||
.orEmpty()
|
||||
|
||||
val accountAddress = when (method) {
|
||||
is WcSolanaMethod.SignAllTransaction -> anyAddress()
|
||||
is WcSolanaMethod.SignMessage -> anyAddress()
|
||||
is WcSolanaMethod.SignTransaction -> method.address ?: anyAddress()
|
||||
}
|
||||
val walletNetwork = networksConverter
|
||||
.findWalletNetworkForRequest(request, session, accountAddress)
|
||||
?: anyExistNetwork()
|
||||
?: return error("Failed to find walletNetwork for accountAddress $accountAddress")
|
||||
|
||||
val context = WcMethodUseCaseContext(
|
||||
session = session,
|
||||
rawSdkRequest = request,
|
||||
network = network,
|
||||
accountAddress = accountAddress.orEmpty(),
|
||||
network = walletNetwork,
|
||||
accountAddress = accountAddress,
|
||||
)
|
||||
return when (method) {
|
||||
is WcSolanaMethod.SignMessage -> factories.messageSign.create(context, method)
|
||||
is WcSolanaMethod.SignTransaction -> factories.signTransaction.create(context, method)
|
||||
is WcSolanaMethod.SignAllTransaction -> factories.signAllTransaction.create(context, method)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getAccountAddress(session: WcSession, network: Network): String? {
|
||||
return try {
|
||||
walletManagersFacade.getDefaultAddress(session.wallet.walletId, network)
|
||||
} catch (exception: Exception) {
|
||||
Timber.e(exception)
|
||||
null
|
||||
}
|
||||
}.right()
|
||||
}
|
||||
|
||||
internal class NamespaceConverter @Inject constructor(
|
||||
|
|
@ -95,23 +112,26 @@ internal class WcSolanaNetwork(
|
|||
}
|
||||
}
|
||||
|
||||
private fun WcSolanaMethodName.toMethod(request: WcSdkSessionRequest): WcSolanaMethod? {
|
||||
private fun WcSolanaMethodName.toMethod(request: WcSdkSessionRequest): Either<Throwable, WcSolanaMethod?> {
|
||||
val rawParams = request.request.params
|
||||
return when (this) {
|
||||
WcSolanaMethodName.SignMessage -> moshi.fromJson<WcSolanaSignMessageRequest>(rawParams)?.let { request ->
|
||||
val humanMsg = request.message.decodeBase58()?.toHexString().orEmpty()
|
||||
WcSolanaMethod.SignMessage(
|
||||
pubKey = request.publicKey,
|
||||
rawMessage = request.message,
|
||||
humanMsg = humanMsg,
|
||||
)
|
||||
}
|
||||
WcSolanaMethodName.SignMessage -> moshi.fromJson<WcSolanaSignMessageRequest>(rawParams)
|
||||
.getOrElse { return it.left() }
|
||||
?.let { request ->
|
||||
val humanMsg = request.message.decodeBase58()?.toHexString().orEmpty()
|
||||
WcSolanaMethod.SignMessage(
|
||||
pubKey = request.publicKey,
|
||||
rawMessage = request.message,
|
||||
humanMsg = humanMsg,
|
||||
)
|
||||
}
|
||||
WcSolanaMethodName.SignTransaction -> moshi.fromJson<WcSolanaSignTransactionRequest>(rawParams)
|
||||
?.let { request -> WcSolanaMethod.SignTransaction(request.transaction) }
|
||||
WcSolanaMethodName.SendAllTransaction -> moshi.fromJson<List<String>>(rawParams)?.let { list ->
|
||||
WcSolanaMethod.SignAllTransaction(list)
|
||||
}
|
||||
}
|
||||
.getOrElse { return it.left() }
|
||||
?.let { request -> WcSolanaMethod.SignTransaction(request.transaction, request.feePayer) }
|
||||
WcSolanaMethodName.SendAllTransaction -> moshi.fromJson<List<String>>(rawParams)
|
||||
.getOrElse { return it.left() }
|
||||
?.let { list -> WcSolanaMethod.SignAllTransaction(list) }
|
||||
}.right()
|
||||
}
|
||||
|
||||
internal class Factories @Inject constructor(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
|
|||
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
|
||||
import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate
|
||||
import com.tangem.domain.transaction.usecase.PrepareForSendUseCase
|
||||
import com.tangem.domain.walletconnect.error.parseSendError
|
||||
import com.tangem.domain.walletconnect.model.WcSolanaMethod
|
||||
import com.tangem.domain.walletconnect.usecase.method.BlockAidTransactionCheck
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcListTransactionUseCase
|
||||
|
|
@ -47,7 +48,7 @@ internal class WcSolanaSignAllTransactionUseCase @AssistedInject constructor(
|
|||
override suspend fun SignCollector<List<TransactionData>>.onSign(state: WcSignState<List<TransactionData>>) {
|
||||
val hash = prepareForSend.invoke(transactionData = state.signModel, userWallet = wallet, network = network)
|
||||
.onLeft { error ->
|
||||
emit(state.toResult(error.left()))
|
||||
emit(state.toResult(parseSendError(error).left()))
|
||||
}
|
||||
.getOrNull()
|
||||
?: return
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
|
|||
import com.tangem.data.walletconnect.sign.WcMethodUseCaseContext
|
||||
import com.tangem.data.walletconnect.utils.BlockAidVerificationDelegate
|
||||
import com.tangem.domain.transaction.usecase.PrepareForSendUseCase
|
||||
import com.tangem.domain.walletconnect.error.parseSendError
|
||||
import com.tangem.domain.walletconnect.model.WcSolanaMethod
|
||||
import com.tangem.domain.walletconnect.usecase.method.BlockAidTransactionCheck
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcSignState
|
||||
|
|
@ -45,7 +46,7 @@ internal class WcSolanaSignTransactionUseCase @AssistedInject constructor(
|
|||
override suspend fun SignCollector<TransactionData>.onSign(state: WcSignState<TransactionData>) {
|
||||
val hash = prepareForSend.invoke(transactionData = state.signModel, userWallet = wallet, network = network)
|
||||
.onLeft { error ->
|
||||
emit(state.toResult(error.left()))
|
||||
emit(state.toResult(parseSendError(error).left()))
|
||||
}
|
||||
.getOrNull()
|
||||
?: return
|
||||
|
|
|
|||
|
|
@ -5,10 +5,14 @@ import com.reown.walletkit.client.Wallet.Model.Namespace
|
|||
import com.tangem.data.walletconnect.utils.WcNamespaceConverter
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.walletconnect.model.WcPairError
|
||||
import com.tangem.domain.walletconnect.model.WcSessionProposal.ProposalNetwork
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.models.isMultiCurrency
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
|
||||
|
|
@ -16,36 +20,34 @@ internal class AssociateNetworksDelegate(
|
|||
private val namespaceConverters: Set<WcNamespaceConverter>,
|
||||
private val getWallets: GetWalletsUseCase,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
) {
|
||||
|
||||
suspend fun associate(wallet: UserWallet, namespaces: Map<String, Namespace.Session>): Set<Network> {
|
||||
val walletNetworks = getWalletNetworks(wallet)
|
||||
val namespacesSet = namespaces.values.flatMap { proposal -> proposal.chains ?: listOf() }.toSet()
|
||||
return namespacesSet.mapNotNullTo(mutableSetOf()) { chainId ->
|
||||
val wcNetwork = namespaceConverters
|
||||
.firstNotNullOfOrNull { it.toNetwork(chainId, wallet) } ?: return@mapNotNullTo null
|
||||
walletNetworks.find { network -> wcNetwork.id == network.id }
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(WcPairError.UnsupportedBlockchains::class)
|
||||
suspend fun associate(sessionProposal: Wallet.Model.SessionProposal): Map<UserWallet, ProposalNetwork> {
|
||||
val userWallets = getWallets.invokeSync().filter { it.isMultiCurrency }
|
||||
val requiredNamespaces: Set<String> = sessionProposal.requiredNamespaces.setOfChainId()
|
||||
val optionalNamespaces: Set<String> = sessionProposal.optionalNamespaces.setOfChainId()
|
||||
// remove duplicates
|
||||
.subtract(requiredNamespaces)
|
||||
|
||||
return userWallets
|
||||
.associateWith { wallet -> mapNetworksForWallet(wallet, requiredNamespaces, optionalNamespaces) }
|
||||
return userWallets.associateWith { wallet ->
|
||||
mapNetworksForWallet(wallet, requiredNamespaces, optionalNamespaces, sessionProposal)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("ComplexCondition")
|
||||
private suspend fun mapNetworksForWallet(
|
||||
wallet: UserWallet,
|
||||
requiredNamespaces: Set<String>,
|
||||
optionalNamespaces: Set<String>,
|
||||
sessionProposal: Wallet.Model.SessionProposal,
|
||||
): ProposalNetwork {
|
||||
val walletNetworks = getWalletNetworks(wallet)
|
||||
val walletNetworks = getWalletNetworks(userWalletId = wallet.walletId)
|
||||
|
||||
val unknownRequired = mutableSetOf<String>()
|
||||
val unknownOptional = mutableSetOf<String>()
|
||||
val missingRequired = mutableSetOf<Network>()
|
||||
val required = mutableSetOf<Network>()
|
||||
val available = mutableSetOf<Network>()
|
||||
|
|
@ -57,7 +59,8 @@ internal class AssociateNetworksDelegate(
|
|||
unknownRequired.add(missingNetworkName(chainId))
|
||||
return@forEach
|
||||
}
|
||||
val walletNetwork = walletNetworks.find { network -> wcNetwork.id == network.id }
|
||||
val walletNetwork = walletNetworks.find { network -> wcNetwork.rawId == network.rawId }
|
||||
|
||||
if (walletNetwork == null) {
|
||||
missingRequired.add(wcNetwork)
|
||||
} else {
|
||||
|
|
@ -66,15 +69,23 @@ internal class AssociateNetworksDelegate(
|
|||
}
|
||||
optionalNamespaces.forEach { chainId ->
|
||||
val wcNetwork = namespaceConverters.firstNotNullOfOrNull { it.toNetwork(chainId, wallet) }
|
||||
?: return@forEach
|
||||
val walletNetwork = walletNetworks.find { network -> wcNetwork.id == network.id }
|
||||
if (wcNetwork == null) {
|
||||
unknownOptional.add(missingNetworkName(chainId))
|
||||
return@forEach
|
||||
}
|
||||
val walletNetwork = walletNetworks.find { network -> wcNetwork.rawId == network.rawId }
|
||||
if (walletNetwork != null) {
|
||||
available.add(walletNetwork)
|
||||
} else {
|
||||
notAdded.add(wcNetwork)
|
||||
}
|
||||
}
|
||||
if (unknownRequired.isNotEmpty()) throw WcPairError.UnsupportedBlockchains(unknownRequired)
|
||||
if (unknownRequired.isNotEmpty()) {
|
||||
throw WcPairError.UnsupportedBlockchains(unknownRequired, sessionProposal.name)
|
||||
}
|
||||
if (unknownOptional.isNotEmpty() && required.isEmpty() && available.isEmpty() && missingRequired.isEmpty()) {
|
||||
throw WcPairError.UnsupportedBlockchains(unknownOptional, sessionProposal.name)
|
||||
}
|
||||
return ProposalNetwork(
|
||||
wallet = wallet,
|
||||
missingRequired = missingRequired,
|
||||
|
|
@ -84,10 +95,20 @@ internal class AssociateNetworksDelegate(
|
|||
)
|
||||
}
|
||||
|
||||
private suspend fun getWalletNetworks(wallet: UserWallet): List<Network> =
|
||||
currenciesRepository.getMultiCurrencyWalletCurrenciesSync(wallet.walletId)
|
||||
private suspend fun getWalletNetworks(userWalletId: UserWalletId): List<Network> {
|
||||
return if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
|
||||
multiWalletCryptoCurrenciesSupplier.getSyncOrNull(
|
||||
params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId = userWalletId),
|
||||
)
|
||||
.orEmpty()
|
||||
} else {
|
||||
currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId)
|
||||
}
|
||||
.filterIsInstance<CryptoCurrency.Coin>()
|
||||
.map { it.network }
|
||||
.map(CryptoCurrency.Coin::network)
|
||||
// flatten all derivation
|
||||
.distinctBy { it.rawId }
|
||||
}
|
||||
|
||||
private fun Map<String, Namespace.Proposal>.setOfChainId(): Set<String> =
|
||||
this.values.flatMap { proposal -> proposal.chains ?: listOf() }.toSet()
|
||||
|
|
|
|||
|
|
@ -1,48 +1,47 @@
|
|||
package com.tangem.data.walletconnect.pair
|
||||
|
||||
import com.reown.walletkit.client.Wallet
|
||||
import com.tangem.data.walletconnect.model.CAIP10
|
||||
import com.tangem.data.walletconnect.utils.WcNamespaceConverter
|
||||
import com.tangem.data.walletconnect.utils.WcNetworksConverter
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.data.walletconnect.model.CAIP10
|
||||
import com.tangem.domain.walletconnect.model.WcSessionApprove
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
internal class CaipNamespaceDelegate(
|
||||
private val namespaceConverters: Set<WcNamespaceConverter>,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val wcNetworksConverter: WcNetworksConverter,
|
||||
) {
|
||||
|
||||
suspend fun associate(
|
||||
sessionProposal: Wallet.Model.SessionProposal,
|
||||
userWallet: UserWallet,
|
||||
networks: List<Network>,
|
||||
sessionForApprove: WcSessionApprove,
|
||||
): Map<String, Wallet.Model.Namespace.Session> {
|
||||
val userWallet = sessionForApprove.wallet
|
||||
val result = mutableMapOf<String, Session>()
|
||||
|
||||
networks.map { network ->
|
||||
val address = walletManagersFacade.getDefaultAddress(userWallet.walletId, network)
|
||||
val chainId = namespaceConverters.firstNotNullOfOrNull { it.toCAIP2(network) }
|
||||
requireNotNull(chainId)
|
||||
requireNotNull(address)
|
||||
CAIP10(chainId = chainId, accountAddress = address)
|
||||
}.forEach { account ->
|
||||
val namespaceKey = account.chainId.namespace
|
||||
val session = result.getOrPut(namespaceKey) { Session() }
|
||||
val requiredNamespaces = sessionProposal.requiredNamespaces
|
||||
val optionalNamespaces = sessionProposal.optionalNamespaces
|
||||
val methods = buildSet {
|
||||
requiredNamespaces[namespaceKey]?.methods?.let { addAll(it) }
|
||||
optionalNamespaces[namespaceKey]?.methods?.let { addAll(it) }
|
||||
wcNetworksConverter.convertNetworksForApprove(sessionForApprove)
|
||||
.mapNotNull { createCAIP10(userWallet.walletId, it) }
|
||||
.forEach { account ->
|
||||
val namespaceKey = account.chainId.namespace
|
||||
val session = result.getOrPut(namespaceKey) { Session() }
|
||||
val requiredNamespaces = sessionProposal.requiredNamespaces
|
||||
val optionalNamespaces = sessionProposal.optionalNamespaces
|
||||
val methods = buildSet {
|
||||
requiredNamespaces[namespaceKey]?.methods?.let { addAll(it) }
|
||||
optionalNamespaces[namespaceKey]?.methods?.let { addAll(it) }
|
||||
}
|
||||
val events = buildSet {
|
||||
requiredNamespaces[namespaceKey]?.events?.let { addAll(it) }
|
||||
optionalNamespaces[namespaceKey]?.events?.let { addAll(it) }
|
||||
}
|
||||
session.chains.add(account.chainId.raw)
|
||||
session.accounts.add(account.raw)
|
||||
session.methods.addAll(methods)
|
||||
session.events.addAll(events)
|
||||
}
|
||||
val events = buildSet {
|
||||
requiredNamespaces[namespaceKey]?.events?.let { addAll(it) }
|
||||
optionalNamespaces[namespaceKey]?.events?.let { addAll(it) }
|
||||
}
|
||||
session.chains.add(account.chainId.raw)
|
||||
session.accounts.add(account.raw)
|
||||
session.methods.addAll(methods)
|
||||
session.events.addAll(events)
|
||||
}
|
||||
return result.mapValues { (_, session) ->
|
||||
Wallet.Model.Namespace.Session(
|
||||
chains = session.chains.toList(),
|
||||
|
|
@ -53,6 +52,13 @@ internal class CaipNamespaceDelegate(
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun createCAIP10(userWalletId: UserWalletId, network: Network): CAIP10? {
|
||||
val address = walletManagersFacade.getDefaultAddress(userWalletId, network)
|
||||
val chainId = namespaceConverters.firstNotNullOfOrNull { it.toCAIP2(network) }
|
||||
if (chainId == null || address == null) return null
|
||||
return CAIP10(chainId = chainId, accountAddress = address)
|
||||
}
|
||||
|
||||
private data class Session(
|
||||
val chains: MutableSet<String> = mutableSetOf(),
|
||||
val accounts: MutableSet<String> = mutableSetOf(),
|
||||
|
|
|
|||
|
|
@ -1,17 +1,45 @@
|
|||
package com.tangem.data.walletconnect.pair
|
||||
|
||||
import androidx.core.net.toUri
|
||||
import com.tangem.domain.walletconnect.WcPairService
|
||||
import com.tangem.domain.walletconnect.model.WcPairRequest
|
||||
import com.tangem.domain.walletconnect.repository.WcSessionsManager
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import javax.inject.Inject
|
||||
|
||||
class DefaultWcPairService @Inject constructor() : WcPairService {
|
||||
class DefaultWcPairService @Inject constructor(
|
||||
private val sessionsManager: WcSessionsManager,
|
||||
) : WcPairService {
|
||||
private val _pairFlow: Channel<WcPairRequest> = Channel(Channel.BUFFERED)
|
||||
override val pairFlow: Flow<WcPairRequest> = _pairFlow.receiveAsFlow()
|
||||
override val pairFlow: Flow<WcPairRequest> = _pairFlow
|
||||
.receiveAsFlow()
|
||||
.filter(::filterDeeplinkRequestWithSessionTopic)
|
||||
|
||||
override fun pair(request: WcPairRequest) {
|
||||
_pairFlow.trySend(request)
|
||||
}
|
||||
|
||||
// some dApp sends deeplink with session request
|
||||
// we filter session exist, but start dApp pair flow if unexist
|
||||
private suspend fun filterDeeplinkRequestWithSessionTopic(request: WcPairRequest): Boolean {
|
||||
when (request.source) {
|
||||
WcPairRequest.Source.QR,
|
||||
WcPairRequest.Source.CLIPBOARD,
|
||||
WcPairRequest.Source.ETC,
|
||||
-> return true
|
||||
WcPairRequest.Source.DEEPLINK -> Unit
|
||||
}
|
||||
|
||||
val isExistSession = existSessionTopic(request.uri).getOrNull() ?: false
|
||||
return !isExistSession
|
||||
}
|
||||
|
||||
private suspend fun existSessionTopic(uri: String) = runCatching {
|
||||
val sessionTopic = uri.toUri().getQueryParameter("sessionTopic") ?: return@runCatching false
|
||||
val isExistSession = sessionsManager.findSessionByTopic(sessionTopic) != null
|
||||
return@runCatching isExistSession
|
||||
}
|
||||
}
|
||||
|
|
@ -8,10 +8,10 @@ import com.domain.blockaid.models.dapp.CheckDAppResult
|
|||
import com.domain.blockaid.models.dapp.DAppData
|
||||
import com.reown.walletkit.client.Wallet
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents
|
||||
import com.tangem.data.walletconnect.utils.WC_TAG
|
||||
import com.tangem.data.walletconnect.utils.WcSdkSessionConverter
|
||||
import com.tangem.domain.blockaid.BlockAidVerifier
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents
|
||||
import com.tangem.domain.walletconnect.model.*
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData
|
||||
import com.tangem.domain.walletconnect.repository.WcSessionsManager
|
||||
|
|
@ -22,10 +22,9 @@ import dagger.assisted.AssistedFactory
|
|||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.joda.time.DateTime
|
||||
import timber.log.Timber
|
||||
|
||||
val unsupportedDApps = listOf("dYdX", "dYdX v4", "Apex Pro", "The Sandbox")
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class DefaultWcPairUseCase @AssistedInject constructor(
|
||||
private val sessionsManager: WcSessionsManager,
|
||||
|
|
@ -39,6 +38,7 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
|
|||
|
||||
private val onCallTerminalAction = Channel<TerminalAction>()
|
||||
|
||||
@Suppress("LongMethod")
|
||||
override operator fun invoke(): Flow<WcPairState> {
|
||||
val (uri: String, source: WcPairRequest.Source) = pairRequest
|
||||
return flow {
|
||||
|
|
@ -49,14 +49,15 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
|
|||
val sdkSessionProposal = sdkDelegate.pair(uri)
|
||||
.onLeft {
|
||||
Timber.tag(WC_TAG).e(it, "Failed to call pair $pairRequest")
|
||||
analytics.send(WcAnalyticEvents.PairFailed)
|
||||
emit(WcPairState.Error(it))
|
||||
}
|
||||
.getOrNull() ?: return@flow
|
||||
|
||||
// check unsupported dApps, just local constant for now, finish if unsupported
|
||||
if (sdkSessionProposal.name in unsupportedDApps) {
|
||||
if (UnsupportedDApps.list.any { sdkSessionProposal.url.contains(it, ignoreCase = true) }) {
|
||||
Timber.tag(WC_TAG).i("Unsupported DApp ${sdkSessionProposal.name}")
|
||||
val error = WcPairState.Error(WcPairError.UnsupportedDomain)
|
||||
val error = WcPairState.Error(WcPairError.UnsupportedDApp(sdkSessionProposal.name))
|
||||
emit(error)
|
||||
return@flow
|
||||
}
|
||||
|
|
@ -78,7 +79,7 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
|
|||
}
|
||||
// finish flow if rejected above
|
||||
if (sessionForApprove == null) {
|
||||
analytics.send(WcAnalyticEvents.SessionDisconnected(proposalState.dAppSession))
|
||||
analytics.send(WcAnalyticEvents.SessionDisconnected(proposalState.dAppSession.dAppMetaData))
|
||||
sdkDelegate.rejectSession(sdkSessionProposal.proposerPublicKey)
|
||||
return@flow
|
||||
}
|
||||
|
|
@ -94,6 +95,8 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
|
|||
sdkModel = WcSdkSessionConverter.convert(settledSession.session),
|
||||
securityStatus = proposalState.dAppSession.securityStatus,
|
||||
networks = sessionForApprove.network.toSet(),
|
||||
connectingTime = DateTime.now().millis,
|
||||
showWalletInfo = proposalState.dAppSession.proposalNetwork.keys.size > 1,
|
||||
)
|
||||
sessionsManager.saveSession(newSession)
|
||||
analytics.send(
|
||||
|
|
@ -134,8 +137,7 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
|
|||
): Either<WcPairError, Wallet.Model.SettledSessionResponse.Result> {
|
||||
val namespaces = caipNamespaceDelegate.associate(
|
||||
sdkSessionProposal,
|
||||
sessionForApprove.wallet,
|
||||
sessionForApprove.network,
|
||||
sessionForApprove,
|
||||
)
|
||||
val sessionApprove = Wallet.Params.SessionApprove(
|
||||
proposerPublicKey = sdkSessionProposal.proposerPublicKey,
|
||||
|
|
@ -157,7 +159,7 @@ internal class DefaultWcPairUseCase @AssistedInject constructor(
|
|||
analytics.send(
|
||||
WcAnalyticEvents.PairRequested(
|
||||
network = requestedNetworks,
|
||||
verificationInfo.name,
|
||||
verificationInfo,
|
||||
),
|
||||
)
|
||||
val appMetaData = WcAppMetaData(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package com.tangem.data.walletconnect.pair
|
||||
|
||||
/**
|
||||
* These dApps are not supported by WC due to various issues with signing formats
|
||||
* @see <a href = "https://www.notion.so/tangem/WalletConnect-1935d34eb678805fb9aff2e046dc1dfa?source=copy_link#1a15d34eb6788073925ce9c2b9fd12a5">Documentation<a/>
|
||||
*/
|
||||
object UnsupportedDApps {
|
||||
val list = listOf(
|
||||
"dydx.trade",
|
||||
"dydx.exchange",
|
||||
"pro.apex.exchange",
|
||||
"services.dfx.swiss",
|
||||
"sandbox.game",
|
||||
"app.paradex.trade",
|
||||
)
|
||||
}
|
||||
|
|
@ -5,59 +5,94 @@ import arrow.core.left
|
|||
import arrow.core.right
|
||||
import com.reown.walletkit.client.Wallet
|
||||
import com.reown.walletkit.client.WalletKit
|
||||
import com.tangem.data.walletconnect.utils.WC_TAG
|
||||
import com.tangem.data.walletconnect.utils.WcSdkObserver
|
||||
import com.tangem.domain.walletconnect.model.WcPairError
|
||||
import kotlinx.coroutines.async
|
||||
import com.tangem.domain.walletconnect.model.WcPairError.ApprovalFailed
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.channels.awaitClose
|
||||
import kotlinx.coroutines.flow.callbackFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
internal class WcPairSdkDelegate : WcSdkObserver {
|
||||
|
||||
private val onSessionProposal = Channel<Wallet.Model.SessionProposal>()
|
||||
private val onSdkErrorCallback = Channel<Wallet.Model.Error>()
|
||||
private val onSessionSettleResponse = Channel<Wallet.Model.SettledSessionResponse>()
|
||||
|
||||
suspend fun pair(url: String): Either<WcPairError, Wallet.Model.SessionProposal> = coroutineScope {
|
||||
suspend fun proposalCallback() = onSessionProposal
|
||||
.receiveAsFlow()
|
||||
.first()
|
||||
|
||||
val proposalCallback = async { withTimeout(CALLBACK_TIMEOUT.seconds) { proposalCallback() } }
|
||||
val pairCall = async { sdkPair(url) }
|
||||
val proposal = async { withTimeout(20.seconds) { proposalCallback() } }
|
||||
pairCall.await().onLeft {
|
||||
proposal.cancel()
|
||||
proposalCallback.cancel()
|
||||
return@coroutineScope it.left()
|
||||
}
|
||||
proposal.await().right()
|
||||
proposalCallback.await()
|
||||
}
|
||||
|
||||
private suspend fun proposalCallback() = callbackFlow {
|
||||
// wait first onSessionProposal callback
|
||||
launch {
|
||||
val sessionProposal = onSessionProposal.receiveAsFlow().first()
|
||||
trySend(sessionProposal.right())
|
||||
channel.close()
|
||||
}
|
||||
// OR
|
||||
// wait first onError callback
|
||||
launch {
|
||||
val error = onSdkErrorCallback.receiveAsFlow().first()
|
||||
trySend(error.throwable.toPairError().left())
|
||||
channel.close()
|
||||
}
|
||||
awaitClose()
|
||||
}.first()
|
||||
|
||||
suspend fun approve(
|
||||
sessionApprove: Wallet.Params.SessionApprove,
|
||||
): Either<WcPairError, Wallet.Model.SettledSessionResponse.Result> = coroutineScope {
|
||||
suspend fun approveCallback() = onSessionSettleResponse
|
||||
.receiveAsFlow()
|
||||
.first()
|
||||
|
||||
val approveCallback = async { withTimeout(CALLBACK_TIMEOUT.seconds) { approveCallback() } }
|
||||
val approveCall = async { sdkApprove(sessionApprove) }
|
||||
val approveCallback = async { withTimeout(20.seconds) { approveCallback() } }
|
||||
approveCall.await()
|
||||
.onLeft {
|
||||
approveCallback.cancel()
|
||||
return@coroutineScope it.left()
|
||||
}
|
||||
when (val result = approveCallback.await()) {
|
||||
is Wallet.Model.SettledSessionResponse.Result -> result.right()
|
||||
is Wallet.Model.SettledSessionResponse.Error ->
|
||||
WcPairError.ApprovalFailed(result.errorMessage).left()
|
||||
}
|
||||
return@coroutineScope approveCallback.await().fold(
|
||||
ifLeft = { it.left() },
|
||||
ifRight = { result ->
|
||||
when (result) {
|
||||
is Wallet.Model.SettledSessionResponse.Result -> result.right()
|
||||
is Wallet.Model.SettledSessionResponse.Error ->
|
||||
ApprovalFailed(result.errorMessage).left()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun approveCallback() = callbackFlow<Either<WcPairError, Wallet.Model.SettledSessionResponse>> {
|
||||
// wait first onSessionSettleResponse callback
|
||||
launch {
|
||||
val settledSessionResponse = onSessionSettleResponse.receiveAsFlow().first()
|
||||
trySend(settledSessionResponse.right())
|
||||
channel.close()
|
||||
}
|
||||
// OR
|
||||
// wait first onError callback
|
||||
launch {
|
||||
val error = onSdkErrorCallback.receiveAsFlow().first()
|
||||
trySend(error.throwable.toApproveError())
|
||||
channel.close()
|
||||
}
|
||||
awaitClose()
|
||||
}.first()
|
||||
|
||||
fun rejectSession(proposerPublicKey: String) {
|
||||
Timber.tag(WC_TAG).i("reject session proposerPublicKey = $proposerPublicKey")
|
||||
WalletKit.rejectSession(
|
||||
params = Wallet.Params.SessionReject(
|
||||
proposerPublicKey = proposerPublicKey,
|
||||
|
|
@ -68,6 +103,10 @@ internal class WcPairSdkDelegate : WcSdkObserver {
|
|||
)
|
||||
}
|
||||
|
||||
override fun onError(error: Wallet.Model.Error) {
|
||||
onSdkErrorCallback.trySend(error)
|
||||
}
|
||||
|
||||
override fun onSessionProposal(
|
||||
sessionProposal: Wallet.Model.SessionProposal,
|
||||
verifyContext: Wallet.Model.VerifyContext,
|
||||
|
|
@ -85,8 +124,8 @@ internal class WcPairSdkDelegate : WcSdkObserver {
|
|||
return suspendCancellableCoroutine { continuation ->
|
||||
WalletKit.approveSession(
|
||||
params = sessionApprove,
|
||||
onSuccess = { continuation.resume(Unit.right()) },
|
||||
onError = { continuation.resume(it.throwable.toApproveError()) },
|
||||
onSuccess = { if (continuation.isActive) continuation.resume(Unit.right()) },
|
||||
onError = { if (continuation.isActive) continuation.resume(it.throwable.toApproveError()) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -95,8 +134,8 @@ internal class WcPairSdkDelegate : WcSdkObserver {
|
|||
return suspendCancellableCoroutine { continuation ->
|
||||
WalletKit.pair(
|
||||
params = Wallet.Params.Pair(uri),
|
||||
onSuccess = { continuation.resume(Unit.right()) },
|
||||
onError = { continuation.resume(it.throwable.toPairError().left()) },
|
||||
onSuccess = { if (continuation.isActive) continuation.resume(Unit.right()) },
|
||||
onError = { if (continuation.isActive) continuation.resume(it.throwable.toPairError().left()) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -109,7 +148,12 @@ internal class WcPairSdkDelegate : WcSdkObserver {
|
|||
private fun Throwable.toApproveError() = WcPairError.ApprovalFailed(this.localizedMessage.orEmpty()).left()
|
||||
|
||||
companion object {
|
||||
private const val CALLBACK_TIMEOUT = 30
|
||||
// com.reown.android.pairing.engine.domain.PairingEngine.pair
|
||||
private val pairingExpiredMessages = listOf("Pairing URI expired", "Pairing expired")
|
||||
private val pairingExpiredMessages = listOf(
|
||||
"Pairing URI expired",
|
||||
"Pairing expired",
|
||||
"No proposal or pending session authenticate request for pairing topic",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,10 +3,13 @@ package com.tangem.data.walletconnect.request
|
|||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.data.walletconnect.utils.WC_TAG
|
||||
import com.tangem.domain.walletconnect.WcRequestUseCaseFactory
|
||||
import com.tangem.domain.walletconnect.model.WcMethod
|
||||
import com.tangem.domain.walletconnect.model.WcRequestError
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcMethodUseCase
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultWcRequestUseCaseFactory @Inject constructor(
|
||||
|
|
@ -17,7 +20,17 @@ internal class DefaultWcRequestUseCaseFactory @Inject constructor(
|
|||
override suspend fun <T : WcMethodUseCase> createUseCase(
|
||||
request: WcSdkSessionRequest,
|
||||
): Either<WcMethod.Unsupported, T> {
|
||||
val useCase = requestConverters.firstNotNullOfOrNull { converter -> converter.toUseCase(request) }
|
||||
return (useCase as? T)?.let { useCase.right() } ?: WcMethod.Unsupported(request).left()
|
||||
val useCase = requestConverters
|
||||
.find { it.toWcMethodName(request) != null }
|
||||
?.toUseCase(request)
|
||||
?: WcRequestError.HandleMethodError("Failed to create WcUseCase").left()
|
||||
|
||||
return useCase.fold(
|
||||
ifLeft = {
|
||||
Timber.tag(WC_TAG).e("$it")
|
||||
WcMethod.Unsupported(request).left()
|
||||
},
|
||||
ifRight = { (it as? T)?.right() ?: WcMethod.Unsupported(request).left() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,27 @@
|
|||
package com.tangem.data.walletconnect.request
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.squareup.moshi.adapter
|
||||
import com.tangem.domain.walletconnect.model.WcMethodName
|
||||
import com.tangem.domain.walletconnect.model.WcRequestError
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcMethodUseCase
|
||||
|
||||
interface WcRequestToUseCaseConverter {
|
||||
fun toWcMethodName(request: WcSdkSessionRequest): WcMethodName?
|
||||
suspend fun toUseCase(request: WcSdkSessionRequest): WcMethodUseCase?
|
||||
suspend fun toUseCase(request: WcSdkSessionRequest): Either<WcRequestError.HandleMethodError, WcMethodUseCase>
|
||||
|
||||
companion object {
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
inline fun <reified T> Moshi.fromJson(params: String): T? =
|
||||
runCatching { this.adapter<T>().fromJson(params) }.getOrNull()
|
||||
inline fun <reified T> Moshi.fromJson(params: String): Either<Throwable, T?> {
|
||||
return runCatching { this.adapter<T>().fromJson(params) }
|
||||
.fold(
|
||||
onSuccess = { it.right() },
|
||||
onFailure = { it.left() },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import arrow.core.right
|
|||
import com.reown.walletkit.client.Wallet
|
||||
import com.reown.walletkit.client.WalletKit
|
||||
import com.tangem.data.walletconnect.utils.WC_TAG
|
||||
import com.tangem.domain.walletconnect.model.WcRequestError
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import timber.log.Timber
|
||||
|
|
@ -13,7 +14,7 @@ import kotlin.coroutines.resume
|
|||
|
||||
internal class DefaultWcRespondService : WcRespondService {
|
||||
|
||||
override suspend fun respond(request: WcSdkSessionRequest, response: String): Either<Throwable, Unit> =
|
||||
override suspend fun respond(request: WcSdkSessionRequest, response: String): Either<WcRequestError, String> =
|
||||
suspendCancellableCoroutine { continuation ->
|
||||
WalletKit.respondSessionRequest(
|
||||
params = Wallet.Params.SessionRequestResponse(
|
||||
|
|
@ -24,12 +25,26 @@ internal class DefaultWcRespondService : WcRespondService {
|
|||
),
|
||||
),
|
||||
onSuccess = {
|
||||
Timber.tag(WC_TAG).i("Successful respond for request $request")
|
||||
continuation.resume(Unit.right())
|
||||
if (continuation.isCompleted) return@respondSessionRequest
|
||||
val result = when (val response = it.jsonRpcResponse) {
|
||||
is Wallet.Model.JsonRpcResponse.JsonRpcError -> {
|
||||
Timber.tag(WC_TAG).e("Failed respond $response for request $request")
|
||||
WcRequestError.WcRespondError(
|
||||
code = response.code,
|
||||
message = response.message,
|
||||
).left()
|
||||
}
|
||||
is Wallet.Model.JsonRpcResponse.JsonRpcResult -> {
|
||||
Timber.tag(WC_TAG).i("Successful respond $response for request $request")
|
||||
response.result.right()
|
||||
}
|
||||
}
|
||||
continuation.resume(result)
|
||||
},
|
||||
onError = {
|
||||
if (continuation.isCompleted) return@respondSessionRequest
|
||||
Timber.tag(WC_TAG).e(it.throwable, "Failed respond for request $request")
|
||||
continuation.resume(it.throwable.left())
|
||||
continuation.resume(WcRequestError.UnknownError(it.throwable).left())
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
package com.tangem.data.walletconnect.respond
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.walletconnect.model.WcRequestError
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
|
||||
interface WcRespondService {
|
||||
suspend fun respond(request: WcSdkSessionRequest, response: String): Either<Throwable, Unit>
|
||||
suspend fun respond(request: WcSdkSessionRequest, response: String): Either<WcRequestError, String>
|
||||
fun rejectRequestNonBlock(request: WcSdkSessionRequest, message: String = "")
|
||||
}
|
||||
|
|
@ -6,11 +6,10 @@ import arrow.core.right
|
|||
import com.domain.blockaid.models.dapp.CheckDAppResult
|
||||
import com.reown.walletkit.client.Wallet
|
||||
import com.reown.walletkit.client.WalletKit
|
||||
import com.tangem.data.walletconnect.pair.AssociateNetworksDelegate
|
||||
import com.tangem.data.walletconnect.utils.WC_TAG
|
||||
import com.tangem.data.walletconnect.utils.WcSdkObserver
|
||||
import com.tangem.data.walletconnect.utils.WcSdkSessionConverter
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.data.walletconnect.utils.*
|
||||
import com.tangem.datasource.local.walletconnect.WalletConnectStore
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents
|
||||
import com.tangem.domain.walletconnect.model.WcSession
|
||||
import com.tangem.domain.walletconnect.model.WcSessionDTO
|
||||
import com.tangem.domain.walletconnect.model.legacy.WalletConnectSessionsRepository
|
||||
|
|
@ -21,21 +20,23 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
|||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.*
|
||||
import org.joda.time.DateTime
|
||||
import timber.log.Timber
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class DefaultWcSessionsManager(
|
||||
private val store: WalletConnectStore,
|
||||
private val legacyStore: WalletConnectSessionsRepository,
|
||||
private val getWallets: GetWalletsUseCase,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val associateNetworks: AssociateNetworksDelegate,
|
||||
private val wcNetworksConverter: WcNetworksConverter,
|
||||
private val analytics: AnalyticsEventHandler,
|
||||
private val scope: CoroutineScope,
|
||||
) : WcSessionsManager, WcSdkObserver {
|
||||
|
||||
private val onSessionDelete = Channel<Wallet.Model.SessionDelete>(capacity = Channel.BUFFERED)
|
||||
private val oneTimeMigration = MutableStateFlow(true)
|
||||
private val oneTimeSessionExtend = MutableStateFlow(true)
|
||||
|
||||
override val sessions: Flow<Map<UserWallet, List<WcSession>>>
|
||||
get() = combine(getWallets(), store.sessions) { wallets, inStore -> wallets to inStore }
|
||||
|
|
@ -50,10 +51,6 @@ internal class DefaultWcSessionsManager(
|
|||
val associatedSessions: List<WcSession> = associate(inSdk, inStore, wallets)
|
||||
val someRemove = removeUnknownSessions(inStore, associatedSessions)
|
||||
if (someRemove) return@transform // ignore emit, wait next one
|
||||
if (oneTimeSessionExtend.value) {
|
||||
oneTimeSessionExtend.value = false
|
||||
scope.launch { extendAliveSessions(associatedSessions) }
|
||||
}
|
||||
emit(associatedSessions.groupBy { it.wallet })
|
||||
}
|
||||
.distinctUntilChanged()
|
||||
|
|
@ -61,35 +58,33 @@ internal class DefaultWcSessionsManager(
|
|||
|
||||
override fun onWcSdkInit() {
|
||||
oneTimeMigration.value = true
|
||||
oneTimeSessionExtend.value = true
|
||||
listenOnSessionDelete()
|
||||
extendSessions()
|
||||
}
|
||||
|
||||
override suspend fun saveSession(session: WcSession) {
|
||||
store.saveSession(WcSessionDTO(session.sdkModel.topic, session.wallet.walletId, session.securityStatus))
|
||||
store.saveSession(
|
||||
WcSessionDTO(
|
||||
topic = session.sdkModel.topic,
|
||||
walletId = session.wallet.walletId,
|
||||
securityStatus = session.securityStatus,
|
||||
connectingTime = session.connectingTime ?: DateTime.now().millis,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun removeSession(session: WcSession): Either<Throwable, Unit> {
|
||||
val topic = session.sdkModel.topic
|
||||
val sdkCall = sdkDisconnectSession(topic)
|
||||
.onRight { onSessionDelete.trySend(Wallet.Model.SessionDelete.Success(topic = topic, reason = "")) }
|
||||
analytics.send(WcAnalyticEvents.SessionDisconnected(session.sdkModel.appMetaData))
|
||||
return sdkCall
|
||||
}
|
||||
|
||||
override suspend fun findSessionByTopic(topic: String): WcSession? = withContext(dispatchers.io) {
|
||||
val storedSession = sessions.firstOrNull()
|
||||
sessions.firstOrNull()
|
||||
?.values?.flatten()
|
||||
?.firstOrNull { it.sdkModel.topic == topic }
|
||||
?: return@withContext null
|
||||
val sdkSession = WalletKit.getActiveSessionByTopic(topic) ?: return@withContext null
|
||||
val wallet = storedSession.wallet
|
||||
val networks = associateNetworks.associate(wallet, sdkSession.namespaces)
|
||||
WcSession(
|
||||
wallet = wallet,
|
||||
sdkModel = WcSdkSessionConverter.convert(sdkSession),
|
||||
securityStatus = storedSession.securityStatus,
|
||||
networks = networks,
|
||||
)
|
||||
}
|
||||
|
||||
override fun onSessionDelete(sessionDelete: Wallet.Model.SessionDelete) {
|
||||
|
|
@ -128,15 +123,17 @@ internal class DefaultWcSessionsManager(
|
|||
inStore: Set<WcSessionDTO>,
|
||||
wallets: List<UserWallet>,
|
||||
): List<WcSession> {
|
||||
val wcSessions = inStore.mapNotNull { session ->
|
||||
val wallet = wallets.find { it.walletId == session.walletId } ?: return@mapNotNull null
|
||||
val sdkSession = inSdk.find { it.topic == session.topic } ?: return@mapNotNull null
|
||||
val networks = associateNetworks.associate(wallet, sdkSession.namespaces)
|
||||
val wcSessions = inStore.mapNotNull { storeSession ->
|
||||
val wallet = wallets.find { it.walletId == storeSession.walletId } ?: return@mapNotNull null
|
||||
val sdkSession = inSdk.find { it.topic == storeSession.topic } ?: return@mapNotNull null
|
||||
val networks = wcNetworksConverter.findWalletNetworks(wallet, sdkSession)
|
||||
WcSession(
|
||||
wallet = wallet,
|
||||
sdkModel = WcSdkSessionConverter.convert(sdkSession),
|
||||
securityStatus = session.securityStatus,
|
||||
securityStatus = storeSession.securityStatus,
|
||||
networks = networks,
|
||||
connectingTime = storeSession.connectingTime,
|
||||
showWalletInfo = wallets.size > 1,
|
||||
)
|
||||
}
|
||||
return wcSessions
|
||||
|
|
@ -154,17 +151,20 @@ internal class DefaultWcSessionsManager(
|
|||
return haveSomeUnknown
|
||||
}
|
||||
|
||||
private suspend fun extendAliveSessions(sessions: List<WcSession>) = coroutineScope {
|
||||
val jobs = sessions.map { launch { sdkSessionExtend(it.sdkModel.topic) } }
|
||||
jobs.joinAll()
|
||||
private fun extendSessions() {
|
||||
scope.launch(dispatchers.io) {
|
||||
val topics: List<String> = WalletKit.getListOfActiveSessions().map { it.topic }
|
||||
val jobs = topics.map { topic -> launch { sdkSessionExtend(topic) } }
|
||||
jobs.joinAll()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun sdkDisconnectSession(topic: String): Either<Throwable, Unit> {
|
||||
return suspendCancellableCoroutine { continuation ->
|
||||
WalletKit.disconnectSession(
|
||||
params = Wallet.Params.SessionDisconnect(topic),
|
||||
onSuccess = { continuation.resume(Unit.right()) },
|
||||
onError = { continuation.resume(it.throwable.left()) },
|
||||
onSuccess = { if (continuation.isActive) continuation.resume(Unit.right()) },
|
||||
onError = { if (continuation.isActive) continuation.resume(it.throwable.left()) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -173,8 +173,8 @@ internal class DefaultWcSessionsManager(
|
|||
return suspendCancellableCoroutine { continuation ->
|
||||
WalletKit.extendSession(
|
||||
params = Wallet.Params.SessionExtend(topic),
|
||||
onSuccess = { continuation.resume(Unit.right()) },
|
||||
onError = { continuation.resume(it.throwable.left()) },
|
||||
onSuccess = { if (continuation.isActive) continuation.resume(Unit.right()) },
|
||||
onError = { if (continuation.isActive) continuation.resume(it.throwable.left()) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.data.walletconnect.sign
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.utils.converter.Converter
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class BlockAidChainNameConverter @Inject constructor() : Converter<Network, String> {
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
override fun convert(value: Network): String {
|
||||
return when (Blockchain.fromNetworkId(value.backendId)) {
|
||||
Blockchain.Arbitrum -> "arbitrum"
|
||||
Blockchain.Avalanche -> "avalanche"
|
||||
Blockchain.AvalancheTestnet -> "avalanche-fuji"
|
||||
Blockchain.Binance, Blockchain.BSC -> "bsc"
|
||||
Blockchain.Ethereum -> "ethereum"
|
||||
Blockchain.EthereumTestnet -> "ethereum-sepolia"
|
||||
Blockchain.Polygon -> "polygon"
|
||||
Blockchain.Solana -> "mainnet"
|
||||
Blockchain.Gnosis -> "gnosis"
|
||||
Blockchain.Optimism -> "optimism"
|
||||
Blockchain.ZkSyncEra -> "zksync"
|
||||
Blockchain.ZkSyncEraTestnet -> "zksync-sepolia"
|
||||
Blockchain.Base -> "base"
|
||||
Blockchain.BaseTestnet -> "base-sepolia"
|
||||
Blockchain.Blast, Blockchain.BlastTestnet -> "blast"
|
||||
Blockchain.ApeChain, Blockchain.ApeChainTestnet -> "apechain"
|
||||
Blockchain.Scroll -> "scroll"
|
||||
else -> value.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,12 @@
|
|||
package com.tangem.data.walletconnect.sign
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.walletconnect.model.WcRequestError
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcSignState
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcSignStep
|
||||
|
||||
object SignStateConverter {
|
||||
|
||||
internal fun <M> preSign(signModel: M) = WcSignState(signModel, WcSignStep.PreSign)
|
||||
internal fun <M> signing(signModel: M) = WcSignState(signModel, WcSignStep.Signing)
|
||||
internal fun <M> result(result: Either<Throwable, Unit>, signModel: M) =
|
||||
WcSignState(signModel, WcSignStep.Result(result))
|
||||
|
||||
internal fun <M> WcSignState<M>.toPreSign(signModel: M = this.signModel) = copy(
|
||||
signModel = signModel,
|
||||
domainStep = WcSignStep.PreSign,
|
||||
|
|
@ -21,8 +17,9 @@ object SignStateConverter {
|
|||
signModel = signModel,
|
||||
)
|
||||
|
||||
internal fun <M> WcSignState<M>.toResult(result: Either<Throwable, Unit>, signModel: M = this.signModel) = copy(
|
||||
domainStep = WcSignStep.Result(result),
|
||||
signModel = signModel,
|
||||
)
|
||||
internal fun <M> WcSignState<M>.toResult(result: Either<WcRequestError, String>, signModel: M = this.signModel) =
|
||||
copy(
|
||||
domainStep = WcSignStep.Result(result),
|
||||
signModel = signModel,
|
||||
)
|
||||
}
|
||||
|
|
@ -6,6 +6,8 @@ import com.tangem.data.walletconnect.sign.SignStateConverter.toPreSign
|
|||
import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
|
||||
import com.tangem.data.walletconnect.sign.SignStateConverter.toSigning
|
||||
import com.tangem.domain.walletconnect.WcAnalyticEvents
|
||||
import com.tangem.domain.walletconnect.model.WcRequestError
|
||||
import com.tangem.domain.walletconnect.model.WcRequestError.Companion.code
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcSignState
|
||||
import com.tangem.domain.walletconnect.usecase.method.WcSignStep
|
||||
import kotlinx.coroutines.Job
|
||||
|
|
@ -62,18 +64,19 @@ internal class WcSignUseCaseDelegate<MiddleAction, SignModel>(
|
|||
fun signFlow() = flow { onSign(state.updateAndGet { it.toSigning() }) }
|
||||
.onEach { newState -> state.update { newState } }
|
||||
.catch { exception ->
|
||||
val errorResult = state.value.toResult(exception.left())
|
||||
val errorResult = state.value
|
||||
.toResult(WcRequestError.UnknownError(exception).left())
|
||||
state.update { errorResult }
|
||||
}
|
||||
.onEach { state ->
|
||||
val step = state.domainStep as? WcSignStep.Result ?: return@onEach
|
||||
val event = step.result.fold(
|
||||
ifLeft = {
|
||||
ifLeft = { error ->
|
||||
WcAnalyticEvents.SignatureRequestFailed(
|
||||
session = context.session,
|
||||
rawRequest = context.rawSdkRequest,
|
||||
network = context.network,
|
||||
it.message.orEmpty(),
|
||||
errorCode = error.code() ?: error::class.simpleName.orEmpty(),
|
||||
)
|
||||
},
|
||||
ifRight = {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.data.walletconnect.utils
|
||||
|
||||
import com.domain.blockaid.models.transaction.*
|
||||
import com.tangem.data.walletconnect.sign.BlockAidChainNameConverter
|
||||
import com.tangem.domain.blockaid.BlockAidVerifier
|
||||
import com.tangem.domain.core.lce.Lce
|
||||
import com.tangem.domain.core.lce.LceFlow
|
||||
|
|
@ -16,6 +17,7 @@ import javax.inject.Inject
|
|||
|
||||
internal class BlockAidVerificationDelegate @Inject constructor(
|
||||
private val blockAidVerifier: BlockAidVerifier,
|
||||
private val blockAidChainNameConverter: BlockAidChainNameConverter,
|
||||
) {
|
||||
|
||||
fun getSecurityStatus(
|
||||
|
|
@ -47,7 +49,7 @@ internal class BlockAidVerificationDelegate @Inject constructor(
|
|||
}?.let { params ->
|
||||
blockAidVerifier.verifyTransaction(
|
||||
TransactionData(
|
||||
chain = network.name,
|
||||
chain = blockAidChainNameConverter.convert(network),
|
||||
accountAddress = accountAddress,
|
||||
method = rawSdkRequest.request.method,
|
||||
domainUrl = session.sdkModel.appMetaData.url,
|
||||
|
|
|
|||
|
|
@ -3,11 +3,10 @@ package com.tangem.data.walletconnect.utils
|
|||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.data.common.network.NetworkFactory
|
||||
import com.tangem.data.walletconnect.model.CAIP2
|
||||
import com.tangem.data.walletconnect.model.NamespaceKey
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.data.walletconnect.model.CAIP2
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.requireColdWallet
|
||||
|
||||
internal interface WcNamespaceConverter {
|
||||
|
||||
|
|
@ -27,7 +26,7 @@ internal interface WcNamespaceConverter {
|
|||
return NetworkFactory(excludedBlockchains).create(
|
||||
blockchain = blockchain,
|
||||
extraDerivationPath = null,
|
||||
scanResponse = wallet.requireColdWallet().scanResponse, // TODO [REDACTED_TASK_KEY]
|
||||
userWallet = wallet,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
package com.tangem.data.walletconnect.utils
|
||||
|
||||
import com.reown.walletkit.client.Wallet
|
||||
import com.tangem.data.common.currency.isCustomCoin
|
||||
import com.tangem.data.walletconnect.model.CAIP10
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesProducer
|
||||
import com.tangem.domain.tokens.MultiWalletCryptoCurrenciesSupplier
|
||||
import com.tangem.domain.tokens.TokensFeatureToggles
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.walletconnect.model.WcSession
|
||||
import com.tangem.domain.walletconnect.model.WcSessionApprove
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSessionRequest
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class WcNetworksConverter @Inject constructor(
|
||||
private val namespaceConverters: Set<WcNamespaceConverter>,
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val currenciesRepository: CurrenciesRepository,
|
||||
private val multiWalletCryptoCurrenciesSupplier: MultiWalletCryptoCurrenciesSupplier,
|
||||
private val tokensFeatureToggles: TokensFeatureToggles,
|
||||
) {
|
||||
|
||||
suspend fun findWalletNetworkForRequest(
|
||||
request: WcSdkSessionRequest,
|
||||
session: WcSession,
|
||||
requestAddress: String,
|
||||
): Network? {
|
||||
val wallet = session.wallet
|
||||
val allCoinNetwork = filterWalletNetworkForRequest(request.chainId.orEmpty(), session.wallet)
|
||||
|
||||
val requestNetwork = allCoinNetwork.find { network ->
|
||||
val address = walletManagersFacade.getDefaultAddress(wallet.walletId, network)
|
||||
requestAddress.lowercase() == address?.lowercase()
|
||||
}
|
||||
return requestNetwork
|
||||
}
|
||||
|
||||
/**
|
||||
* return network with not custom derivationPath or first custom or any
|
||||
*/
|
||||
suspend fun mainOrAnyWalletNetworkForRequest(rawChainId: String, wallet: UserWallet): Network? {
|
||||
val networks = filterWalletNetworkForRequest(rawChainId, wallet)
|
||||
return networks.firstOrNull { !isCustomCoin(it) } ?: networks.firstOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
* return all exist derivation networks
|
||||
*/
|
||||
suspend fun filterWalletNetworkForRequest(rawChainId: String, wallet: UserWallet): List<Network> {
|
||||
val walletNetworks = getWalletNetworks(wallet.walletId)
|
||||
|
||||
val blockchain = namespaceConverters
|
||||
.firstNotNullOfOrNull { it.toBlockchain(rawChainId) } ?: return listOf()
|
||||
|
||||
val allCoinNetwork = walletNetworks.filter { it.rawId == blockchain.id }
|
||||
return allCoinNetwork
|
||||
}
|
||||
|
||||
suspend fun findWalletNetworks(wallet: UserWallet, sdkSession: Wallet.Model.Session): Set<Network> {
|
||||
val walletNetworks = getWalletNetworks(wallet.walletId)
|
||||
val existNetworks = sdkSession.namespaces.values
|
||||
.map { it.accounts }.flatten().toSet()
|
||||
.mapNotNull { CAIP10.fromRaw(it) }
|
||||
.mapNotNullTo(mutableSetOf()) { caip10 ->
|
||||
val blockchain = namespaceConverters
|
||||
.firstNotNullOfOrNull { it.toBlockchain(caip10.chainId) }
|
||||
?: return@mapNotNullTo null
|
||||
walletNetworks
|
||||
// find all derivation
|
||||
.filter { it.rawId == blockchain.id }
|
||||
// find equal address
|
||||
.firstOrNull {
|
||||
val walletAddress = walletManagersFacade.getDefaultAddress(wallet.walletId, it)
|
||||
walletAddress?.lowercase() == caip10.accountAddress.lowercase()
|
||||
}
|
||||
}
|
||||
|
||||
return existNetworks
|
||||
}
|
||||
|
||||
suspend fun convertNetworksForApprove(sessionForApprove: WcSessionApprove): List<Network> {
|
||||
val walletNetworks = getWalletNetworks(sessionForApprove.wallet.walletId)
|
||||
return sessionForApprove.network
|
||||
.map { network -> walletNetworks.filter { walletNetwork -> walletNetwork.rawId == network.rawId } }
|
||||
.flatten()
|
||||
}
|
||||
|
||||
private suspend fun getWalletNetworks(userWalletId: UserWalletId): List<Network> {
|
||||
return if (tokensFeatureToggles.isWalletBalanceFetcherEnabled) {
|
||||
multiWalletCryptoCurrenciesSupplier.getSyncOrNull(
|
||||
params = MultiWalletCryptoCurrenciesProducer.Params(userWalletId = userWalletId),
|
||||
).orEmpty()
|
||||
} else {
|
||||
currenciesRepository.getMultiCurrencyWalletCurrenciesSync(userWalletId)
|
||||
}.filterIsInstance<CryptoCurrency.Coin>().map(CryptoCurrency.Coin::network)
|
||||
}
|
||||
}
|
||||
|
|
@ -58,8 +58,8 @@ internal class DefaultWcPairUseCaseTest {
|
|||
relayData = "",
|
||||
)
|
||||
|
||||
private val unsupportedDApp = "Apex Pro"
|
||||
private val unsupportedSdkProposal get() = sdkProposal.copy(name = unsupportedDApp)
|
||||
private val unsupportedDAppUrl = "dydx.trade"
|
||||
private val unsupportedSdkProposal get() = sdkProposal.copy(url = unsupportedDAppUrl)
|
||||
|
||||
private val sessionForApprove: WcSessionApprove
|
||||
get() = WcSessionApprove(
|
||||
|
|
@ -95,6 +95,8 @@ internal class DefaultWcPairUseCaseTest {
|
|||
sdkModel = WcSdkSessionConverter.convert(this),
|
||||
securityStatus = CheckDAppResult.SAFE,
|
||||
networks = setOf(),
|
||||
connectingTime = null,
|
||||
showWalletInfo = false,
|
||||
)
|
||||
|
||||
private fun useCaseFactory() = DefaultWcPairUseCase(
|
||||
|
|
@ -113,8 +115,7 @@ internal class DefaultWcPairUseCaseTest {
|
|||
coEvery {
|
||||
caipNamespaceDelegate.associate(
|
||||
sessionProposal = sdkProposal,
|
||||
userWallet = sessionForApprove.wallet,
|
||||
networks = sessionForApprove.network,
|
||||
sessionForApprove = sessionForApprove,
|
||||
)
|
||||
} returns mapOf()
|
||||
}
|
||||
|
|
@ -144,7 +145,7 @@ internal class DefaultWcPairUseCaseTest {
|
|||
|
||||
coEvery { sdkDelegate.pair(url) } returns sdkProposal.right()
|
||||
coEvery { sdkDelegate.approve(sdkApprove) } returns sdkApproveSuccess.right()
|
||||
coEvery { sessionsManager.saveSession(sessionForSave) } returns Unit
|
||||
coEvery { sessionsManager.saveSession(any()) } returns Unit
|
||||
coEvery { blockAidVerifier.verifyDApp(any()) } returns Either.catch { CheckDAppResult.SAFE }
|
||||
|
||||
val useCase = useCaseFactory()
|
||||
|
|
@ -162,9 +163,12 @@ internal class DefaultWcPairUseCaseTest {
|
|||
assertEquals(approveLoading, awaitItem())
|
||||
coVerifyOrder {
|
||||
sdkDelegate.approve(sdkApprove)
|
||||
sessionsManager.saveSession(sessionForSave)
|
||||
sessionsManager.saveSession(any())
|
||||
}
|
||||
assertEquals(result, awaitItem())
|
||||
val actual: WcPairState = awaitItem()
|
||||
assert(actual is WcPairState.Approving.Result)
|
||||
actual as WcPairState.Approving.Result
|
||||
assertEquals(result.session, actual.session)
|
||||
awaitComplete()
|
||||
}
|
||||
}
|
||||
|
|
@ -196,7 +200,7 @@ internal class DefaultWcPairUseCaseTest {
|
|||
@Test
|
||||
fun `success pair and reject unsupported dApp`() = runTest {
|
||||
coEvery { sdkDelegate.pair(url) } returns unsupportedSdkProposal.right()
|
||||
val unsupportedDAppError = WcPairState.Error(WcPairError.UnsupportedDomain)
|
||||
val unsupportedDAppError = WcPairState.Error(WcPairError.UnsupportedDApp(unsupportedSdkProposal.name))
|
||||
|
||||
val useCase = useCaseFactory()
|
||||
useCase.invoke().test {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
|
|||
import com.tangem.data.walletconnect.sign.*
|
||||
import com.tangem.data.walletconnect.sign.SignStateConverter.toResult
|
||||
import com.tangem.data.walletconnect.sign.SignStateConverter.toSigning
|
||||
import com.tangem.domain.walletconnect.model.WcRequestError
|
||||
import com.tangem.domain.walletconnect.model.WcSession
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcAppMetaData
|
||||
import com.tangem.domain.walletconnect.model.sdkcopy.WcSdkSession
|
||||
|
|
@ -32,6 +33,7 @@ internal class WcSignUseCaseDelegateTest {
|
|||
object : FinalActionCollector<TestSignModel> {}
|
||||
private val initSignModel = TestSignModel()
|
||||
private val analytics: AnalyticsEventHandler = mockk<AnalyticsEventHandler>(relaxed = true)
|
||||
private val simpleResult = "hex".right()
|
||||
private val rawRequestMock = WcSdkSessionRequest(
|
||||
topic = "",
|
||||
chainId = "",
|
||||
|
|
@ -49,6 +51,7 @@ internal class WcSignUseCaseDelegateTest {
|
|||
wallet = MockUserWalletFactory.create(),
|
||||
networks = setOf(),
|
||||
securityStatus = CheckDAppResult.FAILED_TO_VERIFY,
|
||||
connectingTime = 0L,
|
||||
sdkModel = WcSdkSession(
|
||||
topic = "",
|
||||
appMetaData = WcAppMetaData(
|
||||
|
|
@ -59,19 +62,20 @@ internal class WcSignUseCaseDelegateTest {
|
|||
redirect = "",
|
||||
),
|
||||
),
|
||||
showWalletInfo = false,
|
||||
),
|
||||
)
|
||||
|
||||
private val initState = WcSignState(initSignModel, WcSignStep.PreSign)
|
||||
private val signing = initState.toSigning()
|
||||
private val result = signing.toResult(Unit.right())
|
||||
private val testException = RuntimeException("test")
|
||||
private val result = signing.toResult(simpleResult)
|
||||
private val testException = WcRequestError.UnknownError(RuntimeException("test"))
|
||||
|
||||
private val successSign: suspend FlowCollector<WcSignState<TestSignModel>>.(
|
||||
currentState: WcSignState<TestSignModel>,
|
||||
) -> Unit = { state ->
|
||||
delay(2)
|
||||
emit(state.toResult(Unit.right()))
|
||||
emit(state.toResult(simpleResult))
|
||||
}
|
||||
|
||||
private val failedSign: suspend FlowCollector<WcSignState<TestSignModel>>.(
|
||||
|
|
@ -161,8 +165,9 @@ internal class WcSignUseCaseDelegateTest {
|
|||
|
||||
@Test
|
||||
fun `failed sign and catch unknown exception`() = runTest {
|
||||
val exception = RuntimeException("asd")
|
||||
val expectedErrorState = signing.toResult(exception.left())
|
||||
val exception = RuntimeException("test")
|
||||
val testException = WcRequestError.UnknownError(exception)
|
||||
val expectedErrorState = signing.toResult(testException.left())
|
||||
finalActionCollector = object : FinalActionCollector<TestSignModel> {
|
||||
override suspend fun SignCollector<TestSignModel>.onSign(state: WcSignState<TestSignModel>) {
|
||||
delay(2)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue