Updated on 2026-08-14

This commit is contained in:
Tangem 2023-11-16 14:04:21 +02:00
parent 479d554dae
commit 75075b53c8
18 changed files with 412 additions and 229 deletions

View file

@ -0,0 +1,52 @@
package com.tangem.domain.tokens
import arrow.core.Either
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.error.mapper.mapToTokenListError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.domain.tokens.operations.TokenListOperations
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
class GetCryptoCurrencyStatusUseCase(
internal val currenciesRepository: CurrenciesRepository,
internal val quotesRepository: QuotesRepository,
internal val networksRepository: NetworksRepository,
internal val dispatchers: CoroutineDispatcherProvider,
) {
operator fun invoke(userWalletId: UserWalletId): Flow<Either<TokenListError, List<CryptoCurrencyStatus>>> {
val operations = CurrenciesStatusesOperations(
userWalletId = userWalletId,
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
)
return operations.getCurrenciesStatusesFlow()
.map { maybeCurrenciesStatuses ->
maybeCurrenciesStatuses.mapLeft(CurrenciesStatusesOperations.Error::mapToTokenListError)
}
}
private fun createTokenList(
userWalletId: UserWalletId,
tokens: List<CryptoCurrencyStatus>,
): Flow<Either<TokenListError, TokenList>> {
val operations = TokenListOperations(
userWalletId = userWalletId,
tokens = tokens,
currenciesRepository = currenciesRepository,
)
return operations.getTokenListFlow().map { maybeTokenList ->
maybeTokenList.mapLeft(TokenListOperations.Error::mapToTokenListError)
}
}
}

View file

