Updated on 2026-08-14

This commit is contained in:
Tangem 2023-12-26 10:05:09 +03:00
parent f70d183814
commit e4f1557d72
6 changed files with 164 additions and 52 deletions

View file

@ -2,8 +2,10 @@ package com.tangem.data.txhistory.repository.paging
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.tangem.blockchain.common.Blockchain
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.datasource.local.txhistory.TxHistoryItemsStore
import com.tangem.domain.common.extensions.fromNetworkId
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.txhistory.models.PaginationWrapper
import com.tangem.domain.txhistory.models.TxHistoryItem
@ -20,6 +22,8 @@ internal class TxHistoryPagingSource(
private val storeKey = TxHistoryItemsStore.Key(sourceParams.userWalletId, sourceParams.currency)
override val keyReuseSupported: Boolean get() = true
override fun getRefreshKey(state: PagingState<Int, TxHistoryItem>): Int? {
return state.anchorPosition?.let { anchorPosition ->
val anchorPage = state.closestPageToPosition(anchorPosition)
@ -70,14 +74,54 @@ internal class TxHistoryPagingSource(
}
private suspend fun fetch(pageToLoad: Int, pageSize: Int) {
val wrappedItems = walletManagersFacade.getTxHistoryItems(
var wrappedItems = walletManagersFacade.getTxHistoryItems(
userWalletId = sourceParams.userWalletId,
currency = sourceParams.currency,
page = pageToLoad,
pageSize = pageSize,
)
txHistoryItemsStore.store(storeKey, wrappedItems)
if (pageToLoad == 1) {
wrappedItems = wrappedItems.addRecentTransactions()
}
txHistoryItemsStore.store(key = storeKey, value = wrappedItems)
}
private suspend fun PaginationWrapper<TxHistoryItem>.addRecentTransactions(): PaginationWrapper<TxHistoryItem> {
val recentTxHistoryItems = Blockchain.fromNetworkId(sourceParams.currency.network.backendId)?.let {
walletManagersFacade.getRecentTransactions(
userWalletId = sourceParams.userWalletId,
blockchain = it,
derivationPath = sourceParams.currency.network.derivationPath.value,
)
.filterUnconfirmedTransaction()
.filterIfApiKnowsAboutTx(apiItems = items)
} ?: emptyList()
return if (recentTxHistoryItems.isEmpty()) {
Timber.d("Nothing to add to TxHistory")
this
} else {
Timber.d(
"Recent transactions were added to TxHistory: %s",
recentTxHistoryItems.joinToString(
prefix = "[",
postfix = "]",
transform = TxHistoryItem::txHash,
),
)
return copy(items = recentTxHistoryItems + items)
}
}
private fun List<TxHistoryItem>.filterUnconfirmedTransaction(): List<TxHistoryItem> {
return filter { it.status == TxHistoryItem.TransactionStatus.Unconfirmed }
}
private fun List<TxHistoryItem>.filterIfApiKnowsAboutTx(apiItems: List<TxHistoryItem>): List<TxHistoryItem> {
return filter { item -> apiItems.none { it.txHash == item.txHash } }
}
private fun getTxHistoryPageKey(page: Int): String {

View file

@ -38,7 +38,7 @@ import timber.log.Timber
import java.math.BigDecimal
import java.util.EnumSet
@Suppress("LargeClass")
@Suppress("LargeClass", "TooManyFunctions")
// FIXME: Move to its own module and make internal
@Deprecated("Inject the WalletManagerFacade interface using DI instead")
class DefaultWalletManagersFacade(
@ -481,6 +481,29 @@ class DefaultWalletManagersFacade(
return (walletManager as TransactionSender).send(txData, signer)
}
override suspend fun getRecentTransactions(
userWalletId: UserWalletId,
blockchain: Blockchain,
derivationPath: String?,
): List<TxHistoryItem> {
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = derivationPath,
)
if (walletManager == null) {
Timber.e("Unable to get a wallet manager for blockchain: $blockchain")
return emptyList()
}
val transactionDataConverter = TransactionDataToTxHistoryItemConverter(
walletAddresses = SdkAddressToAddressConverter.convertList(walletManager.wallet.addresses).toSet(),
)
return walletManager.wallet.recentTransactions.mapNotNull(transactionDataConverter::convert)
}
private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set<CryptoCurrency.Token>) {
if (tokens.isEmpty()) return

View file

@ -25,6 +25,7 @@ import java.util.EnumSet
/**
* A facade for managing wallets.
*/
@Suppress("TooManyFunctions")
interface WalletManagersFacade {
/**
@ -218,4 +219,11 @@ interface WalletManagersFacade {
userWalletId: UserWalletId,
network: Network,
): SimpleResult
/** Get recent transactions of [userWalletId] for [blockchain] with [derivationPath] */
suspend fun getRecentTransactions(
userWalletId: UserWalletId,
blockchain: Blockchain,
derivationPath: String?,
): List<TxHistoryItem>
}

View file

@ -0,0 +1,24 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.blockchain.common.address.AddressType
import com.tangem.domain.walletmanager.model.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,58 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionStatus
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.walletmanager.model.Address
import com.tangem.utils.converter.Converter
import timber.log.Timber
import java.math.BigDecimal
/**
* Convert [TransactionData] to [TxHistoryItem]
*
* @property walletAddresses wallet addresses
*
[REDACTED_AUTHOR]
*/
internal class TransactionDataToTxHistoryItemConverter(
private val walletAddresses: Set<Address>,
) : Converter<TransactionData, TxHistoryItem?> {
override fun convert(value: TransactionData): TxHistoryItem? {
val hash = value.hash ?: return null
val millis = value.date?.timeInMillis ?: return null
val amount = getTransactionAmountValue(value.amount) ?: return null
val isOutgoing = value.sourceAddress in walletAddresses.map(Address::value)
return TxHistoryItem(
txHash = hash,
timestampInMillis = millis,
isOutgoing = isOutgoing,
destinationType = TxHistoryItem.DestinationType.Single(
addressType = TxHistoryItem.AddressType.User(value.destinationAddress),
),
sourceType = TxHistoryItem.SourceType.Single(value.sourceAddress),
interactionAddressType = TxHistoryItem.InteractionAddressType.User(
address = if (isOutgoing) value.destinationAddress else value.sourceAddress,
),
status = when (value.status) {
TransactionStatus.Confirmed -> TxHistoryItem.TransactionStatus.Confirmed
TransactionStatus.Unconfirmed -> TxHistoryItem.TransactionStatus.Unconfirmed
},
type = TxHistoryItem.TransactionType.Transfer,
amount = amount,
)
}
private fun getTransactionAmountValue(amount: Amount): BigDecimal? {
val value = amount.value
if (value == null) {
Timber.w("Transaction amount must not be null: ${amount.currencySymbol}")
}
return value
}
}

View file

@ -1,9 +1,7 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.address.AddressType
import com.tangem.domain.common.extensions.amountToCreateAccount
import com.tangem.domain.txhistory.models.TxHistoryItem
import com.tangem.domain.walletmanager.model.Address
import com.tangem.domain.walletmanager.model.CryptoCurrencyAmount
import com.tangem.domain.walletmanager.model.CryptoCurrencyTransaction
@ -111,13 +109,14 @@ internal class UpdateWalletManagerResultFactory {
walletAddresses: Set<Address>,
data: TransactionData,
): CryptoCurrencyTransaction? {
val txHistoryItemConverter = TransactionDataToTxHistoryItemConverter(walletAddresses)
return when (val type = data.amount.type) {
is AmountType.Coin -> {
val txHistoryItem = createTxHistoryItem(walletAddresses, data) ?: return null
val txHistoryItem = txHistoryItemConverter.convert(data) ?: return null
CryptoCurrencyTransaction.Coin(txHistoryItem)
}
is AmountType.Token -> {
val txHistoryItem = createTxHistoryItem(walletAddresses, data) ?: return null
val txHistoryItem = txHistoryItemConverter.convert(data) ?: return null
CryptoCurrencyTransaction.Token(
tokenId = type.token.id,
tokenContractAddress = type.token.contractAddress,
@ -128,42 +127,8 @@ internal class UpdateWalletManagerResultFactory {
}
}
private fun createTxHistoryItem(walletAddresses: Set<Address>, data: TransactionData): TxHistoryItem? {
val hash = data.hash ?: return null
val millis = data.date?.timeInMillis ?: return null
val amount = getTransactionAmountValue(data.amount) ?: return null
val isOutgoing = data.sourceAddress in walletAddresses.map { it.value }
return TxHistoryItem(
txHash = hash,
timestampInMillis = millis,
isOutgoing = isOutgoing,
destinationType = TxHistoryItem.DestinationType.Single(
TxHistoryItem.AddressType.User(data.destinationAddress),
),
sourceType = TxHistoryItem.SourceType.Single(data.sourceAddress),
interactionAddressType = TxHistoryItem.InteractionAddressType.User(
if (isOutgoing) data.destinationAddress else data.sourceAddress,
),
status = when (data.status) {
TransactionStatus.Confirmed -> TxHistoryItem.TransactionStatus.Confirmed
TransactionStatus.Unconfirmed -> TxHistoryItem.TransactionStatus.Unconfirmed
},
type = TxHistoryItem.TransactionType.Transfer,
amount = amount,
)
}
private fun getAvailableAddresses(addresses: Set<SdkAddress>): Set<Address> {
return addresses.mapTo(hashSetOf()) { sdkAddress ->
Address(
value = sdkAddress.value,
type = when (sdkAddress.type) {
AddressType.Default -> Address.Type.Primary
AddressType.Legacy -> Address.Type.Secondary
},
)
}
return SdkAddressToAddressConverter.convertList(addresses).toSet()
}
private fun getCurrencyAmountValue(amount: Amount): BigDecimal? {
@ -175,14 +140,4 @@ internal class UpdateWalletManagerResultFactory {
return value
}
private fun getTransactionAmountValue(amount: Amount): BigDecimal? {
val value = amount.value
if (value == null) {
Timber.w("Transaction amount must not be null: ${amount.currencySymbol}")
}
return value
}
}