Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-24 17:48:38 +03:00
parent 85803abc7f
commit 095c755362
194 changed files with 830 additions and 473 deletions

View file

@ -0,0 +1,744 @@
package com.tangem.data.walletmanager
import arrow.core.Either
import arrow.core.raise.either
import arrow.core.raise.ensureNotNull
import arrow.core.right
import com.tangem.blockchain.blockchains.solana.RentProvider
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.address.Address
import com.tangem.blockchain.common.address.AddressType
import com.tangem.blockchain.common.address.EstimationFeeAddressFactory
import com.tangem.blockchain.common.pagination.Page
import com.tangem.blockchain.common.smartcontract.SmartContractCallDataProviderFactory
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchain.common.trustlines.AssetRequirementsManager
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.blockchain.nft.models.NFTAsset
import com.tangem.blockchain.nft.models.NFTCollection
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryRequest
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.data.walletmanager.utils.*
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.datasource.local.walletmanager.WalletManagersStore
import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.TxInfo
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.domain.txhistory.models.PaginationWrapper
import com.tangem.domain.txhistory.models.TxHistoryState
import com.tangem.domain.walletmanager.WalletManagersFacade
import com.tangem.domain.walletmanager.model.RentData
import com.tangem.domain.walletmanager.model.SmartContractMethod
import com.tangem.domain.walletmanager.model.TokenInfo
import com.tangem.domain.walletmanager.utils.SdkPageConverter
import com.tangem.domain.wallets.extension.hasDerivation
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.math.BigDecimal
import java.util.EnumSet
import javax.inject.Inject
@Suppress("LargeClass", "TooManyFunctions")
internal class DefaultWalletManagersFacade @Inject constructor(
private val walletManagersStore: WalletManagersStore,
private val userWalletsStore: UserWalletsStore,
private val assetLoader: AssetLoader,
private val dispatchers: CoroutineDispatcherProvider,
blockchainSDKFactory: BlockchainSDKFactory,
) : WalletManagersFacade {
private val demoConfig by lazy { DemoConfig() }
private val resultFactory by lazy { UpdateWalletManagerResultFactory() }
private val walletManagerFactory by lazy { WalletManagerFactory(blockchainSDKFactory) }
private val sdkTokenConverter by lazy { SdkTokenConverter() }
private val txHistoryStateConverter by lazy { SdkTransactionHistoryStateConverter() }
private val sdkPageConverter by lazy { SdkPageConverter() }
private val cryptoCurrencyTypeConverter by lazy { CryptoCurrencyTypeConverter() }
private val requirementsConditionConverter by lazy { SdkRequirementsConditionConverter() }
private val estimationFeeAddressFactory by lazy { EstimationFeeAddressFactory() }
private val initMutex = Mutex()
override suspend fun update(
userWalletId: UserWalletId,
network: Network,
extraTokens: Set<CryptoCurrency.Token>,
): UpdateWalletManagerResult {
val userWallet = getUserWallet(userWalletId)
val blockchain = network.toBlockchain()
val derivationPath = network.derivationPath.value
return getAndUpdateWalletManager(userWallet, blockchain, derivationPath, extraTokens)
}
override suspend fun remove(userWalletId: UserWalletId, networks: Set<Network>) {
if (networks.isEmpty()) return
val blockchainsToDerivationPaths = networks.map {
it.toBlockchain() to it.derivationPath.value
}
withContext(dispatchers.io) {
walletManagersStore.remove(userWalletId) { walletManager ->
val wallet = walletManager.wallet
val blockchainToDerivationPath = wallet.blockchain to wallet.publicKey.derivationPath?.rawPath
blockchainToDerivationPath in blockchainsToDerivationPaths
}
}
}
override suspend fun removeTokens(userWalletId: UserWalletId, tokens: Set<CryptoCurrency.Token>) {
if (tokens.isEmpty()) return
tokens
.groupBy(CryptoCurrency.Token::network)
.forEach { (network, networkTokens) ->
removeTokens(
userWalletId = userWalletId,
network = network,
networkTokens = sdkTokenConverter.convertList(networkTokens),
)
}
}
override suspend fun removeTokensByTokenInfo(userWalletId: UserWalletId, tokenInfos: Set<TokenInfo>) {
if (tokenInfos.isEmpty()) return
tokenInfos
.groupBy { it.network }
.forEach { (network, tokenInfoList) ->
removeTokens(
userWalletId = userWalletId,
network = network,
networkTokens = tokenInfoList.map {
Token(
name = it.name,
symbol = it.symbol,
contractAddress = it.contractAddress,
decimals = it.decimals,
id = it.id,
)
},
)
}
}
private suspend fun removeTokens(userWalletId: UserWalletId, network: Network, networkTokens: List<Token>) {
withContext(dispatchers.io) {
val walletManager = walletManagersStore.getSyncOrNull(
userWalletId = userWalletId,
blockchain = network.toBlockchain(),
derivationPath = network.derivationPath.value,
) ?: return@withContext
networkTokens.forEach { token ->
walletManager.removeToken(token)
}
walletManagersStore.store(userWalletId, walletManager)
}
}
override suspend fun updatePendingTransactions(
userWalletId: UserWalletId,
network: Network,
): UpdateWalletManagerResult {
val userWallet = getUserWallet(userWalletId)
val blockchain = network.toBlockchain()
val derivationPath = network.derivationPath.value
if (derivationPath != null &&
!userWallet.hasDerivation(blockchain, derivationPath)
) {
Timber.w("Derivation missed for: $blockchain")
return UpdateWalletManagerResult.MissedDerivation
}
val walletManager = getOrCreateWalletManager(userWalletId, blockchain, derivationPath)
if (walletManager == null || blockchain == Blockchain.Unknown) {
Timber.w("Unable to get a wallet manager for blockchain: $blockchain")
return UpdateWalletManagerResult.Unreachable()
}
return getLastWalletManagerResult(walletManager)
}
override suspend fun getExploreUrl(
userWalletId: UserWalletId,
network: Network,
addressType: AddressType,
contractAddress: String?,
): String {
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
requireNotNull(walletManager) {
"Unable to get a wallet manager for blockchain: $blockchain"
}
val address = walletManager
.wallet
.addresses
.find { it.type == addressType }
?.value ?: walletManager.wallet.address
return blockchain.getExploreUrl(address, contractAddress)
}
override suspend fun getTxHistoryState(userWalletId: UserWalletId, currency: CryptoCurrency): TxHistoryState {
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = currency.network,
)
requireNotNull(walletManager) {
"Unable to get a wallet manager for blockchain: ${currency.network}"
}
return walletManager
.getTransactionHistoryState(
address = walletManager.wallet.address,
filterType = when (currency) {
is CryptoCurrency.Coin -> TransactionHistoryRequest.FilterType.Coin
is CryptoCurrency.Token -> {
val blockchainToken = Token(
name = currency.name,
symbol = currency.symbol,
contractAddress = currency.contractAddress,
decimals = currency.decimals,
id = currency.id.rawCurrencyId?.value,
)
TransactionHistoryRequest.FilterType.Contract(blockchainToken)
}
},
)
.let(txHistoryStateConverter::convert)
}
override suspend fun getTxHistoryItems(
userWalletId: UserWalletId,
currency: CryptoCurrency,
page: Page,
pageSize: Int,
): PaginationWrapper<TxInfo> {
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = currency.network,
)
requireNotNull(walletManager) {
"Unable to get a wallet manager for blockchain: ${currency.network}"
}
val itemsResult = walletManager.getTransactionsHistory(
request = TransactionHistoryRequest(
address = walletManager.wallet.address,
decimals = currency.decimals,
page = page,
pageSize = pageSize,
filterType = when (currency) {
is CryptoCurrency.Coin -> TransactionHistoryRequest.FilterType.Coin
is CryptoCurrency.Token -> {
val blockchainToken = Token(
name = currency.name,
symbol = currency.symbol,
contractAddress = currency.contractAddress,
decimals = currency.decimals,
id = currency.id.rawCurrencyId?.value,
)
TransactionHistoryRequest.FilterType.Contract(blockchainToken)
}
},
),
)
return when (itemsResult) {
is Result.Success -> PaginationWrapper(
currentPage = sdkPageConverter.convert(page),
nextPage = sdkPageConverter.convert(itemsResult.data.nextPage),
items = SdkTransactionHistoryItemConverter(smartContractMethods = readSmartContractMethods())
.convertList(itemsResult.data.items),
)
is Result.Failure -> error(itemsResult.error.message ?: itemsResult.error.customMessage)
}
}
private fun getUserWallet(userWalletId: UserWalletId) = userWalletsStore.getSyncStrict(userWalletId)
private suspend fun getAndUpdateWalletManager(
userWallet: UserWallet,
blockchain: Blockchain,
derivationPath: String?,
extraTokens: Set<CryptoCurrency.Token>,
): UpdateWalletManagerResult {
if (derivationPath != null && !userWallet.hasDerivation(blockchain, derivationPath)) {
Timber.w("Derivation missed for: $blockchain")
return UpdateWalletManagerResult.MissedDerivation
}
val walletManager = getOrCreateWalletManager(
userWalletId = userWallet.walletId,
blockchain = blockchain,
derivationPath = derivationPath,
)
if (walletManager == null || blockchain == Blockchain.Unknown) {
Timber.w("Unable to create or find a wallet manager for blockchain: $blockchain")
return UpdateWalletManagerResult.Unreachable()
}
updateWalletManagerTokensIfNeeded(walletManager, extraTokens)
return try {
if (userWallet is UserWallet.Cold && demoConfig.isDemoCardId(userWallet.scanResponse.card.cardId)) {
updateDemoWalletManager(walletManager)
} else {
updateWalletManager(walletManager)
}
} finally {
walletManagersStore.store(userWallet.walletId, walletManager)
}
}
private fun updateDemoWalletManager(walletManager: WalletManager): UpdateWalletManagerResult {
val amount = demoConfig.getBalance(walletManager.wallet.blockchain)
walletManager.wallet.setAmount(amount)
return resultFactory.getDemoResult(walletManager, amount)
}
private suspend fun updateWalletManager(walletManager: WalletManager): UpdateWalletManagerResult {
return try {
walletManager.update()
resultFactory.getResult(walletManager)
} catch (e: BlockchainSdkError.AccountNotFound) {
resultFactory.getNoAccountResult(
walletManager = walletManager,
customMessage = e.customMessage,
amountToCreateAccount = e.amountToCreateAccount,
)
} catch (e: Throwable) {
Timber.w(e, "Unable to update a wallet manager for: ${walletManager.wallet.blockchain}")
resultFactory.getUnreachableResult(walletManager)
}
}
private fun getLastWalletManagerResult(walletManager: WalletManager): UpdateWalletManagerResult {
return try {
resultFactory.getResult(walletManager)
} catch (e: BlockchainSdkError.AccountNotFound) {
resultFactory.getNoAccountResult(
walletManager = walletManager,
customMessage = e.customMessage,
amountToCreateAccount = e.amountToCreateAccount,
)
} catch (e: Throwable) {
Timber.w(e, "Unable to update a wallet manager for: ${walletManager.wallet.blockchain}")
resultFactory.getUnreachableResult(walletManager)
}
}
@Deprecated("Will be removed in future")
override suspend fun getOrCreateWalletManager(
userWalletId: UserWalletId,
blockchain: Blockchain,
derivationPath: String?,
): WalletManager? {
initMutex.withLock {
val userWallet = getUserWallet(userWalletId)
var walletManager = walletManagersStore.getSyncOrNull(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = derivationPath,
)
val path = derivationPath?.let { DerivationPath(rawPath = it) }
if (walletManager == null) {
when (userWallet) {
is UserWallet.Hot -> {
walletManager = walletManagerFactory.createWalletManagerForHot(
hotWallet = userWallet,
blockchain = blockchain,
derivationPath = path,
)
}
is UserWallet.Cold -> {
walletManager = walletManagerFactory.createWalletManager(
scanResponse = userWallet.scanResponse,
blockchain = blockchain,
derivationPath = path,
)
}
}
walletManager ?: return null
walletManagersStore.store(userWalletId, walletManager)
}
return walletManager
}
}
@Deprecated("Will be removed in future")
override suspend fun getOrCreateWalletManager(userWalletId: UserWalletId, network: Network): WalletManager? {
val blockchain = network.toBlockchain()
return getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
)
}
@Deprecated("Will be removed in future")
override suspend fun getStoredWalletManagers(userWalletId: UserWalletId): List<WalletManager> {
return walletManagersStore.getAllSync(userWalletId)
}
override suspend fun getDefaultAddress(userWalletId: UserWalletId, network: Network): String? {
return getAddresses(userWalletId, network)
.firstOrNull { it.type == AddressType.Default }
?.value
}
override suspend fun getAddresses(userWalletId: UserWalletId, network: Network): Set<Address> {
val manager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
return manager?.wallet?.addresses.orEmpty()
}
override suspend fun getRentInfo(userWalletId: UserWalletId, network: Network): RentData? {
val manager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
if (manager !is RentProvider) return null
return when (val result = manager.minimalBalanceForRentExemption()) {
is Result.Success -> {
RentData(manager.rentAmount(), result.data)
}
is Result.Failure -> null
}
}
@Deprecated("Will be removed in future")
override fun getAll(userWalletId: UserWalletId): Flow<List<WalletManager>> {
return walletManagersStore.getAll(userWalletId)
}
override suspend fun validateSignatureCount(
userWalletId: UserWalletId,
network: Network,
signedHashes: Int,
): Either<Throwable, Unit> {
return either {
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
val validator = ensureNotNull(walletManager as? SignatureCountValidator) {
raise(IllegalStateException("Wallet manager is not a SignatureCountValidator"))
}
when (val result = validator.validateSignatureCount(signedHashes)) {
is SimpleResult.Failure -> raise(result.error)
is SimpleResult.Success -> Unit.right()
}
}
}
@Deprecated("Will be removed in future")
override suspend fun getFee(
amount: Amount,
destination: String,
userWalletId: UserWalletId,
network: Network,
): Result<TransactionFee>? = withContext(dispatchers.io) {
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
(walletManager as? TransactionSender)?.getFee(
amount = amount,
destination = destination,
)
}
override suspend fun estimateFee(
amount: Amount,
userWalletId: UserWalletId,
network: Network,
): Result<TransactionFee>? = withContext(dispatchers.io) {
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
)
val destination = estimationFeeAddressFactory.makeAddress(blockchain)
val callData = if (amount.type is AmountType.Token) {
SmartContractCallDataProviderFactory.getTokenTransferCallData(
destinationAddress = destination,
amount = amount,
blockchain = blockchain,
)
} else {
null
}
(walletManager as? TransactionSender)?.estimateFee(
amount = amount,
destination = destination,
callData = callData,
)
}
@Deprecated("Will be removed in future")
override suspend fun validateTransaction(
amount: Amount,
fee: Amount?,
userWalletId: UserWalletId,
network: Network,
): EnumSet<TransactionError>? {
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
return walletManager?.validateTransaction(amount, fee)
}
@Deprecated("Will be removed in future")
override suspend fun createTransaction(
amount: Amount,
fee: Fee,
memo: String?,
destination: String,
userWalletId: UserWalletId,
network: Network,
): TransactionData? {
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
return walletManager?.createTransaction(amount, fee, destination)
}
override suspend fun getRecentTransactions(userWalletId: UserWalletId, currency: CryptoCurrency): List<TxInfo> {
val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network)
if (walletManager == null) {
Timber.e("Unable to get a wallet manager for blockchain: ${currency.network.id}")
return emptyList()
}
val transactionDataConverter = TransactionDataToTxHistoryItemConverter(
walletAddresses = SdkAddressToAddressConverter.convertList(walletManager.wallet.addresses).toSet(),
feePaidCurrency = walletManager.wallet.blockchain.feePaidCurrency(),
)
return walletManager.wallet.recentTransactions
.filter { transaction ->
when (currency) {
is CryptoCurrency.Coin -> transaction.amount.type is AmountType.Coin
is CryptoCurrency.Token -> transaction.contractAddress == currency.contractAddress
}
}
.mapNotNull(transactionDataConverter::convert)
}
override suspend fun tokenBalance(
userWalletId: UserWalletId,
network: Network,
name: String,
symbol: String,
contractAddress: String,
decimals: Int,
id: String?,
): BigDecimal {
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
)
requireNotNull(walletManager) { "Unable to get a wallet manager for blockchain: $blockchain" }
return walletManager.wallet.fundsAvailable(
AmountType.Token(
token = Token(
name = name,
symbol = symbol,
contractAddress = contractAddress,
decimals = decimals,
id = id,
),
),
)
}
override suspend fun getAssetRequirements(
userWalletId: UserWalletId,
currency: CryptoCurrency,
): AssetRequirementsCondition? {
return withContext(dispatchers.io) {
val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network)
val currencyType = cryptoCurrencyTypeConverter.convert(currency)
if (walletManager !is AssetRequirementsManager) {
return@withContext null
}
val condition = walletManager.requirementsCondition(currencyType) ?: return@withContext null
requirementsConditionConverter.convert(condition)
}
}
override suspend fun fulfillRequirements(
userWalletId: UserWalletId,
currency: CryptoCurrency,
signer: TransactionSigner,
): SimpleResult {
return withContext(dispatchers.io) {
val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network)
val currencyType = cryptoCurrencyTypeConverter.convert(currency)
if (walletManager !is AssetRequirementsManager) {
return@withContext SimpleResult.Failure(
BlockchainSdkError.CustomError("WalletManager is not implemented AssetRequirementsManager"),
)
}
walletManager.fulfillRequirements(currencyType, signer)
}
}
override suspend fun discardRequirements(userWalletId: UserWalletId, currency: CryptoCurrency): SimpleResult {
return withContext(dispatchers.io) {
val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network)
val currencyType = cryptoCurrencyTypeConverter.convert(currency)
if (walletManager !is AssetRequirementsManager) {
return@withContext SimpleResult.Failure(
BlockchainSdkError.CustomError("WalletManager is not implemented AssetRequirementsManager"),
)
}
walletManager.discardRequirements(currencyType)
}
}
override suspend fun checkUtxoConsolidationAvailability(userWalletId: UserWalletId, network: Network): Boolean {
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
) ?: return false
return (walletManager as? UtxoBlockchainManager)?.allowConsolidation == true
}
override suspend fun getNFTCollections(userWalletId: UserWalletId, network: Network): List<NFTCollection> {
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
) ?: return emptyList()
val address = walletManager.wallet.address
return walletManager.getCollections(address)
}
override suspend fun getNFTAssets(
userWalletId: UserWalletId,
network: Network,
collectionIdentifier: NFTCollection.Identifier,
): List<NFTAsset> {
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
) ?: return emptyList()
val address = walletManager.wallet.address
return walletManager.getAssets(address, collectionIdentifier)
}
override suspend fun getNFTAsset(
userWalletId: UserWalletId,
network: Network,
collectionIdentifier: NFTCollection.Identifier,
assetIdentifier: NFTAsset.Identifier,
): NFTAsset? {
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
) ?: return null
return walletManager.getAsset(collectionIdentifier, assetIdentifier)
}
override suspend fun getNFTSalePrice(
userWalletId: UserWalletId,
network: Network,
collectionIdentifier: NFTCollection.Identifier,
assetIdentifier: NFTAsset.Identifier,
): NFTAsset.SalePrice? {
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
) ?: return null
return walletManager.getSalePrice(collectionIdentifier, assetIdentifier)
}
override suspend fun getNFTExploreUrl(network: Network, assetIdentifier: NFTAsset.Identifier): String? {
val blockchain = network.toBlockchain()
return blockchain.getNFTExploreUrl(assetIdentifier)
}
override suspend fun isAccountInitialized(userWalletId: UserWalletId, network: Network): Boolean {
val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = network)
val initializableAccountWalletManger = walletManager as? InitializableAccount ?: return true
return initializableAccountWalletManger.accountInitializationState == InitializableAccount.State.INITIALIZED
}
private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set<CryptoCurrency.Token>) {
if (tokens.isEmpty()) return
val tokensToAdd = sdkTokenConverter
.convertList(tokens)
.filter { it !in walletManager.cardTokens }
walletManager.addTokens(tokensToAdd)
}
private suspend fun readSmartContractMethods(): Map<String, SmartContractMethod> {
return assetLoader.loadMap<SmartContractMethod>(fileName = "contract_methods")
}
}

