Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-10 16:33:07 +04:00
parent 2f2d6755f9
commit 5f4dfefc2e
20 changed files with 190 additions and 311 deletions

View file

@ -21,4 +21,10 @@ abstract class SingleAccountListSupplier(
params = SingleAccountListProducer.Params(userWalletId = userWalletId),
)
}
suspend fun getSyncOrNull(userWalletId: UserWalletId): AccountList? {
return getSyncOrNull(
params = SingleAccountListProducer.Params(userWalletId = userWalletId),
)
}
}

View file

@ -20,4 +20,9 @@ abstract class SingleAccountStatusListSupplier(
val params = SingleAccountStatusListProducer.Params(userWalletId)
return this.invoke(params)
}
suspend fun getSyncOrNull(userWalletId: UserWalletId): AccountStatusList? {
val params = SingleAccountStatusListProducer.Params(userWalletId)
return this.getSyncOrNull(params)
}
}

View file

@ -1,106 +0,0 @@
package com.tangem.domain.tokens
import arrow.core.Either
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.error.mapper.mapToCurrencyError
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
import com.tangem.domain.tokens.operations.CurrenciesStatusesOperations
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
/**
* Use case for fetching the status of a cryptocurrency associated with a user wallet.
*
*/
class GetSingleCryptoCurrencyStatusUseCase(
private val currencyStatusOperations: BaseCurrencyStatusOperations,
private val dispatchers: CoroutineDispatcherProvider,
) {
/**
* Returns cryptocurrency status flow for Multi-Currency wallet
*
* @param userWalletId The unique identifier of the user's wallet.
* @param currencyId The unique identifier of the cryptocurrency.
* @param isSingleWalletWithTokens Indicates whether the user wallet contains only one token on card (old cards)
* @return A [Flow] emitting either a [CurrencyStatusError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation.
*/
fun invokeMultiWallet(
userWalletId: UserWalletId,
currencyId: CryptoCurrency.ID,
isSingleWalletWithTokens: Boolean,
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
return flow {
emitAll(
getCurrencyStatus(
userWalletId = userWalletId,
currencyId = currencyId,
isSingleWalletWithTokens = isSingleWalletWithTokens,
),
)
}.flowOn(dispatchers.io)
}
/**
* Returns cryptocurrency status flow for primary currency for Single-Currency wallet
*
* @param userWalletId The unique identifier of the user's wallet.
* @return A [Flow] emitting either a [CurrencyStatusError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation.
*/
fun invokeSingleWallet(userWalletId: UserWalletId): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
return flow {
emitAll(
currencyStatusOperations.getPrimaryCurrencyStatusFlow(userWalletId).map { maybeCurrency ->
maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError)
},
)
}.flowOn(dispatchers.io)
}
/**
* Returns synchronously cryptocurrency status for Multi-Currency wallet
*
* @param userWalletId The unique identifier of the user's wallet.
* @param cryptoCurrencyId The unique identifier of the cryptocurrency.
* @param isSingleWalletWithTokens Indicates whether the user wallet contains only one token on card (old cards)
* @return A [Flow] emitting either a [CurrencyStatusError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation.
*/
suspend fun invokeMultiWalletSync(
userWalletId: UserWalletId,
cryptoCurrencyId: CryptoCurrency.ID,
isSingleWalletWithTokens: Boolean = false,
): Either<CurrencyStatusError, CryptoCurrencyStatus> {
return currencyStatusOperations.getCurrencyStatusSync(userWalletId, cryptoCurrencyId, isSingleWalletWithTokens)
.mapLeft { error -> error.mapToCurrencyError() }
}
/**
* Returns synchronously cryptocurrency status for primary currency for Single-Currency wallet
*
* @param userWalletId The unique identifier of the user's wallet.
* @return A [Flow] emitting either a [CurrencyStatusError] or a [CryptoCurrencyStatus], indicating the result of the fetch operation.
*/
suspend fun invokeSingleWalletSync(userWalletId: UserWalletId): Either<CurrencyStatusError, CryptoCurrencyStatus> {
return currencyStatusOperations.getPrimaryCurrencyStatusSync(userWalletId)
.mapLeft { error -> error.mapToCurrencyError() }
}
private suspend fun getCurrencyStatus(
userWalletId: UserWalletId,
currencyId: CryptoCurrency.ID,
isSingleWalletWithTokens: Boolean,
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
val currencyFlow = currencyStatusOperations.getCurrencyStatusFlow(
userWalletId = userWalletId,
currencyId = currencyId,
isSingleWalletWithTokens = isSingleWalletWithTokens,
)
return currencyFlow.map { maybeCurrency ->
maybeCurrency.mapLeft(CurrenciesStatusesOperations.Error::mapToCurrencyError)
}
}
}

View file