@ -193,26 +193,26 @@ internal class SwapRepositoryImpl @Inject constructor(
override suspend fun getCryptoCurrency(
userWallet: UserWallet,
currency: Currency,
currency: CryptoCurrency,
network: Network,
): CryptoCurrency? {
val blockchain = Blockchain.fromNetworkId(currency.networkId) ?: return null
val blockchain = Blockchain.fromNetworkId(currency.network.id.value) ?: return null
val cryptoCurrencyFactory = CryptoCurrencyFactory()
return when (currency) {
is Currency.NativeToken -> {
is CryptoCurrency.Coin -> {
cryptoCurrencyFactory.createCoin(
blockchain = blockchain,
extraDerivationPath = network.derivationPath.value,
derivationStyleProvider = userWallet.scanResponse.derivationStyleProvider,
)
}
is Currency.NonNativeToken -> {
is CryptoCurrency.Token -> {
val sdkToken = SdkToken(
name = currency.name,
symbol = currency.symbol,
contractAddress = currency.contractAddress,
decimals = currency.decimalCount,
id = currency.id,
decimals = currency.decimals,
id = currency.id.value,
)
cryptoCurrencyFactory.createToken(
sdkToken = sdkToken,
@ -258,7 +258,7 @@ internal class SwapRepositoryImpl @Inject constructor(
userWalletId: UserWalletId,
networkId: String,
derivationPath: String?,
currency: Currency,
currency: CryptoCurrency,
amount: BigDecimal?,
): String {
val blockchain =
@ -276,16 +276,16 @@ internal class SwapRepositoryImpl @Inject constructor(
) ?: error("Cannot cast to Approver")
}
private fun convertToAmount(amount: BigDecimal, currency: Currency, blockchain: Blockchain): Amount {
private fun convertToAmount(amount: BigDecimal, currency: CryptoCurrency, blockchain: Blockchain): Amount {
return when (currency) {
is Currency.NativeToken -> {
is CryptoCurrency.Token -> {
Amount(value = amount, blockchain = blockchain)
}
is Currency.NonNativeToken -> {
is CryptoCurrency.Coin -> {
Amount(
currencySymbol = currency.symbol,
value = amount,
decimals = currency.decimalCount,
decimals = currency.decimals,
)
}
}

View file

@ -1,11 +1,11 @@
package com.tangem.feature.swap.domain
import com.tangem.feature.swap.domain.models.domain.Currency
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.feature.swap.domain.models.domain.NetworkInfo
interface BlockchainInteractor {
fun getTokenDecimals(token: Currency): Int
fun getTokenDecimals(token: CryptoCurrency): Int
/**
* In app blockchain id, actual in blockchain sdk, not the same as networkId

View file

@ -1,11 +1,11 @@
package com.tangem.feature.swap.domain
import com.tangem.feature.swap.domain.models.domain.Currency
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.feature.swap.domain.models.domain.NetworkInfo
import com.tangem.lib.crypto.TransactionManager
import javax.inject.Inject
internal class BlockchainInteractorImpl @Inject constructor(
internal class DefaultBlockchainInteractor @Inject constructor(
private val transactionManager: TransactionManager,
) : BlockchainInteractor {
@ -23,11 +23,11 @@ internal class BlockchainInteractorImpl @Inject constructor(
return transactionManager.getExplorerTransactionLink(networkId, txAddress)
}
override fun getTokenDecimals(token: Currency): Int {
return if (token is Currency.NonNativeToken) {
token.decimalCount
override fun getTokenDecimals(token: CryptoCurrency): Int {
return if (token is CryptoCurrency.Token) {
token.decimals
} else {
transactionManager.getNativeTokenDecimals(token.networkId)
transactionManager.getNativeTokenDecimals(token.network.id.value)
}
}
}

View file

@ -9,9 +9,7 @@ import java.math.BigDecimal
interface SwapInteractor {
suspend fun getPairs(currency: Currency): List<SwapPair>
suspend fun getPairs(initialCurrency: LeastTokenInfo, currenciesList: List<CryptoCurrency>): List<SwapPairLeast>
suspend fun getTokensDataState(currency: Currency): TokensDataStateExpress
fun initDerivationPathAndNetwork(derivationPath: String?, network: Network?)
@ -31,17 +29,17 @@ interface SwapInteractor {
*
* @param networkId networkId for tokens
* @param searchQuery string query for search
* @return [FoundTokensState] that contains list of tokens matching condition query
* @return [FoundTokensStateExpress] that contains list of tokens matching condition query
*/
suspend fun searchTokens(networkId: String, searchQuery: String): FoundTokensState
suspend fun searchTokens(networkId: String, searchQuery: String): FoundTokensStateExpress
/**
* Find specific token by id, null if not found
*
* @param id token id
* @return [Currency] or null
* @return [CryptoCurrency] or null
*/
fun findTokenById(id: String): Currency?
fun findTokenById(id: String): CryptoCurrency?
/**
* Gives permission to swap, this starts scan card process
@ -66,8 +64,8 @@ interface SwapInteractor {
@Throws(IllegalStateException::class)
suspend fun findBestQuote(
networkId: String,
fromToken: Currency,
toToken: Currency,
fromToken: CryptoCurrency,
toToken: CryptoCurrency,
amountToSwap: String,
selectedFee: FeeType = FeeType.NORMAL,
): SwapState
@ -88,8 +86,8 @@ interface SwapInteractor {
suspend fun onSwap(
networkId: String,
swapStateData: SwapStateData,
currencyToSend: Currency,
currencyToGet: Currency,
currencyToSend: CryptoCurrency,
currencyToGet: CryptoCurrency,
amountToSwap: String,
fee: TxFee,
): TxState
@ -100,16 +98,16 @@ interface SwapInteractor {
* @param networkId
* @param token
*/
fun getTokenBalance(networkId: String, token: Currency): SwapAmount
fun getTokenBalance(networkId: String, token: CryptoCurrency): SwapAmount
fun isAvailableToSwap(networkId: String): Boolean
fun getSwapAmountForToken(amount: String, token: Currency): SwapAmount
fun getSwapAmountForToken(amount: String, token: CryptoCurrency): SwapAmount
suspend fun checkFeeIsEnough(
fee: BigDecimal?,
spendAmount: SwapAmount,
networkId: String,
fromToken: Currency,
fromToken: CryptoCurrency,
): Boolean
}

View file

@ -1,12 +1,13 @@
package com.tangem.feature.swap.domain
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
import arrow.core.getOrElse
import com.tangem.domain.tokens.GetCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.feature.swap.domain.cache.SwapDataCache
import com.tangem.feature.swap.domain.converters.SwapCurrencyConverter
@ -21,6 +22,7 @@ import com.tangem.lib.crypto.UserWalletManager
import com.tangem.lib.crypto.models.*
import com.tangem.lib.crypto.models.transactions.SendTxResult
import com.tangem.utils.toFiatString
import kotlinx.coroutines.flow.first
import timber.log.Timber
import java.math.BigDecimal
import java.math.RoundingMode
@ -37,7 +39,7 @@ internal class SwapInteractorImpl @Inject constructor(
private val networksRepository: NetworksRepository,
private val walletFeatureToggles: WalletFeatureToggles,
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase,
private val getMultiCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusUseCase,
) : SwapInteractor {
// TODO: Move to DI
@ -50,26 +52,65 @@ internal class SwapInteractorImpl @Inject constructor(
private var derivationPath: String? = null
private var network: Network? = null
override suspend fun getPairs(currency: Currency): List<SwapPair> {
val currencies = getSelectedWalletSyncUseCase().fold(
ifLeft = { emptyList() },
ifRight = { selectedWallet ->
getCryptoCurrenciesUseCase(selectedWallet.walletId).fold(
ifLeft = { emptyList() },
ifRight = { it },
)
},
override suspend fun getTokensDataState(currency: Currency): TokensDataStateExpress {
val selectedWallet = getSelectedWalletSyncUseCase().fold(
ifLeft = { null },
ifRight = { it },
)
val pairs = getPairs(
requireNotNull(selectedWallet)
val currencyStatuses = getMultiCryptoCurrencyStatusUseCase(selectedWallet.walletId)
.first()
.getOrElse { emptyList() }
// .filter { it.currency.network.backendId != currency.networkId }
val currencies = currencyStatuses.map { it.currency }
val pairsLeast = getPairs(
initialCurrency = LeastTokenInfo(
contractAddress = (currency as? Currency.NonNativeToken)?.contractAddress ?: "0",
network = currency.networkId,
),
currenciesList = currencies,
currenciesList = currencyStatuses.map { it.currency },
)
return createCryptoCurrencyPairs(pairs, currencies)
val pairs = createCryptoCurrencyPairs(pairsLeast, currencies)
val initialCryptoCurrency = mapLegacyCurrencyToCryptoCurrency(currency, currencyStatuses)
?: error("Initial crypto currency must not be null")
return TokensDataStateExpress(
initialCryptoCurrency = initialCryptoCurrency,
preselectTokens = getPreselectTokens(currency, currencyStatuses),
foundTokensState = FoundTokensStateExpress(emptyList(), emptyList()),
pairs = pairs,
)
}
private fun getPreselectTokens(currency: Currency, currencies: List<CryptoCurrencyStatus>): PreselectTokensExpress {
val from = mapLegacyCurrencyToCryptoCurrency(currency, currencies)
val to = currencies.firstOrNull()?.currency // TODO choose of 3 variants
if (from != null && to != null) {
return PreselectTokensExpress(
fromToken = from,
toToken = to,
)
} else {
error("From and to currencies must not be null")
}
}
private fun mapLegacyCurrencyToCryptoCurrency(
currency: Currency,
currencies: List<CryptoCurrencyStatus>,
): CryptoCurrency? {
return currencies.map { it.currency }
.find {
it.network.backendId == currency.networkId &&
it.getContractAddress() == currency.getContractAddress()
}
}
private fun createCryptoCurrencyPairs(
@ -97,22 +138,35 @@ internal class SwapInteractorImpl @Inject constructor(
): CryptoCurrency? {
return cryptoCurrenciesList.find {
it.network.backendId == leastTokenInfo.network &&
(it as? CryptoCurrency.Token)?.contractAddress ?: "0" == leastTokenInfo.contractAddress
it.getContractAddress() == leastTokenInfo.contractAddress
}
}
override suspend fun getPairs(
initialCurrency: LeastTokenInfo,
currenciesList: List<CryptoCurrency>,
): List<SwapPairLeast> {
private fun CryptoCurrency.getContractAddress(): String {
return when (this) {
is CryptoCurrency.Token -> this.contractAddress
is CryptoCurrency.Coin -> "0"
}
}
private fun Currency.getContractAddress(): String {
return when (this) {
is Currency.NativeToken -> "0"
is Currency.NonNativeToken -> this.contractAddress
}
}
suspend fun getPairs(initialCurrency: LeastTokenInfo, currenciesList: List<CryptoCurrency>): List<SwapPairLeast> {
return repository.getPairs(initialCurrency, currenciesList)
}
@Deprecated("used in old swap mechanism")
override fun initDerivationPathAndNetwork(derivationPath: String?, network: Network?) {
this.derivationPath = derivationPath
this.network = network
}
@Deprecated("used in old swap mechanism")
override suspend fun initTokensToSwap(initialCurrency: Currency): TokensDataState {
// TODO: refactor this function
val networkId = initialCurrency.networkId
@ -134,7 +188,7 @@ internal class SwapInteractorImpl @Inject constructor(
allLoadedTokens.firstOrNull { it.symbol == token.symbol }?.let {
loadedOnWalletsMap.add(it.symbol)
it
} ?: swapCurrencyConverter.convertBack(token)
} ?: TODO()
}
.filter { it.symbol != initialCurrency.symbol && allLoadedTokens.contains(it) }
val loadedTokens = allLoadedTokens
@ -146,21 +200,22 @@ internal class SwapInteractorImpl @Inject constructor(
val appCurrency = userWalletManager.getUserAppCurrency()
val rates = repository.getRates(appCurrency.code, tokensInWallet.map { it.id })
cache.cacheBalances(networkId, derivationPath, tokensBalance)
cache.cacheLoadedTokens(loadedTokens.map { TokenWithBalance(it) })
cache.cacheInWalletTokens(getTokensWithBalance(tokensInWallet, tokensBalance, rates, appCurrency))
// cache.cacheLoadedTokens(loadedTokens.map { TokenWithBalance(it) })
// cache.cacheInWalletTokens(getTokensWithBalance(tokensInWallet, tokensBalance, rates, appCurrency))
return TokensDataState(
preselectTokens = PreselectTokens(
fromToken = initialCurrency,
toToken = selectToToken(initialCurrency, tokensInWallet, loadedTokens),
),
foundTokensState = FoundTokensState(
tokensInWallet = cache.getInWalletTokens(),
loadedTokens = cache.getLoadedTokens(),
tokensInWallet = emptyList(), // cache.getInWalletTokens(),
loadedTokens = emptyList(), // cache.getLoadedTokens(),
),
)
}
override suspend fun searchTokens(networkId: String, searchQuery: String): FoundTokensState {
@Deprecated("used in old swap mechanism")
override suspend fun searchTokens(networkId: String, searchQuery: String): FoundTokensStateExpress {
val searchQueryLowerCase = searchQuery.lowercase()
val tokensInWallet = cache.getInWalletTokens()
.filter {
@ -172,19 +227,21 @@ internal class SwapInteractorImpl @Inject constructor(
it.token.name.lowercase().contains(searchQueryLowerCase) ||
it.token.symbol.lowercase().contains(searchQueryLowerCase)
}
return FoundTokensState(
return FoundTokensStateExpress(
tokensInWallet = tokensInWallet,
loadedTokens = loadedTokens,
)
}
override fun findTokenById(id: String): Currency? {
@Deprecated("used in old swap mechanism")
override fun findTokenById(id: String): CryptoCurrency? {
val tokensInWallet = cache.getInWalletTokens()
val loadedTokens = cache.getLoadedTokens()
return tokensInWallet.firstOrNull { it.token.id == id }?.token
?: loadedTokens.firstOrNull { it.token.id == id }?.token
return tokensInWallet.firstOrNull { it.token.id.value == id }?.token
?: loadedTokens.firstOrNull { it.token.id.value == id }?.token
}
@Deprecated("used in old swap mechanism")
override suspend fun givePermissionToSwap(networkId: String, permissionOptions: PermissionOptions): TxState {
val dataToSign = if (permissionOptions.approveType == SwapApproveType.UNLIMITED) {
getApproveData(
@ -223,10 +280,11 @@ internal class SwapInteractorImpl @Inject constructor(
}
}
@Deprecated("used in old swap mechanism")
override suspend fun findBestQuote(
networkId: String,
fromToken: Currency,
toToken: Currency,
fromToken: CryptoCurrency,
toToken: CryptoCurrency,
amountToSwap: String,
selectedFee: FeeType,
): SwapState {
@ -268,11 +326,12 @@ internal class SwapInteractorImpl @Inject constructor(
}
}
@Deprecated("used in old swap mechanism")
override suspend fun onSwap(
networkId: String,
swapStateData: SwapStateData,
currencyToSend: Currency,
currencyToGet: Currency,
currencyToSend: CryptoCurrency,
currencyToGet: CryptoCurrency,
amountToSwap: String,
fee: TxFee,
): TxState {
@ -322,7 +381,8 @@ internal class SwapInteractorImpl @Inject constructor(
}
}
override fun getTokenBalance(networkId: String, token: Currency): SwapAmount {
@Deprecated("used in old swap mechanism")
override fun getTokenBalance(networkId: String, token: CryptoCurrency): SwapAmount {
return cache.getBalanceForToken(
networkId = networkId,
derivationPath = derivationPath,
@ -330,25 +390,28 @@ internal class SwapInteractorImpl @Inject constructor(
) ?: SwapAmount(BigDecimal.ZERO, getTokenDecimals(token))
}
@Deprecated("used in old swap mechanism")
override fun isAvailableToSwap(networkId: String): Boolean {
return ONE_INCH_SUPPORTED_NETWORKS.contains(networkId)
}
override fun getSwapAmountForToken(amount: String, token: Currency): SwapAmount {
@Deprecated("used in old swap mechanism")
override fun getSwapAmountForToken(amount: String, token: CryptoCurrency): SwapAmount {
val amountDecimal = requireNotNull(toBigDecimalOrNull(amount)) { "wrong amount format" }
return SwapAmount(amountDecimal, getTokenDecimals(token))
}
private suspend fun onSuccessLegacyFlow(currency: Currency) {
@Deprecated("used in old swap mechanism")
private suspend fun onSuccessLegacyFlow(currency: CryptoCurrency) {
userWalletManager.addToken(swapCurrencyConverter.convert(currency), derivationPath)
userWalletManager.refreshWallet()
}
private suspend fun onSuccessNewFlow(currency: Currency) {
val network = network ?: return
@Deprecated("used in old swap mechanism")
private suspend fun onSuccessNewFlow(currency: CryptoCurrency) {
getSelectedWalletSyncUseCase().fold(
ifRight = { userWallet ->
getAndAddCryptoCurrency(userWallet, currency, network)
addCryptoCurrenciesUseCase(userWallet.walletId, currency)
},
ifLeft = {
Timber.e("Swap Error on getSelectedWalletUseCase")
@ -356,24 +419,20 @@ internal class SwapInteractorImpl @Inject constructor(
)
}
private suspend fun getAndAddCryptoCurrency(userWallet: UserWallet, currency: Currency, network: Network) {
repository.getCryptoCurrency(userWallet, currency, network)?.let { cryptoCurrency ->
addCryptoCurrenciesUseCase(userWallet.walletId, cryptoCurrency)
}
}
@Deprecated("used in old swap mechanism")
private fun getTangemFee(): Double {
return repository.getTangemFee()
}
private fun getTokenDecimals(token: Currency): Int {
return if (token is Currency.NonNativeToken) {
token.decimalCount
private fun getTokenDecimals(token: CryptoCurrency): Int {
return if (token is CryptoCurrency.Token) {
token.decimals
} else {
transactionManager.getNativeTokenDecimals(token.networkId)
transactionManager.getNativeTokenDecimals(token.network.id.value)
}
}
@Deprecated("used in old swap mechanism")
private fun selectToToken(
initialToken: Currency,
tokensInWallet: List<Currency>,
@ -394,6 +453,7 @@ internal class SwapInteractorImpl @Inject constructor(
return toToken
}
@Deprecated("used in old swap mechanism")
private fun getTokensWithBalance(
tokens: List<Currency>,
balances: Map<String, SwapAmount>,
@ -418,8 +478,8 @@ internal class SwapInteractorImpl @Inject constructor(
}
}
private suspend fun isAllowedToSpend(networkId: String, fromToken: Currency, amount: SwapAmount): Boolean {
if (fromToken is Currency.NativeToken) return true
private suspend fun isAllowedToSpend(networkId: String, fromToken: CryptoCurrency, amount: SwapAmount): Boolean {
if (fromToken is CryptoCurrency.Coin) return true
return getSelectedWalletSyncUseCase().fold(
ifRight = { userWallet ->
val allowance = repository.getAllowance(
@ -438,7 +498,11 @@ internal class SwapInteractorImpl @Inject constructor(
)
}
private fun createEmptyAmountState(networkId: String, fromToken: Currency, toToken: Currency): SwapState {
private fun createEmptyAmountState(
networkId: String,
fromToken: CryptoCurrency,
toToken: CryptoCurrency,
): SwapState {
val appCurrency = userWalletManager.getUserAppCurrency()
val fromTokenBalance = cache.getBalanceForToken(networkId, derivationPath, fromToken.symbol)
val toTokenBalance = cache.getBalanceForToken(networkId, derivationPath, toToken.symbol)
@ -462,8 +526,8 @@ internal class SwapInteractorImpl @Inject constructor(
fromTokenAddress: String,
toTokenAddress: String,
amount: SwapAmount,
fromToken: Currency,
toToken: Currency,
fromToken: CryptoCurrency,
toToken: CryptoCurrency,
isAllowedToSpend: Boolean,
isBalanceWithoutFeeEnough: Boolean,
): SwapState {
@ -520,8 +584,8 @@ internal class SwapInteractorImpl @Inject constructor(
networkId: String,
fromTokenAddress: String,
toTokenAddress: String,
fromToken: Currency,
toToken: Currency,
fromToken: CryptoCurrency,
toToken: CryptoCurrency,
amount: SwapAmount,
selectedFee: FeeType,
): SwapState {
@ -585,45 +649,45 @@ internal class SwapInteractorImpl @Inject constructor(
@Suppress("LongParameterList")
private suspend fun updateBalances(
networkId: String,
fromToken: Currency,
toToken: Currency,
fromToken: CryptoCurrency,
toToken: CryptoCurrency,
fromTokenAmount: SwapAmount,
toTokenAmount: SwapAmount,
swapStateData: SwapStateData?,
): SwapState.QuotesLoadedState {
val appCurrency = userWalletManager.getUserAppCurrency()
val nativeToken = userWalletManager.getNativeTokenForNetwork(networkId)
val rates = repository.getRates(appCurrency.code, listOf(fromToken.id, toToken.id, nativeToken.id))
val rates = repository.getRates(appCurrency.code, listOf(fromToken.id.value, toToken.id.value, nativeToken.id))
val fromTokenBalance = cache.getBalanceForToken(networkId, derivationPath, fromToken.symbol)
val toTokenBalance = cache.getBalanceForToken(networkId, derivationPath, toToken.symbol)
return SwapState.QuotesLoadedState(
fromTokenInfo = TokenSwapInfo(
tokenAmount = fromTokenAmount,
coinId = fromToken.id,
coinId = fromToken.id.value,
tokenWalletBalance = fromTokenBalance?.let { amountFormatter.formatSwapAmountToUI(it, "") }
?: ZERO_BALANCE,
tokenFiatBalance = fromTokenAmount.value.toFiatString(
rateValue = rates[fromToken.id]?.toBigDecimal() ?: BigDecimal.ZERO,
rateValue = rates[fromToken.id.value]?.toBigDecimal() ?: BigDecimal.ZERO,
fiatCurrencyName = appCurrency.symbol,
formatWithSpaces = true,
),
),
toTokenInfo = TokenSwapInfo(
tokenAmount = toTokenAmount,
coinId = toToken.id,
coinId = toToken.id.value,
tokenWalletBalance = toTokenBalance?.let { amountFormatter.formatSwapAmountToUI(it, "") }
?: ZERO_BALANCE,
tokenFiatBalance = toTokenAmount.value.toFiatString(
rateValue = rates[toToken.id]?.toBigDecimal() ?: BigDecimal.ZERO,
rateValue = rates[toToken.id.value]?.toBigDecimal() ?: BigDecimal.ZERO,
fiatCurrencyName = appCurrency.symbol,
formatWithSpaces = true,
),
),
priceImpact = calculatePriceImpact(
fromTokenAmount = fromTokenAmount.value,
fromRate = rates[fromToken.id] ?: 0.0,
fromRate = rates[fromToken.id.value] ?: 0.0,
toTokenAmount = toTokenAmount.value,
toRate = rates[toToken.id] ?: 0.0,
toRate = rates[toToken.id.value] ?: 0.0,
),
networkCurrency = userWalletManager.getNetworkCurrency(networkId),
swapDataModel = swapStateData,
@ -634,7 +698,7 @@ internal class SwapInteractorImpl @Inject constructor(
@Suppress("LongParameterList")
private suspend fun updatePermissionState(
networkId: String,
fromToken: Currency,
fromToken: CryptoCurrency,
swapAmount: SwapAmount,
quotesLoadedState: SwapState.QuotesLoadedState,
): SwapState.QuotesLoadedState {
@ -691,7 +755,7 @@ internal class SwapInteractorImpl @Inject constructor(
)
}
private suspend fun syncWalletBalanceForTokens(networkId: String, tokens: List<Currency>) {
private suspend fun syncWalletBalanceForTokens(networkId: String, tokens: List<CryptoCurrency>) {
val tokensToSync = tokens.filter { cache.getBalanceForToken(networkId, derivationPath, it.symbol) == null }
if (tokensToSync.isNotEmpty()) {
val tokensBalance =
@ -746,12 +810,12 @@ internal class SwapInteractorImpl @Inject constructor(
private fun isBalanceEnough(
networkId: String,
fromToken: Currency,
fromToken: CryptoCurrency,
amount: SwapAmount,
fee: BigDecimal?,
): Boolean {
val tokenBalance = getTokenBalance(networkId, fromToken).value
return if (fromToken is Currency.NonNativeToken) {
return if (fromToken is CryptoCurrency.Token) {
tokenBalance >= amount.value
} else {
tokenBalance > amount.value.plus(fee ?: BigDecimal.ZERO)
@ -762,12 +826,12 @@ internal class SwapInteractorImpl @Inject constructor(
return userWalletManager.getWalletAddress(networkId, derivationPath)
}
private fun getTokenAddress(currency: Currency): String {
private fun getTokenAddress(currency: CryptoCurrency): String {
return when (currency) {
is Currency.NativeToken -> {
is CryptoCurrency.Coin -> {
DEFAULT_BLOCKCHAIN_INCH_ADDRESS
}
is Currency.NonNativeToken -> {
is CryptoCurrency.Token -> {
currency.contractAddress
}
}
@ -777,7 +841,7 @@ internal class SwapInteractorImpl @Inject constructor(
fee: BigDecimal?,
spendAmount: SwapAmount,
networkId: String,
fromToken: Currency,
fromToken: CryptoCurrency,
): Boolean {
if (fee == null) {
return false
@ -785,12 +849,12 @@ internal class SwapInteractorImpl @Inject constructor(
val nativeTokenBalance = userWalletManager.getNativeTokenBalance(networkId, derivationPath)
val percentsToFeeIncrease = BigDecimal.ONE
return when (fromToken) {
is Currency.NativeToken -> {
is CryptoCurrency.Coin -> {
nativeTokenBalance?.let { balance ->
return balance.value.minus(spendAmount.value) > fee.multiply(percentsToFeeIncrease)
} ?: false
}
is Currency.NonNativeToken -> {
is CryptoCurrency.Token -> {
nativeTokenBalance?.let { balance ->
return balance.value > fee.multiply(percentsToFeeIncrease)
} ?: false
@ -816,7 +880,7 @@ internal class SwapInteractorImpl @Inject constructor(
private suspend fun getApproveData(
networkId: String,
derivationPath: String?,
fromToken: Currency,
fromToken: CryptoCurrency,
swapAmount: SwapAmount? = null,
): String {
return getSelectedWalletSyncUseCase().fold(

View file

@ -1,8 +1,6 @@
package com.tangem.feature.swap.domain
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.swap.domain.models.data.AggregatedSwapDataModel
import com.tangem.feature.swap.domain.models.domain.*
@ -46,8 +44,6 @@ interface SwapRepository {
*/
fun getTangemFee(): Double
suspend fun getCryptoCurrency(userWallet: UserWallet, currency: Currency, network: Network): CryptoCurrency?
@Throws(IllegalStateException::class)
suspend fun getAllowance(
userWalletId: UserWalletId,
@ -62,7 +58,7 @@ interface SwapRepository {
userWalletId: UserWalletId,
networkId: String,
derivationPath: String?,
currency: Currency,
currency: CryptoCurrency,
amount: BigDecimal?,
): String
}

View file

@ -2,19 +2,19 @@ package com.tangem.feature.swap.domain.cache
import com.tangem.feature.swap.domain.models.SwapAmount
import com.tangem.feature.swap.domain.models.domain.Currency
import com.tangem.feature.swap.domain.models.ui.TokenWithBalance
import com.tangem.feature.swap.domain.models.ui.TokenWithBalanceExpress
import java.math.BigDecimal
interface SwapDataCache {
fun cacheAvailableToSwapTokens(networkId: String, tokens: List<Currency>)
fun cacheInWalletTokens(tokens: List<TokenWithBalance>)
fun cacheLoadedTokens(tokens: List<TokenWithBalance>)
fun cacheInWalletTokens(tokens: List<TokenWithBalanceExpress>)
fun cacheLoadedTokens(tokens: List<TokenWithBalanceExpress>)
fun cacheBalances(networkId: String, derivationPath: String?, balances: Map<String, SwapAmount>)
fun cacheLastFeeForNetwork(fee: BigDecimal, networkId: String)
fun getAvailableTokens(networkId: String): List<Currency>
fun getInWalletTokens(): List<TokenWithBalance>
fun getLoadedTokens(): List<TokenWithBalance>
fun getInWalletTokens(): List<TokenWithBalanceExpress>
fun getLoadedTokens(): List<TokenWithBalanceExpress>
fun getBalanceForToken(networkId: String, derivationPath: String?, symbol: String): SwapAmount?
fun getLastFeeForNetwork(networkId: String): BigDecimal?
}

View file

@ -2,7 +2,7 @@ package com.tangem.feature.swap.domain.cache
import com.tangem.feature.swap.domain.models.SwapAmount
import com.tangem.feature.swap.domain.models.domain.Currency
import com.tangem.feature.swap.domain.models.ui.TokenWithBalance
import com.tangem.feature.swap.domain.models.ui.TokenWithBalanceExpress
import java.math.BigDecimal
class SwapDataCacheImpl : SwapDataCache {
@ -10,28 +10,28 @@ class SwapDataCacheImpl : SwapDataCache {
private val availableTokensForNetwork: MutableMap<String, List<Currency>> = mutableMapOf()
private val feesForNetworks: MutableMap<String, BigDecimal> = mutableMapOf()
private val tokensBalances: MutableMap<String, Map<String, SwapAmount>> = mutableMapOf()
private val lastInWalletTokens = mutableListOf<TokenWithBalance>()
private val lastLoadedTokens = mutableListOf<TokenWithBalance>()
private val lastInWalletTokens = mutableListOf<TokenWithBalanceExpress>()
private val lastLoadedTokens = mutableListOf<TokenWithBalanceExpress>()
override fun cacheLastFeeForNetwork(fee: BigDecimal, networkId: String) {
feesForNetworks[networkId] = fee
}
override fun cacheInWalletTokens(tokens: List<TokenWithBalance>) {
override fun cacheInWalletTokens(tokens: List<TokenWithBalanceExpress>) {
lastInWalletTokens.clear()
lastInWalletTokens.addAll(tokens)
}
override fun cacheLoadedTokens(tokens: List<TokenWithBalance>) {
override fun cacheLoadedTokens(tokens: List<TokenWithBalanceExpress>) {
lastLoadedTokens.clear()
lastLoadedTokens.addAll(tokens)
}
override fun getInWalletTokens(): List<TokenWithBalance> {
override fun getInWalletTokens(): List<TokenWithBalanceExpress> {
return lastInWalletTokens
}
override fun getLoadedTokens(): List<TokenWithBalance> {
override fun getLoadedTokens(): List<TokenWithBalanceExpress> {
return lastLoadedTokens
}

View file

@ -1,54 +1,29 @@
package com.tangem.feature.swap.domain.converters
import com.tangem.feature.swap.domain.models.domain.Currency
import com.tangem.utils.converter.TwoWayConverter
import com.tangem.lib.crypto.models.Currency as CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.lib.crypto.models.Currency
import com.tangem.utils.converter.Converter
class SwapCurrencyConverter : TwoWayConverter<Currency, CryptoCurrency> {
class SwapCurrencyConverter : Converter<CryptoCurrency, Currency> {
override fun convert(value: Currency): CryptoCurrency {
override fun convert(value: CryptoCurrency): Currency {
return when (value) {
is Currency.NonNativeToken -> {
CryptoCurrency.NonNativeToken(
id = value.id,
name = value.name,
symbol = value.symbol,
networkId = value.networkId,
contractAddress = value.contractAddress,
decimalCount = value.decimalCount,
)
}
is Currency.NativeToken -> {
CryptoCurrency.NativeToken(
id = value.id,
name = value.name,
symbol = value.symbol,
networkId = value.networkId,
)
}
}
}
override fun convertBack(value: CryptoCurrency): Currency {
return when (value) {
is CryptoCurrency.NonNativeToken -> {
is CryptoCurrency.Token -> {
Currency.NonNativeToken(
id = value.id,
id = value.id.value,
name = value.name,
symbol = value.symbol,
networkId = value.networkId,
networkId = value.network.id.value,
contractAddress = value.contractAddress,
decimalCount = value.decimalCount,
logoUrl = "",
decimalCount = value.decimals,
)
}
is CryptoCurrency.NativeToken -> {
is CryptoCurrency.Coin -> {
Currency.NativeToken(
id = value.id,
id = value.id.value,
name = value.name,
symbol = value.symbol,
networkId = value.networkId,
logoUrl = "",
networkId = value.network.id.value,
)
}
}

View file

@ -1,8 +1,11 @@
package com.tangem.feature.swap.domain.di
import com.tangem.domain.tokens.GetCardTokensListUseCase
import com.tangem.domain.tokens.GetCryptoCurrenciesUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.tokens.repository.QuotesRepository
import com.tangem.domain.wallets.legacy.WalletsStateHolder
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.feature.swap.domain.*
@ -10,6 +13,7 @@ import com.tangem.feature.swap.domain.cache.SwapDataCacheImpl
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
import com.tangem.lib.crypto.TransactionManager
import com.tangem.lib.crypto.UserWalletManager
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -31,7 +35,8 @@ class SwapDomainModule {
networksRepository: NetworksRepository,
walletFeatureToggles: WalletFeatureToggles,
@SwapScope getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
@SwapScope getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase,
@SwapScope getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusUseCase,
@SwapScope getCardTokensListUseCase: GetCardTokensListUseCase,
): SwapInteractor {
return SwapInteractorImpl(
transactionManager = transactionManager,
@ -43,14 +48,15 @@ class SwapDomainModule {
networksRepository = networksRepository,
walletFeatureToggles = walletFeatureToggles,
getSelectedWalletSyncUseCase = getSelectedWalletSyncUseCase,
getCryptoCurrenciesUseCase = getCryptoCurrenciesUseCase,
getMultiCryptoCurrencyStatusUseCase = getCryptoCurrencyStatusUseCase,
getCardTokensListUseCase = getCardTokensListUseCase,
)
}
@Provides
@Singleton
fun provideBlockchainInteractor(transactionManager: TransactionManager): BlockchainInteractor {
return BlockchainInteractorImpl(
return DefaultBlockchainInteractor(
transactionManager = transactionManager,
)
}
@ -68,6 +74,40 @@ class SwapDomainModule {
fun providesGetCryptoCurrenciesUseCase(currenciesRepository: CurrenciesRepository): GetCryptoCurrenciesUseCase {
return GetCryptoCurrenciesUseCase(currenciesRepository = currenciesRepository)
}
@SwapScope
@Provides
@Singleton
fun providesGetCryptoCurrencyStatusUseCase(
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
dispatchers: CoroutineDispatcherProvider,
): GetCryptoCurrencyStatusUseCase {
return GetCryptoCurrencyStatusUseCase(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
dispatchers = dispatchers,
)
}
@SwapScope
@Provides
@Singleton
fun providesGetCardTokensListUseCase(
currenciesRepository: CurrenciesRepository,
quotesRepository: QuotesRepository,
networksRepository: NetworksRepository,
dispatchers: CoroutineDispatcherProvider,
): GetCardTokensListUseCase {
return GetCardTokensListUseCase(
currenciesRepository = currenciesRepository,
quotesRepository = quotesRepository,
networksRepository = networksRepository,
dispatchers = dispatchers,
)
}
}
@Qualifier

View file

@ -1,5 +1,6 @@
package com.tangem.feature.swap.domain.models.domain
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.feature.swap.domain.models.ui.RequestApproveStateData
import com.tangem.feature.swap.domain.models.ui.TxFee
@ -15,7 +16,7 @@ import com.tangem.feature.swap.domain.models.ui.TxFee
data class PermissionOptions(
val approveData: RequestApproveStateData,
val forTokenContractAddress: String,
val fromToken: Currency,
val fromToken: CryptoCurrency,
val approveType: SwapApproveType,
val txFee: TxFee,
)

View file

@ -0,0 +1,31 @@
package com.tangem.feature.swap.domain.models.ui
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.feature.swap.domain.models.domain.SwapPair
data class TokensDataStateExpress(
val initialCryptoCurrency: CryptoCurrency,
val preselectTokens: PreselectTokensExpress,
val foundTokensState: FoundTokensStateExpress,
val pairs: List<SwapPair>,
)
data class FoundTokensStateExpress(
val tokensInWallet: List<TokenWithBalanceExpress>,
val loadedTokens: List<TokenWithBalanceExpress>,
)
data class PreselectTokensExpress(
val fromToken: CryptoCurrency,
val toToken: CryptoCurrency,
)
data class TokenWithBalanceExpress(
val token: CryptoCurrency,
val tokenBalanceData: TokenBalanceDataExpress? = null,
)
data class TokenBalanceDataExpress(
val amount: String?,
val amountEquivalent: String?,
)

View file

@ -2,10 +2,10 @@ package com.tangem.feature.swap.converters
import com.tangem.common.Provider
import com.tangem.core.ui.components.currency.tokenicon.TokenIconState
import com.tangem.feature.swap.domain.models.domain.Currency
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.feature.swap.domain.models.domain.NetworkInfo
import com.tangem.feature.swap.domain.models.ui.FoundTokensState
import com.tangem.feature.swap.domain.models.ui.TokenWithBalance
import com.tangem.feature.swap.domain.models.ui.FoundTokensStateExpress
import com.tangem.feature.swap.domain.models.ui.TokenWithBalanceExpress
import com.tangem.feature.swap.models.Network
import com.tangem.feature.swap.models.SwapSelectTokenStateHolder
import com.tangem.feature.swap.models.TokenBalanceData
@ -18,7 +18,7 @@ class TokensDataConverter(
private val isBalanceHiddenProvider: Provider<Boolean>,
) {
fun convertWithNetwork(value: FoundTokensState, network: NetworkInfo): SwapSelectTokenStateHolder {
fun convertWithNetwork(value: FoundTokensStateExpress, network: NetworkInfo): SwapSelectTokenStateHolder {
return SwapSelectTokenStateHolder(
availableTokens = value.tokensInWallet.map { tokenWithBalanceToTokenToSelect(it) }.toImmutableList(),
unavailableTokens = value.loadedTokens.map { tokenWithBalanceToTokenToSelect(it) }.toImmutableList(),
@ -28,12 +28,14 @@ class TokensDataConverter(
)
}
private fun tokenWithBalanceToTokenToSelect(tokenWithBalance: TokenWithBalance): TokenToSelectState.TokenToSelect {
private fun tokenWithBalanceToTokenToSelect(
tokenWithBalance: TokenWithBalanceExpress,
): TokenToSelectState.TokenToSelect {
return TokenToSelectState.TokenToSelect(
id = tokenWithBalance.token.id,
id = tokenWithBalance.token.id.value,
name = tokenWithBalance.token.name,
symbol = tokenWithBalance.token.symbol,
isNative = tokenWithBalance.token is Currency.NativeToken,
isNative = tokenWithBalance.token is CryptoCurrency.Coin,
// todo replace converting
tokenIcon = TokenIconState.CoinIcon(
url = "",

View file

@ -37,7 +37,7 @@ data class SwapCardData(
val amountEquivalent: String?,
val coinId: String?,
val amountTextFieldValue: TextFieldValue?,
val tokenIconUrl: String,
val tokenIconUrl: String?,
val tokenCurrency: String,
val balance: String,
val isBalanceHidden: Boolean,

View file

@ -6,6 +6,7 @@ import com.tangem.common.Provider
import com.tangem.core.ui.components.states.Item
import com.tangem.core.ui.components.states.SelectableItemsState
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.feature.swap.converters.TokensDataConverter
import com.tangem.feature.swap.domain.models.DataError
import com.tangem.feature.swap.domain.models.domain.Currency
@ -73,21 +74,21 @@ internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider:
fun createQuotesLoadingState(
uiStateHolder: SwapStateHolder,
fromToken: Currency,
toToken: Currency,
fromToken: CryptoCurrency,
toToken: CryptoCurrency,
mainTokenId: String,
): SwapStateHolder {
val canSelectSendToken = mainTokenId != fromToken.id
val canSelectReceiveToken = mainTokenId != toToken.id
val canSelectSendToken = mainTokenId != fromToken.id.value // TODO look at id matching
val canSelectReceiveToken = mainTokenId != toToken.id.value // TODO look at id matching
return uiStateHolder.copy(
sendCardData = SwapCardData(
type = requireNotNull(uiStateHolder.sendCardData.type as? TransactionCardType.SendCard),
amountTextFieldValue = uiStateHolder.sendCardData.amountTextFieldValue,
amountEquivalent = null,
tokenIconUrl = fromToken.logoUrl,
tokenIconUrl = fromToken.iconUrl,
tokenCurrency = fromToken.symbol,
coinId = fromToken.id,
isNotNativeToken = fromToken.isNonNative(),
coinId = fromToken.id.value,
isNotNativeToken = fromToken is CryptoCurrency.Token,
canSelectAnotherToken = canSelectSendToken,
balance = if (!canSelectSendToken) uiStateHolder.sendCardData.balance else "",
isBalanceHidden = isBalanceHiddenProvider(),
@ -96,10 +97,10 @@ internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider:
type = TransactionCardType.ReceiveCard(),
amountTextFieldValue = null,
amountEquivalent = null,
tokenIconUrl = toToken.logoUrl,
tokenIconUrl = toToken.iconUrl,
tokenCurrency = toToken.symbol,
coinId = toToken.id,
isNotNativeToken = toToken.isNonNative(),
coinId = toToken.id.value,
isNotNativeToken = toToken is CryptoCurrency.Token,
canSelectAnotherToken = canSelectReceiveToken,
balance = if (!canSelectReceiveToken) uiStateHolder.receiveCardData.balance else "",
isBalanceHidden = isBalanceHiddenProvider(),
@ -123,7 +124,7 @@ internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider:
fun createQuotesLoadedState(
uiStateHolder: SwapStateHolder,
quoteModel: SwapState.QuotesLoadedState,
fromToken: Currency,
fromToken: CryptoCurrency,
onFeeSetup: (TxFee) -> Unit,
): SwapStateHolder {
val warnings = mutableListOf<SwapWarning>()
@ -238,7 +239,7 @@ internal class StateBuilder(val actions: UiActions, val isBalanceHiddenProvider:
fun addTokensToState(
uiState: SwapStateHolder,
dataState: FoundTokensState,
dataState: FoundTokensStateExpress,
networkInfo: NetworkInfo,
): SwapStateHolder {
return uiState.copy(

View file

@ -151,7 +151,7 @@ private fun MainInfo(state: SwapStateHolder) {
},
textFieldValue = state.sendCardData.amountTextFieldValue,
amountEquivalent = state.sendCardData.amountEquivalent,
tokenIconUrl = state.sendCardData.tokenIconUrl,
tokenIconUrl = state.sendCardData.tokenIconUrl ?: "",
tokenCurrency = state.sendCardData.tokenCurrency,
priceImpact = priceImpactWarning,
networkIconRes = if (state.sendCardData.isNotNativeToken) networkIconRes else null,
@ -169,7 +169,7 @@ private fun MainInfo(state: SwapStateHolder) {
balance = if (state.receiveCardData.isBalanceHidden) STARS else state.receiveCardData.balance,
textFieldValue = state.receiveCardData.amountTextFieldValue,
amountEquivalent = state.receiveCardData.amountEquivalent,
tokenIconUrl = state.receiveCardData.tokenIconUrl,
tokenIconUrl = state.receiveCardData.tokenIconUrl ?: "",
tokenCurrency = state.receiveCardData.tokenCurrency,
priceImpact = priceImpactWarning,
networkIconRes = if (state.receiveCardData.isNotNativeToken) networkIconRes else null,

View file

@ -8,6 +8,7 @@ import com.tangem.common.Provider
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.ui.utils.InputNumberFormatter
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.feature.swap.analytics.SwapEvents
import com.tangem.feature.swap.domain.BlockchainInteractor
@ -51,10 +52,14 @@ internal class SwapViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver {
// try to get rid of this and use only CryptoCurrency
private val currency = Json.decodeFromString<Currency>(
savedStateHandle[SwapFragment.CURRENCY_BUNDLE_KEY]
?: error("no expected parameter Currency found"),
)
private var cryptoCurrency: CryptoCurrency by Delegates.notNull()
private val derivationPath = savedStateHandle.get<String>(SwapFragment.DERIVATION_PATH)
private val network = savedStateHandle.get<Network>(SwapFragment.NETWORK)
@ -132,8 +137,21 @@ internal class SwapViewModel @Inject constructor(
// new flow
viewModelScope.launch(dispatchers.main) {
runCatching(dispatchers.io) {
val pairs = swapInteractor.getPairs(currency)
// TODO
swapInteractor.getTokensDataState(currency)
}.onSuccess { state ->
dataState = dataState.copy(
fromCryptoCurrency = state.preselectTokens.fromToken,
toCryptoCurrency = state.preselectTokens.toToken,
)
cryptoCurrency = state.initialCryptoCurrency
// updateTokensState(dataState = state.foundTokensState)
// startLoadingQuotes(
// fromToken = state.preselectTokens.fromToken,
// toToken = state.preselectTokens.toToken,
// amount = lastAmount.value,
// )
}.onFailure {
Timber.tag(loggingTag).e(it)
}
}
@ -143,24 +161,24 @@ internal class SwapViewModel @Inject constructor(
swapInteractor.initTokensToSwap(currency)
}
.onSuccess { state ->
dataState = dataState.copy(
fromCurrency = state.preselectTokens.fromToken,
toCurrency = state.preselectTokens.toToken,
)
updateTokensState(dataState = state.foundTokensState)
startLoadingQuotes(
fromToken = state.preselectTokens.fromToken,
toToken = state.preselectTokens.toToken,
amount = lastAmount.value,
)
// dataState = dataState.copy(
// fromCurrency = state.preselectTokens.fromToken,
// toCurrency = state.preselectTokens.toToken,
// )
// updateTokensState(dataState = state.foundTokensState)
// startLoadingQuotes(
// fromToken = state.preselectTokens.fromToken,
// toToken = state.preselectTokens.toToken,
// amount = lastAmount.value,
// )
}
.onFailure {
Timber.e(it)
Timber.tag(loggingTag).e(it)
}
}
}
private fun updateTokensState(dataState: FoundTokensState) {
private fun updateTokensState(dataState: FoundTokensStateExpress) {
uiState = stateBuilder.addTokensToState(
uiState = uiState,
dataState = dataState,
@ -168,9 +186,9 @@ internal class SwapViewModel @Inject constructor(
)
}
private fun startLoadingQuotes(fromToken: Currency, toToken: Currency, amount: String) {
private fun startLoadingQuotes(fromToken: CryptoCurrency, toToken: CryptoCurrency, amount: String) {
singleTaskScheduler.cancelTask()
uiState = stateBuilder.createQuotesLoadingState(uiState, fromToken, toToken, currency.id)
uiState = stateBuilder.createQuotesLoadingState(uiState, fromToken, toToken, cryptoCurrency.id.value)
singleTaskScheduler.scheduleTask(
viewModelScope,
loadQuotesTask(
@ -182,15 +200,19 @@ internal class SwapViewModel @Inject constructor(
}
private fun startLoadingQuotesFromLastState() {
val fromCurrency = dataState.fromCurrency
val toCurrency = dataState.toCurrency
val fromCurrency = dataState.fromCryptoCurrency
val toCurrency = dataState.toCryptoCurrency
val amount = dataState.amount
if (fromCurrency != null && toCurrency != null && amount != null) {
startLoadingQuotes(fromCurrency, toCurrency, amount)
}
}
private fun loadQuotesTask(fromToken: Currency, toToken: Currency, amount: String): PeriodicTask<SwapState> {
private fun loadQuotesTask(
fromToken: CryptoCurrency,
toToken: CryptoCurrency,
amount: String,
): PeriodicTask<SwapState> {
return PeriodicTask(
UPDATE_DELAY,
task = {
@ -264,8 +286,8 @@ internal class SwapViewModel @Inject constructor(
swapInteractor.onSwap(
networkId = dataState.networkId,
swapStateData = requireNotNull(dataState.swapDataModel),
currencyToSend = requireNotNull(dataState.fromCurrency),
currencyToGet = requireNotNull(dataState.toCurrency),
currencyToSend = requireNotNull(dataState.fromCryptoCurrency),
currencyToGet = requireNotNull(dataState.toCryptoCurrency),
amountToSwap = requireNotNull(dataState.amount),
fee = requireNotNull(dataState.selectedFee),
)
@ -314,9 +336,9 @@ internal class SwapViewModel @Inject constructor(
approveData = requireNotNull(dataState.approveDataModel) {
"dataState.approveDataModel might not be null"
},
forTokenContractAddress = (dataState.fromCurrency as? Currency.NonNativeToken)?.contractAddress
forTokenContractAddress = (dataState.fromCryptoCurrency as? CryptoCurrency.Token)?.contractAddress
?: "",
fromToken = requireNotNull(dataState.fromCurrency) {
fromToken = requireNotNull(dataState.fromCryptoCurrency) {
"dataState.fromCurrency might not be null"
},
approveType = requireNotNull(uiState.permissionState as? SwapPermissionState.ReadyForRequest) {
@ -367,18 +389,18 @@ internal class SwapViewModel @Inject constructor(
)
if (foundToken != null) {
val fromToken: Currency
val toToken: Currency
val fromToken: CryptoCurrency
val toToken: CryptoCurrency
if (isOrderReversed) {
fromToken = foundToken
toToken = currency
toToken = cryptoCurrency
} else {
fromToken = currency
fromToken = cryptoCurrency
toToken = foundToken
}
dataState = dataState.copy(
fromCurrency = fromToken,
toCurrency = toToken,
fromCryptoCurrency = fromToken,
toCryptoCurrency = toToken,
)
startLoadingQuotes(fromToken, toToken, lastAmount.value)
swapRouter.openScreen(SwapNavScreen.Main)
@ -386,12 +408,12 @@ internal class SwapViewModel @Inject constructor(
}
private fun onChangeCardsClicked() {
val newFromToken = dataState.toCurrency
val newToToken = dataState.fromCurrency
val newFromToken = dataState.toCryptoCurrency
val newToToken = dataState.fromCryptoCurrency
if (newFromToken != null && newToToken != null) {
dataState = dataState.copy(
fromCurrency = newFromToken,
toCurrency = newToToken,
fromCryptoCurrency = newFromToken,
toCryptoCurrency = newToToken,
)
isOrderReversed = !isOrderReversed
val decimals = blockchainInteractor.getTokenDecimals(newFromToken)
@ -405,8 +427,8 @@ internal class SwapViewModel @Inject constructor(
}
private fun onAmountChanged(value: String) {
val fromToken = dataState.fromCurrency
val toToken = dataState.toCurrency
val fromToken = dataState.fromCryptoCurrency
val toToken = dataState.toCryptoCurrency
if (fromToken != null && toToken != null) {
val decimals = blockchainInteractor.getTokenDecimals(fromToken)
val cutValue = cutAmountWithDecimals(decimals, value)
@ -420,8 +442,8 @@ internal class SwapViewModel @Inject constructor(
}
private fun onMaxAmountClicked() {
dataState.fromCurrency?.let {
val balance = swapInteractor.getTokenBalance(currency.networkId, it)
dataState.fromCryptoCurrency?.let {
val balance = swapInteractor.getTokenBalance(cryptoCurrency.network.id.value, it)
onAmountChanged(balance.formatToUIRepresentation())
}
}
@ -496,11 +518,11 @@ internal class SwapViewModel @Inject constructor(
onSelectItemFee = { feeItem ->
dataState = dataState.copy(selectedFee = feeItem.data)
val spendAmount = dataState.amount?.let { amount ->
val fromToken = dataState.fromCurrency ?: return@let null
val fromToken = dataState.fromCryptoCurrency ?: return@let null
swapInteractor.getSwapAmountForToken(amount, fromToken)
} ?: dataState.approveDataModel?.fromTokenAmount
spendAmount ?: return@UiActions
val fromToken = dataState.fromCurrency ?: return@UiActions
val fromToken = dataState.fromCryptoCurrency ?: return@UiActions
viewModelScope.launch(dispatchers.io) {
val isFeeEnough = swapInteractor.checkFeeIsEnough(
fee = feeItem.data.feeValue,
@ -517,6 +539,7 @@ internal class SwapViewModel @Inject constructor(
}
companion object {
private const val loggingTag = "SwapViewModel"
private const val INITIAL_AMOUNT = ""
private const val UPDATE_DELAY = 10000L
private const val DEBOUNCE_AMOUNT_DELAY = 1000L