View file

@ -0,0 +1,196 @@
package com.tangem.data.walletmanager
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.Token
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionStatus
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchain.common.address.Address
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult
import com.tangem.blockchainsdk.utils.amountToCreateAccount
import com.tangem.data.walletmanager.utils.SdkAddressToAddressConverter
import com.tangem.data.walletmanager.utils.TransactionDataToTxHistoryItemConverter
import com.tangem.domain.models.currency.CryptoCurrency
import timber.log.Timber
import java.math.BigDecimal
/** Factory for creating [com.tangem.blockchainsdk.models.UpdateWalletManagerResult] */
internal class UpdateWalletManagerResultFactory {
/** Get [com.tangem.blockchainsdk.models.UpdateWalletManagerResult.Verified] result for [walletManager] */
fun getResult(walletManager: WalletManager): UpdateWalletManagerResult.Verified {
val wallet = walletManager.wallet
val addresses = getAvailableAddresses(wallet.addresses)
val feePaidCurrency = wallet.blockchain.feePaidCurrency()
val txHistoryItemConverter = TransactionDataToTxHistoryItemConverter(addresses, feePaidCurrency)
return UpdateWalletManagerResult.Verified(
selectedAddress = wallet.address,
addresses = addresses,
currenciesAmounts = getTokensAmounts(wallet.amounts.values.toSet()),
currentTransactions = getCurrentTransactions(txHistoryItemConverter, wallet.recentTransactions.toSet()),
)
}
/**
* Get demo [UpdateWalletManagerResult.Verified] result
*
* @param walletManager wallet manager
* @param demoAmount amount that will be used for demo result
*/
fun getDemoResult(walletManager: WalletManager, demoAmount: Amount): UpdateWalletManagerResult.Verified {
val wallet = walletManager.wallet
val addresses = getAvailableAddresses(wallet.addresses)
val feePaidCurrency = wallet.blockchain.feePaidCurrency()
val txHistoryItemConverter = TransactionDataToTxHistoryItemConverter(addresses, feePaidCurrency)
return UpdateWalletManagerResult.Verified(
selectedAddress = wallet.address,
addresses = addresses,
currenciesAmounts = getDemoTokensAmounts(demoAmount, walletManager.cardTokens),
currentTransactions = getCurrentTransactions(txHistoryItemConverter, wallet.recentTransactions.toSet()),
)
}
/**
* Get [UpdateWalletManagerResult.NoAccount] result.
* If unable to get required amount for creating account, [UpdateWalletManagerResult.Unreachable] result will be returned.
*
* @param walletManager wallet manager
* @param customMessage custom error message
* @param amountToCreateAccount amount to create account
*/
fun getNoAccountResult(
walletManager: WalletManager,
customMessage: String,
amountToCreateAccount: BigDecimal?,
): UpdateWalletManagerResult {
val wallet = walletManager.wallet
val blockchain = wallet.blockchain
val firstWalletToken = wallet.getTokens().firstOrNull()
val amount = amountToCreateAccount ?: blockchain.amountToCreateAccount(walletManager, firstWalletToken)
return if (amount == null) {
Timber.w("Unable to get required amount to create account for: $blockchain")
UpdateWalletManagerResult.Unreachable(
selectedAddress = wallet.address,
addresses = getAvailableAddresses(wallet.addresses),
)
} else {
UpdateWalletManagerResult.NoAccount(
selectedAddress = wallet.address,
addresses = getAvailableAddresses(wallet.addresses),
amountToCreateAccount = amount,
errorMessage = customMessage,
)
}
}
/** Get [UpdateWalletManagerResult.Unreachable] result for [walletManager] */
fun getUnreachableResult(walletManager: WalletManager): UpdateWalletManagerResult.Unreachable {
val wallet = walletManager.wallet
return UpdateWalletManagerResult.Unreachable(
selectedAddress = wallet.address,
addresses = getAvailableAddresses(wallet.addresses),
)
}
private fun getAvailableAddresses(addresses: Set<Address>): Set<UpdateWalletManagerResult.Address> {
return SdkAddressToAddressConverter.convertList(addresses).toSet()
}
private fun getTokensAmounts(amounts: Set<Amount>): Set<UpdateWalletManagerResult.CryptoCurrencyAmount> {
return amounts.mapNotNullTo(hashSetOf(), ::createCurrencyAmount)
}
private fun createCurrencyAmount(amount: Amount): UpdateWalletManagerResult.CryptoCurrencyAmount? {
return when (val type = amount.type) {
is AmountType.Token -> {
val value = getCurrencyAmountValue(amount) ?: return null
UpdateWalletManagerResult.CryptoCurrencyAmount.Token(
currencyRawId = type.token.id?.let(CryptoCurrency::RawID),
contractAddress = type.token.contractAddress,
value = value,
)
}
is AmountType.Coin -> {
val value = getCurrencyAmountValue(amount) ?: return null
UpdateWalletManagerResult.CryptoCurrencyAmount.Coin(value = value)
}
is AmountType.FeeResource,
is AmountType.Reserve,
-> null
}
}
private fun getCurrencyAmountValue(amount: Amount): BigDecimal? {
val value = amount.value
if (value == null) {
Timber.w("Currency amount must not be null: ${amount.currencySymbol}")
}
return value
}
private fun getDemoTokensAmounts(
demoAmount: Amount,
tokens: Set<Token>,
): Set<UpdateWalletManagerResult.CryptoCurrencyAmount> {
val amountValue = demoAmount.value ?: BigDecimal.ZERO
val demoAmounts = hashSetOf<UpdateWalletManagerResult.CryptoCurrencyAmount>(
UpdateWalletManagerResult.CryptoCurrencyAmount.Coin(amountValue),
)
return tokens.mapTo(demoAmounts) { token ->
UpdateWalletManagerResult.CryptoCurrencyAmount.Token(
currencyRawId = token.id?.let(CryptoCurrency::RawID),
contractAddress = token.contractAddress,
value = amountValue,
)
}
}
private fun getCurrentTransactions(
txHistoryItemConverter: TransactionDataToTxHistoryItemConverter,
recentTransactions: Set<TransactionData.Uncompiled>,
): Set<UpdateWalletManagerResult.CryptoCurrencyTransaction> {
val unconfirmedTransactions = recentTransactions.filter { it.status == TransactionStatus.Unconfirmed }
return unconfirmedTransactions.mapNotNullTo(hashSetOf()) {
createCurrencyTransaction(
txHistoryItemConverter = txHistoryItemConverter,
data = it,
)
}
}
private fun createCurrencyTransaction(
txHistoryItemConverter: TransactionDataToTxHistoryItemConverter,
data: TransactionData.Uncompiled,
): UpdateWalletManagerResult.CryptoCurrencyTransaction? {
return when (val type = data.amount.type) {
is AmountType.Coin -> {
val txHistoryItem = txHistoryItemConverter.convert(data) ?: return null
UpdateWalletManagerResult.CryptoCurrencyTransaction.Coin(txInfo = txHistoryItem)
}
is AmountType.Token -> {
val txHistoryItem = txHistoryItemConverter.convert(data) ?: return null
UpdateWalletManagerResult.CryptoCurrencyTransaction.Token(
tokenId = type.token.id,
contractAddress = type.token.contractAddress,
txInfo = txHistoryItem,
)
}
is AmountType.FeeResource,
is AmountType.Reserve,
-> null
}
}
}