@ -26,6 +26,7 @@ dependencies {
implementation(projects.libs.blockchainSdk)
implementation(projects.libs.crypto)
implementation(projects.domain.account.status)
implementation(projects.domain.models)
implementation(projects.domain.legacy)
implementation(projects.domain.walletManager)

View file

@ -1,6 +1,7 @@
package com.tangem.domain.transaction.usecase.gasless
import arrow.core.Either
import arrow.core.getOrElse
import arrow.core.raise.catch
import arrow.core.raise.either
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
@ -16,13 +17,13 @@ import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldS
import com.tangem.common.CompletionResult
import com.tangem.common.extensions.toDecompressedPublicKey
import com.tangem.common.extensions.toHexString
import com.tangem.domain.account.status.utils.CryptoCurrencyOperations.getCryptoCurrency
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.card.common.TapWorkarounds.isTangemTwins
import com.tangem.domain.card.models.TwinKey
import com.tangem.domain.card.repository.CardSdkConfigRepository
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.transaction.GaslessTransactionRepository
import com.tangem.domain.transaction.error.SendTransactionError
import com.tangem.domain.transaction.models.Eip7702Authorization
@ -33,7 +34,7 @@ import java.math.BigInteger
class CreateAndSendGaslessTransactionUseCase(
private val walletManagersFacade: WalletManagersFacade,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val singleAccountListSupplier: SingleAccountListSupplier,
private val gaslessTransactionRepository: GaslessTransactionRepository,
private val cardSdkConfigRepository: CardSdkConfigRepository,
private val getHotWalletSigner: (UserWallet.Hot) -> TransactionSigner,
@ -74,18 +75,17 @@ class CreateAndSendGaslessTransactionUseCase(
transactionData: TransactionData.Uncompiled,
fee: TransactionFeeExtended,
): GaslessContext {
val tokenForFeeStatus = getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync(
userWallet.walletId,
fee.feeTokenId,
).getOrNull() ?: error("Token for fee not found")
val currency = singleAccountListSupplier.getSyncOrNull(userWalletId = userWallet.walletId)
.getCryptoCurrency(currencyId = fee.feeTokenId, network = null)
.getOrElse { error("Token for fee not found") }
val walletManager = walletManagersFacade.getOrCreateWalletManager(
userWallet.walletId,
tokenForFeeStatus.currency.network,
) ?: error("WalletManager not found for network ${tokenForFeeStatus.currency.network.id}")
currency.network,
) ?: error("WalletManager not found for network ${currency.network.id}")
val gaslessDataProvider = walletManager as? EthereumGaslessDataProvider ?: error(
"WalletManager for network ${tokenForFeeStatus.currency.network.id} " +
"WalletManager for network ${currency.network.id} " +
"does not support gasless transactions",
)
@ -94,16 +94,16 @@ class CreateAndSendGaslessTransactionUseCase(
val gaslessTransactionData = createGaslessTransactionData(
transactionData = transactionData,
txFee = fee,
tokenFeeStatus = tokenForFeeStatus,
currency = currency,
nonce = gaslessContractNonce,
)
val chainId = gaslessTransactionRepository.getChainIdForNetwork(tokenForFeeStatus.currency.network)
val chainId = gaslessTransactionRepository.getChainIdForNetwork(currency.network)
return GaslessContext(
walletManager = walletManager,
gaslessDataProvider = gaslessDataProvider,
tokenForFeeStatus = tokenForFeeStatus,
currency = currency,
gaslessTransactionData = gaslessTransactionData,
chainId = chainId,
)
@ -189,7 +189,7 @@ class CreateAndSendGaslessTransactionUseCase(
transactionData: TransactionData.Uncompiled,
): String {
val txHash = gaslessTransactionRepository.signGaslessTransaction(
network = context.tokenForFeeStatus.currency.network,
network = context.currency.network,
gaslessTransactionData = context.gaslessTransactionData,
signature = signedData.eip712Signature,
userAddress = transactionData.sourceAddress,
@ -244,11 +244,11 @@ class CreateAndSendGaslessTransactionUseCase(
private suspend fun createGaslessTransactionData(
transactionData: TransactionData.Uncompiled,
txFee: TransactionFeeExtended,
tokenFeeStatus: CryptoCurrencyStatus,
currency: CryptoCurrency,
nonce: BigInteger,
): GaslessTransactionData {
val transaction = buildTransaction(transactionData)
val fee = buildFee(txFee, tokenFeeStatus)
val fee = buildFee(txFee, currency)
return GaslessTransactionData(
transaction = transaction,
@ -272,11 +272,8 @@ class CreateAndSendGaslessTransactionUseCase(
)
}
private suspend fun buildFee(
txFee: TransactionFeeExtended,
tokenFeeStatus: CryptoCurrencyStatus,
): GaslessTransactionData.Fee {
val tokenForFee = tokenFeeStatus.currency as? CryptoCurrency.Token
private suspend fun buildFee(txFee: TransactionFeeExtended, currency: CryptoCurrency): GaslessTransactionData.Fee {
val tokenForFee = currency as? CryptoCurrency.Token
?: error("Fee currency must be a token")
val feeInTokenCurrency = txFee.transactionFee.normal as? Fee.Ethereum.TokenCurrency
@ -320,7 +317,7 @@ class CreateAndSendGaslessTransactionUseCase(
private data class GaslessContext(
val walletManager: WalletManager,
val gaslessDataProvider: EthereumGaslessDataProvider,
val tokenForFeeStatus: CryptoCurrencyStatus,
val currency: CryptoCurrency,
val gaslessTransactionData: GaslessTransactionData,
val chainId: Int,
)