View file

@ -0,0 +1,87 @@
package com.tangem.data.walletmanager
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationParams
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.data.walletmanager.extensions.makePublicKey
import com.tangem.data.walletmanager.extensions.makeWalletManagerForApp
import com.tangem.domain.card.DerivationStyleProvider
import com.tangem.domain.card.common.util.derivationStyleProvider
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWallet
import timber.log.Timber
internal class WalletManagerFactory(
private val blockchainSDKFactory: BlockchainSDKFactory,
) {
suspend fun createWalletManager(
scanResponse: ScanResponse,
blockchain: Blockchain,
derivationPath: DerivationPath?,
): WalletManager? {
val derivationParams = getDerivationParams(derivationPath, scanResponse.derivationStyleProvider)
return try {
blockchainSDKFactory.getWalletManagerFactorySync()?.makeWalletManagerForApp(
scanResponse = scanResponse,
blockchain = blockchain,
derivationParams = derivationParams,
)
} catch (e: Throwable) {
Timber.w(e, "Failed to create wallet manager for $blockchain")
null
}
}
suspend fun createWalletManagerForHot(
hotWallet: UserWallet.Hot,
blockchain: Blockchain,
derivationPath: DerivationPath?,
): WalletManager? {
val curve = blockchain.getSupportedCurves().first()
val selectedWallet = hotWallet.wallets.orEmpty().firstOrNull { it.curve == curve }
?: return null
return try {
val factory = blockchainSDKFactory.getWalletManagerFactorySync() ?: return null
if (derivationPath == null) {
factory.createLegacyWalletManager(
blockchain = blockchain,
walletPublicKey = selectedWallet.publicKey,
curve = selectedWallet.curve,
)
} else {
factory.createWalletManager(
blockchain = blockchain,
publicKey = makePublicKey(
seedKey = selectedWallet.publicKey,
blockchain = blockchain,
derivationPath = derivationPath,
derivedWalletKeys = selectedWallet.derivedKeys,
isWallet2 = true,
) ?: return null,
curve = selectedWallet.curve,
)
}
} catch (e: Throwable) {
Timber.w(e, "Failed to create wallet manager for $blockchain")
null
}
}
private fun getDerivationParams(
derivationPath: DerivationPath?,
derivationStyleProvider: DerivationStyleProvider,
): DerivationParams? {
val derivationStyle = derivationStyleProvider.getDerivationStyle() ?: return null
return if (derivationPath == null) {
DerivationParams.Default(derivationStyle)
} else {
DerivationParams.Custom(derivationPath)
}
}
}

View file

@ -0,0 +1,18 @@
package com.tangem.data.walletmanager.di
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
import com.tangem.data.walletmanager.DefaultWalletManagersFacade
import com.tangem.domain.walletmanager.WalletManagersFacade
@Module
@InstallIn(SingletonComponent::class)
internal interface WalletManagerModule {
@Binds
@Singleton
fun bindWalletManagerFacade(impl: DefaultWalletManagersFacade): WalletManagersFacade
}

View file

@ -0,0 +1,151 @@
package com.tangem.data.walletmanager.extensions
import com.tangem.blockchain.blockchains.cardano.CardanoUtils
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toMapKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.card.common.TapWorkarounds.isTestCard
import com.tangem.domain.card.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.card.configs.CardConfig
import com.tangem.domain.card.configs.Wallet2CardConfig
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
fun WalletManagerFactory.makeWalletManagerForApp(
scanResponse: ScanResponse,
blockchain: Blockchain,
derivationParams: DerivationParams?,
): WalletManager? {
val card = scanResponse.card
val cardConfig = CardConfig.createConfig(card)
if (card.isTestCard && blockchain.getTestnetVersion() == null) return null
val supportedCurves = blockchain.getSupportedCurves()
val wallets = card.wallets.filter { wallet -> supportedCurves.contains(wallet.curve) }
val wallet = selectWallet(
wallets = wallets,
cardConfig = cardConfig,
blockchain = blockchain,
) ?: return null
val environmentBlockchain =
if (card.isTestCard) blockchain.getTestnetVersion()!! else blockchain
val seedKey = wallet.extendedPublicKey
return when {
scanResponse.cardTypesResolver.isTangemTwins() && scanResponse.secondTwinPublicKey != null -> {
createTwinWalletManager(
walletPublicKey = wallet.publicKey,
pairPublicKey = scanResponse.secondTwinPublicKey!!.hexToBytes(),
blockchain = environmentBlockchain,
curve = wallet.curve,
)
}
scanResponse.card.settings.isHDWalletAllowed && seedKey != null && derivationParams != null -> {
val derivedKeys = scanResponse.derivedKeys[wallet.publicKey.toMapKey()]
val derivationPath = derivationParams.getPath(blockchain)
val publicKey = makePublicKey(
seedKey = wallet.publicKey,
blockchain = blockchain,
derivationPath = derivationPath ?: return null,
derivedWalletKeys = derivedKeys ?: return null,
isWallet2 = scanResponse.cardTypesResolver.isWallet2(),
) ?: return null
createWalletManager(
blockchain = environmentBlockchain,
publicKey = publicKey,
curve = wallet.curve,
)
}
else -> {
createLegacyWalletManager(
blockchain = environmentBlockchain,
walletPublicKey = wallet.publicKey,
curve = wallet.curve,
)
}
}
}
fun makePublicKey(
seedKey: ByteArray,
blockchain: Blockchain,
derivationPath: DerivationPath,
derivedWalletKeys: Map<DerivationPath, ExtendedPublicKey>,
isWallet2: Boolean,
): Wallet.PublicKey? {
val derivedKey = derivedWalletKeys[derivationPath] ?: return null
val derivationKey = Wallet.HDKey(
path = derivationPath,
extendedPublicKey = derivedKey,
)
// we should generate second key for cardano
// because cardano address generation for wallet2 requires keys from 2 derivations
// https://developers.cardano.org/docs/get-started/cardano-serialization-lib/generating-keys/
if (blockchain == Blockchain.Cardano && isWallet2) {
val extendedDerivationPath = CardanoUtils.extendedDerivationPath(derivationPath)
val secondDerivedKey = derivedWalletKeys[extendedDerivationPath] ?: error("No derivation found")
val secondDerivationKey = Wallet.HDKey(secondDerivedKey, extendedDerivationPath)
return Wallet.PublicKey(
seedKey = seedKey,
derivationType = Wallet.PublicKey.DerivationType.Double(derivationKey, secondDerivationKey),
)
}
return Wallet.PublicKey(
seedKey = seedKey,
derivationType = Wallet.PublicKey.DerivationType.Plain(derivationKey),
)
}
private fun getDerivationParams(card: CardDTO): DerivationParams? {
return if (!card.settings.isHDWalletAllowed) {
null
} else if (card.useOldStyleDerivation) {
DerivationParams.Default(DerivationStyle.LEGACY)
} else {
DerivationParams.Default(DerivationStyle.NEW)
}
}
fun WalletManagerFactory.makePrimaryWalletManager(scanResponse: ScanResponse): WalletManager? {
val blockchain = if (scanResponse.card.isTestCard) {
scanResponse.cardTypesResolver.getBlockchain().getTestnetVersion() ?: return null
} else {
scanResponse.cardTypesResolver.getBlockchain()
}
val derivationParams = getDerivationParams(scanResponse.card)
return makeWalletManagerForApp(
scanResponse = scanResponse,
blockchain = blockchain,
derivationParams = derivationParams,
)
}
private fun selectWallet(
wallets: List<CardDTO.Wallet>,
cardConfig: CardConfig,
blockchain: Blockchain,
): CardDTO.Wallet? {
return if (cardConfig is Wallet2CardConfig) {
val primaryCurve = cardConfig.primaryCurve(blockchain)
wallets.firstOrNull { it.curve == primaryCurve }
} else {
when (wallets.size) {
0 -> null
1 -> wallets[0]
else -> wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: wallets[0]
}
}
}

View file

@ -0,0 +1,23 @@
package com.tangem.data.walletmanager.utils
import com.tangem.blockchain.common.CryptoCurrencyType
import com.tangem.blockchain.common.Token
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.utils.converter.Converter
internal class CryptoCurrencyTypeConverter : Converter<CryptoCurrency, CryptoCurrencyType> {
override fun convert(value: CryptoCurrency): CryptoCurrencyType {
return when (value) {
is CryptoCurrency.Coin -> CryptoCurrencyType.Coin
is CryptoCurrency.Token -> CryptoCurrencyType.Token(
info = Token(
name = value.name,
symbol = value.symbol,
contractAddress = value.contractAddress,
decimals = value.decimals,
id = value.id.rawCurrencyId?.value,
),
)
}
}
}

View file

@ -0,0 +1,24 @@
package com.tangem.data.walletmanager.utils
import com.tangem.blockchain.common.address.AddressType
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult.Address
import com.tangem.utils.converter.Converter
import com.tangem.blockchain.common.address.Address as SdkAddress
/**
* Convert [SdkAddress] to [Address]
*
[REDACTED_AUTHOR]
*/
internal object SdkAddressToAddressConverter : Converter<SdkAddress, Address> {
override fun convert(value: SdkAddress): Address {
return Address(
value = value.value,
type = when (value.type) {
AddressType.Default -> Address.Type.Primary
AddressType.Legacy -> Address.Type.Secondary
},
)
}
}

View file

@ -0,0 +1,31 @@
package com.tangem.data.walletmanager.utils
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.utils.converter.Converter
import com.tangem.blockchain.common.trustlines.AssetRequirementsCondition as SdkRequirementsCondition
internal class SdkRequirementsConditionConverter : Converter<SdkRequirementsCondition, AssetRequirementsCondition> {
override fun convert(value: SdkRequirementsCondition): AssetRequirementsCondition {
return when (value) {
is SdkRequirementsCondition.PaidTransaction -> AssetRequirementsCondition.PaidTransaction
is SdkRequirementsCondition.RequiredTrustline -> AssetRequirementsCondition.RequiredTrustline(
requiredAmount = requireNotNull(value.amount.value),
currencySymbol = value.amount.currencySymbol,
decimals = value.amount.decimals,
)
is SdkRequirementsCondition.PaidTransactionWithFee -> AssetRequirementsCondition.PaidTransactionWithFee(
feeAmount = requireNotNull(value.feeAmount.value),
feeCurrencySymbol = value.feeAmount.currencySymbol,
decimals = value.feeAmount.decimals,
)
is SdkRequirementsCondition.IncompleteTransaction -> AssetRequirementsCondition.IncompleteTransaction(
amount = requireNotNull(value.amount.value),
currencySymbol = value.amount.currencySymbol,
currencyDecimals = value.amount.decimals,
feeAmount = requireNotNull(value.feeAmount.value),
feeCurrencySymbol = value.feeAmount.currencySymbol,
feeCurrencyDecimals = value.feeAmount.decimals,
)
}
}
}

View file

@ -0,0 +1,18 @@
package com.tangem.data.walletmanager.utils
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.utils.converter.Converter
import com.tangem.blockchain.common.Token as SdkToken
internal class SdkTokenConverter : Converter<CryptoCurrency.Token, SdkToken> {
override fun convert(value: CryptoCurrency.Token): SdkToken {
return SdkToken(
id = value.id.rawCurrencyId?.value,
name = value.name,
symbol = value.symbol,
contractAddress = value.contractAddress,
decimals = value.decimals,
)
}
}

View file

@ -0,0 +1,103 @@
package com.tangem.data.walletmanager.utils
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryItem
import com.tangem.domain.models.network.TxInfo
import com.tangem.domain.walletmanager.model.SmartContractMethod
import com.tangem.utils.converter.Converter
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryItem as SdkTransactionHistoryItem
internal class SdkTransactionHistoryItemConverter(
smartContractMethods: Map<String, SmartContractMethod>,
) : Converter<SdkTransactionHistoryItem, TxInfo> {
private val typeConverter by lazy { SdkTransactionTypeConverter(smartContractMethods) }
override fun convert(value: SdkTransactionHistoryItem): TxInfo = TxInfo(
txHash = value.txHash,
timestampInMillis = value.timestamp,
isOutgoing = value.isOutgoing,
destinationType = value.destinationType.toDomain(),
sourceType = value.sourceType.toDomain(),
interactionAddressType = value.extractInteractionAddressType(),
status = when (value.status) {
SdkTransactionHistoryItem.TransactionStatus.Confirmed -> TxInfo.TransactionStatus.Confirmed
SdkTransactionHistoryItem.TransactionStatus.Failed -> TxInfo.TransactionStatus.Failed
SdkTransactionHistoryItem.TransactionStatus.Unconfirmed -> TxInfo.TransactionStatus.Unconfirmed
},
type = typeConverter.convert(value.type),
amount = requireNotNull(value.amount.value) { "Transaction amount value must not be null" },
)
private fun SdkTransactionHistoryItem.SourceType.toDomain(): TxInfo.SourceType = when (this) {
is TransactionHistoryItem.SourceType.Single -> TxInfo.SourceType.Single(address)
is TransactionHistoryItem.SourceType.Multiple -> TxInfo.SourceType.Multiple(addresses)
}
private fun SdkTransactionHistoryItem.DestinationType.toDomain(): TxInfo.DestinationType = when (this) {
is SdkTransactionHistoryItem.DestinationType.Single -> TxInfo.DestinationType.Single(
addressType.toDomain(),
)
is SdkTransactionHistoryItem.DestinationType.Multiple -> TxInfo.DestinationType.Multiple(
addressTypes.map { it.toDomain() },
)
}
private fun SdkTransactionHistoryItem.AddressType.toDomain(): TxInfo.AddressType = when (this) {
is SdkTransactionHistoryItem.AddressType.Contract -> TxInfo.AddressType.Contract(address)
is SdkTransactionHistoryItem.AddressType.User -> TxInfo.AddressType.User(address)
is SdkTransactionHistoryItem.AddressType.Validator -> TxInfo.AddressType.Validator(address)
}
private fun SdkTransactionHistoryItem.extractInteractionAddressType(): TxInfo.InteractionAddressType? {
return when (val transactionType = type) {
SdkTransactionHistoryItem.TransactionType.Transfer -> if (isOutgoing) {
mapToInteractionAddressType(destinationType = destinationType)
} else {
mapToInteractionAddressType(sourceType = sourceType)
}
is SdkTransactionHistoryItem.TransactionType.ContractMethod,
is SdkTransactionHistoryItem.TransactionType.ContractMethodName,
-> mapToInteractionAddressType(destinationType = destinationType)
is SdkTransactionHistoryItem.TransactionType.TronStakingTransactionType.VoteWitnessContract -> {
TxInfo.InteractionAddressType.Validator(address = transactionType.validatorAddress)
}
else -> null
}
}
private fun mapToInteractionAddressType(
destinationType: SdkTransactionHistoryItem.DestinationType,
): TxInfo.InteractionAddressType {
return when (destinationType) {
is TransactionHistoryItem.DestinationType.Multiple -> TxInfo.InteractionAddressType.Multiple(
destinationType.addressTypes.map { it.address },
)
is TransactionHistoryItem.DestinationType.Single -> when (destinationType.addressType) {
is TransactionHistoryItem.AddressType.Contract -> TxInfo.InteractionAddressType.Contract(
destinationType.addressType.address,
)
is TransactionHistoryItem.AddressType.User -> TxInfo.InteractionAddressType.User(
destinationType.addressType.address,
)
is TransactionHistoryItem.AddressType.Validator -> TxInfo.InteractionAddressType.Validator(
destinationType.addressType.address,
)
}
}
}
private fun mapToInteractionAddressType(
sourceType: SdkTransactionHistoryItem.SourceType,
): TxInfo.InteractionAddressType {
return when (sourceType) {
is TransactionHistoryItem.SourceType.Multiple -> TxInfo.InteractionAddressType.Multiple(
sourceType.addresses,
)
is TransactionHistoryItem.SourceType.Single -> {
TxInfo.InteractionAddressType.User(sourceType.address)
}
}
}
}

View file

@ -0,0 +1,16 @@
package com.tangem.data.walletmanager.utils
import com.tangem.blockchain.transactionhistory.TransactionHistoryState
import com.tangem.domain.txhistory.models.TxHistoryState
import com.tangem.utils.converter.Converter
import com.tangem.blockchain.transactionhistory.TransactionHistoryState as SdkTransactionHistoryState
internal class SdkTransactionHistoryStateConverter : Converter<SdkTransactionHistoryState, TxHistoryState> {
override fun convert(value: TransactionHistoryState): TxHistoryState = when (value) {
is TransactionHistoryState.Success.Empty -> TxHistoryState.Success.Empty
is TransactionHistoryState.Success.HasTransactions -> TxHistoryState.Success.HasTransactions(value.txCount)
is TransactionHistoryState.Failed.FetchError -> TxHistoryState.Failed.FetchError(value.exception)
is TransactionHistoryState.NotImplemented -> TxHistoryState.NotImplemented
}
}

View file

@ -0,0 +1,66 @@
package com.tangem.data.walletmanager.utils
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryItem.TransactionType
import com.tangem.domain.models.network.TxInfo
import com.tangem.domain.walletmanager.model.SmartContractMethod
import com.tangem.utils.converter.Converter
internal class SdkTransactionTypeConverter(
private val smartContractMethods: Map<String, SmartContractMethod>,
) : Converter<TransactionType, TxInfo.TransactionType> {
override fun convert(value: TransactionType): TxInfo.TransactionType {
return when (value) {
is TransactionType.ContractMethod -> {
getTransactionType(methodName = smartContractMethods[value.id]?.name)
}
is TransactionType.ContractMethodName -> {
getTransactionType(methodName = value.name)
}
is TransactionType.Transfer -> {
TxInfo.TransactionType.Transfer
}
is TransactionType.TronStakingTransactionType.FreezeBalanceV2Contract -> {
TxInfo.TransactionType.Staking.Stake
}
is TransactionType.TronStakingTransactionType.UnfreezeBalanceV2Contract -> {
TxInfo.TransactionType.Staking.Unstake
}
is TransactionType.TronStakingTransactionType.VoteWitnessContract -> {
TxInfo.TransactionType.Staking.Vote(value.validatorAddress)
}
is TransactionType.TronStakingTransactionType.WithdrawBalanceContract -> {
TxInfo.TransactionType.Staking.ClaimRewards
}
is TransactionType.TronStakingTransactionType.WithdrawExpireUnfreezeContract -> {
TxInfo.TransactionType.Staking.Withdraw
}
}
}
private fun getTransactionType(methodName: String?): TxInfo.TransactionType {
return when (methodName) {
"transfer" -> TxInfo.TransactionType.Transfer
"approve" -> TxInfo.TransactionType.Approve
"swap" -> TxInfo.TransactionType.Swap
"buyVoucher",
"buyVoucherPOL",
"delegate",
-> TxInfo.TransactionType.Staking.Stake
"sellVoucher_new",
"sellVoucher_newPOL",
"undelegate",
-> TxInfo.TransactionType.Staking.Unstake
"unstakeClaimTokens_new",
"unstakeClaimTokens_newPOL",
"claim",
-> TxInfo.TransactionType.Staking.Withdraw
"withdrawRewards",
"withdrawRewardsPOL",
-> TxInfo.TransactionType.Staking.ClaimRewards
"redelegate" -> TxInfo.TransactionType.Staking.Restake
null -> TxInfo.TransactionType.UnknownOperation
else -> TxInfo.TransactionType.Operation(name = methodName.replaceFirstChar { it.titlecase() })
}
}
}

View file

@ -0,0 +1,77 @@
package com.tangem.data.walletmanager.utils
import com.tangem.blockchain.common.*
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult.Address
import com.tangem.domain.models.network.TxInfo
import com.tangem.utils.converter.Converter
import timber.log.Timber
import java.math.BigDecimal
/**
* Convert [TransactionData] to [TxInfo]
*
* @property walletAddresses wallet addresses
*
[REDACTED_AUTHOR]
*/
internal class TransactionDataToTxHistoryItemConverter(
private val walletAddresses: Set<Address>,
private val feePaidCurrency: FeePaidCurrency,
) : Converter<TransactionData.Uncompiled, TxInfo?> {
override fun convert(value: TransactionData.Uncompiled): TxInfo? {
val hash = value.hash ?: return null
val millis = value.date?.timeInMillis ?: return null
val amount = getTransactionAmountValue(value.amount, value.fee?.amount) ?: return null
val isOutgoing = value.sourceAddress in walletAddresses.map(Address::value)
return TxInfo(
txHash = hash,
timestampInMillis = millis,
isOutgoing = isOutgoing,
destinationType = TxInfo.DestinationType.Single(
addressType = TxInfo.AddressType.User(value.destinationAddress),
),
sourceType = TxInfo.SourceType.Single(value.sourceAddress),
interactionAddressType = TxInfo.InteractionAddressType.User(
address = if (isOutgoing) value.destinationAddress else value.sourceAddress,
),
status = when (value.status) {
TransactionStatus.Confirmed -> TxInfo.TransactionStatus.Confirmed
TransactionStatus.Unconfirmed -> TxInfo.TransactionStatus.Unconfirmed
},
type = TxInfo.TransactionType.Transfer,
amount = amount,
)
}
private fun getTransactionAmountValue(amount: Amount, feeAmount: Amount?): BigDecimal? {
val feeValue = feeAmount?.value ?: BigDecimal.ZERO
val value = amount.value
if (value == null) {
Timber.w("Transaction amount must not be null: ${amount.currencySymbol}")
}
return when (feePaidCurrency) {
FeePaidCurrency.SameCurrency -> value?.plus(feeValue)
FeePaidCurrency.Coin -> {
if (amount.type is AmountType.Coin) value?.plus(feeValue) else value
}
is FeePaidCurrency.Token -> {
val token = (amount.type as? AmountType.Token)?.token ?: return value
if (isSameToken(token, feePaidCurrency.token)) {
value?.plus(feeValue)
} else {
value
}
}
is FeePaidCurrency.FeeResource -> value
}
}
private fun isSameToken(amountToken: Token, feeToken: Token): Boolean {
return amountToken.contractAddress.equals(feeToken.contractAddress, ignoreCase = true) &&
amountToken.symbol.equals(feeToken.symbol, ignoreCase = true)
}
}