diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/bitcoin/BitcoinTransactionHistoryProviderExt.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/bitcoin/BitcoinTransactionHistoryProviderExt.kt deleted file mode 100644 index b8cdcb1ed..000000000 --- a/blockchain/src/main/java/com/tangem/blockchain/blockchains/bitcoin/BitcoinTransactionHistoryProviderExt.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.tangem.blockchain.blockchains.bitcoin - -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.BlockchainSdkConfig -import com.tangem.blockchain.common.txhistory.DefaultTransactionHistoryProvider -import com.tangem.blockchain.common.txhistory.TransactionHistoryProvider -import com.tangem.blockchain.network.blockbook.config.BlockBookConfig -import com.tangem.blockchain.network.blockbook.network.BlockBookApi - -internal fun Blockchain.getBitcoinTransactionHistoryProvider( - config: BlockchainSdkConfig, -): TransactionHistoryProvider { - return when (this) { - Blockchain.Bitcoin, - Blockchain.BitcoinTestnet, - -> { - if (config.nowNodeCredentials != null && config.nowNodeCredentials.apiKey.isNotBlank()) { - BitcoinTransactionHistoryProvider( - blockchain = this, - BlockBookApi( - config = BlockBookConfig.NowNodes(nowNodesCredentials = config.nowNodeCredentials), - blockchain = this, - ) - ) - } else { - DefaultTransactionHistoryProvider - } - } - else -> DefaultTransactionHistoryProvider - } -} diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumTransactionHistoryProvider.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumTransactionHistoryProvider.kt index 2f56b3374..63c4a764c 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumTransactionHistoryProvider.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumTransactionHistoryProvider.kt @@ -213,7 +213,7 @@ internal class EthereumTransactionHistoryProvider( val token = Token( name = transfer.name.orEmpty(), symbol = transfer.symbol.orEmpty(), - contractAddress = transfer.contract, + contractAddress = transfer.contract.orEmpty(), decimals = transfer.decimals, ) Amount(value = BigDecimal(transferValue).movePointLeft(transfer.decimals), token = token) diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumTransactionHistoryProviderExt.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumTransactionHistoryProviderExt.kt deleted file mode 100644 index 597c53ad7..000000000 --- a/blockchain/src/main/java/com/tangem/blockchain/blockchains/ethereum/EthereumTransactionHistoryProviderExt.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.tangem.blockchain.blockchains.ethereum - -import com.tangem.blockchain.common.Blockchain -import com.tangem.blockchain.common.BlockchainSdkConfig -import com.tangem.blockchain.common.txhistory.DefaultTransactionHistoryProvider -import com.tangem.blockchain.common.txhistory.TransactionHistoryProvider -import com.tangem.blockchain.network.blockbook.config.BlockBookConfig -import com.tangem.blockchain.network.blockbook.network.BlockBookApi - -internal fun Blockchain.getEthereumTransactionHistoryProvider( - config: BlockchainSdkConfig, -): TransactionHistoryProvider { - return when (this) { - Blockchain.Ethereum, - Blockchain.EthereumTestnet, - Blockchain.Arbitrum, - Blockchain.Avalanche, - Blockchain.BSC, - Blockchain.Polygon, - Blockchain.EthereumPow, - Blockchain.Kava, - -> { - if (config.nowNodeCredentials != null && config.nowNodeCredentials.apiKey.isNotBlank()) { - EthereumTransactionHistoryProvider( - blockchain = this, - BlockBookApi( - config = BlockBookConfig.NowNodes(nowNodesCredentials = config.nowNodeCredentials), - blockchain = this, - ) - ) - } else { - DefaultTransactionHistoryProvider - } - } - else -> DefaultTransactionHistoryProvider - } -} diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/tron/TronAddressService.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/tron/TronAddressService.kt index 717d0afde..c7087e42c 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/blockchains/tron/TronAddressService.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/blockchains/tron/TronAddressService.kt @@ -39,4 +39,4 @@ class TronAddressService : AddressService() { } } } -} \ No newline at end of file +} diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/tron/TronTransactionHistoryProvider.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/tron/TronTransactionHistoryProvider.kt new file mode 100644 index 000000000..d072eeb82 --- /dev/null +++ b/blockchain/src/main/java/com/tangem/blockchain/blockchains/tron/TronTransactionHistoryProvider.kt @@ -0,0 +1,183 @@ +package com.tangem.blockchain.blockchains.tron + +import com.tangem.Log +import com.tangem.blockchain.common.* +import com.tangem.blockchain.common.txhistory.TransactionHistoryItem +import com.tangem.blockchain.common.txhistory.TransactionHistoryItem.TransactionStatus +import com.tangem.blockchain.common.txhistory.TransactionHistoryProvider +import com.tangem.blockchain.common.txhistory.TransactionHistoryRequest +import com.tangem.blockchain.common.txhistory.TransactionHistoryState +import com.tangem.blockchain.extensions.Result +import com.tangem.blockchain.network.blockbook.network.BlockBookApi +import com.tangem.blockchain.network.blockbook.network.responses.GetAddressResponse +import com.tangem.common.extensions.guard +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.math.BigDecimal +import java.util.concurrent.TimeUnit + +private const val PREFIX = "0x" + +internal class TronTransactionHistoryProvider( + private val blockchain: Blockchain, + private val blockBookApi: BlockBookApi, +) : TransactionHistoryProvider { + override suspend fun getTransactionHistoryState( + address: String, + filterType: TransactionHistoryRequest.FilterType, + ): TransactionHistoryState { + return try { + val response = withContext(Dispatchers.IO) { + blockBookApi.getTransactions( + address = address, + page = 1, + pageSize = 1, // We don't need to know all transactions to define state + filterType = filterType, + ) + } + if (!response.transactions.isNullOrEmpty()) { + TransactionHistoryState.Success.HasTransactions(response.transactions.size) + } else { + TransactionHistoryState.Success.Empty + } + } catch (e: Exception) { + TransactionHistoryState.Failed.FetchError(e) + } + } + + override suspend fun getTransactionsHistory(request: TransactionHistoryRequest): Result> { + return try { + val response = + withContext(Dispatchers.IO) { + blockBookApi.getTransactions( + address = request.address, + page = request.page.number, + pageSize = request.page.size, + filterType = request.filterType, + ) + } + val txs = response.transactions + ?.mapNotNull { tx -> + tx.toTransactionHistoryItem( + walletAddress = request.address, + filterType = request.filterType + ) + } + ?: emptyList() + Result.Success( + PaginationWrapper( + page = response.page ?: request.page.number, + totalPages = response.totalPages ?: 0, + itemsOnPage = response.itemsOnPage ?: 0, + items = txs + ) + ) + } catch (e: Exception) { + Result.Failure(e.toBlockchainSdkError()) + } + } + + private fun GetAddressResponse.Transaction.toTransactionHistoryItem( + walletAddress: String, + filterType: TransactionHistoryRequest.FilterType, + ): TransactionHistoryItem? { + val destinationType = extractDestinationType(this, filterType).guard { + Log.info { "Transaction $this doesn't contain a required value" } + return null + } + val amount = extractAmount(tx = this, filterType = filterType).guard { + Log.info { "Transaction $this doesn't contain a required value" } + return null + } + val sourceType = extractSourceType(tx = this, filterType = filterType).guard { + Log.info { "Transaction $this doesn't contain a required value" } + return null + } + return TransactionHistoryItem( + txHash = txid.removePrefix(PREFIX), + timestamp = TimeUnit.SECONDS.toMillis(blockTime.toLong()), + isOutgoing = fromAddress?.equals(walletAddress, ignoreCase = true) == true, + destinationType = destinationType, + sourceType = sourceType, + status = if (confirmations > 0) TransactionStatus.Confirmed else TransactionStatus.Unconfirmed, + type = extractType(tx = this), + amount = amount, + ) + } + + private fun extractDestinationType( + tx: GetAddressResponse.Transaction, + filterType: TransactionHistoryRequest.FilterType, + ): TransactionHistoryItem.DestinationType? { + tx.toAddress ?: return null + tx.fromAddress ?: return null + return when (filterType) { + TransactionHistoryRequest.FilterType.Coin -> { + TransactionHistoryItem.DestinationType.Single( + addressType = if (tx.tokenTransfers.isEmpty()) { + TransactionHistoryItem.AddressType.User(tx.toAddress) + } else { + TransactionHistoryItem.AddressType.Contract(tx.toAddress) + } + ) + } + + is TransactionHistoryRequest.FilterType.Contract -> { + val transfer = tx.getTokenTransfer(filterType.address) ?: return null + TransactionHistoryItem.DestinationType.Single( + addressType = TransactionHistoryItem.AddressType.User(transfer.to) + ) + } + } + } + + private fun extractSourceType( + tx: GetAddressResponse.Transaction, + filterType: TransactionHistoryRequest.FilterType, + ): TransactionHistoryItem.SourceType? { + val address = when (filterType) { + TransactionHistoryRequest.FilterType.Coin -> tx.fromAddress + is TransactionHistoryRequest.FilterType.Contract -> { + tx.getTokenTransfer(filterType.address)?.from + } + }.guard { return null } + + return TransactionHistoryItem.SourceType.Single(address = address) + } + + private fun extractType(tx: GetAddressResponse.Transaction): TransactionHistoryItem.TransactionType { + // Contract type 1 means transfer + if (tx.contractType == 1) return TransactionHistoryItem.TransactionType.Transfer + + return TransactionHistoryItem.TransactionType.ContractMethod(id = tx.contractAddress.orEmpty()) + } + + private fun extractAmount( + tx: GetAddressResponse.Transaction, + filterType: TransactionHistoryRequest.FilterType, + ): Amount? { + return when (filterType) { + TransactionHistoryRequest.FilterType.Coin -> Amount( + value = BigDecimal(tx.value).movePointLeft(blockchain.decimals()), + blockchain = blockchain, + type = AmountType.Coin + ) + + is TransactionHistoryRequest.FilterType.Contract -> { + val transfer = tx.getTokenTransfer(filterType.address) ?: return null + val transferValue = transfer.value ?: "0" + val token = Token( + name = transfer.name.orEmpty(), + symbol = transfer.symbol.orEmpty(), + contractAddress = transfer.token.orEmpty(), + decimals = transfer.decimals, + ) + Amount(value = BigDecimal(transferValue).movePointLeft(transfer.decimals), token = token) + } + } + } + + private fun GetAddressResponse.Transaction.getTokenTransfer(contractAddress: String): GetAddressResponse.Transaction.TokenTransfer? { + return tokenTransfers.firstOrNull { contractAddress.equals(it.token, ignoreCase = true) } + } +} diff --git a/blockchain/src/main/java/com/tangem/blockchain/blockchains/tron/TronWalletManager.kt b/blockchain/src/main/java/com/tangem/blockchain/blockchains/tron/TronWalletManager.kt index 9090d6322..b4b6449f4 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/blockchains/tron/TronWalletManager.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/blockchains/tron/TronWalletManager.kt @@ -18,6 +18,7 @@ import com.tangem.blockchain.common.Wallet import com.tangem.blockchain.common.WalletManager import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionFee +import com.tangem.blockchain.common.txhistory.TransactionHistoryProvider import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.extensions.SimpleResult import com.tangem.blockchain.extensions.bigIntegerValue @@ -31,9 +32,10 @@ import java.math.BigDecimal class TronWalletManager( wallet: Wallet, + transactionHistoryProvider: TransactionHistoryProvider, private val transactionBuilder: TronTransactionBuilder, private val networkService: TronNetworkService, -) : WalletManager(wallet), TransactionSender { +) : WalletManager(wallet, transactionHistoryProvider = transactionHistoryProvider), TransactionSender { override val currentHost: String = networkService.host diff --git a/blockchain/src/main/java/com/tangem/blockchain/common/assembly/impl/BitcoinCashWalletManagerAssembly.kt b/blockchain/src/main/java/com/tangem/blockchain/common/assembly/impl/BitcoinCashWalletManagerAssembly.kt index 32df4ff4a..c08ec1258 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/common/assembly/impl/BitcoinCashWalletManagerAssembly.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/common/assembly/impl/BitcoinCashWalletManagerAssembly.kt @@ -1,12 +1,12 @@ package com.tangem.blockchain.common.assembly.impl import com.tangem.blockchain.blockchains.bitcoin.getBitcoinNetworkProviders -import com.tangem.blockchain.blockchains.bitcoin.getBitcoinTransactionHistoryProvider import com.tangem.blockchain.blockchains.bitcoin.network.BitcoinNetworkService import com.tangem.blockchain.blockchains.bitcoincash.BitcoinCashTransactionBuilder import com.tangem.blockchain.blockchains.bitcoincash.BitcoinCashWalletManager import com.tangem.blockchain.common.assembly.WalletManagerAssembly import com.tangem.blockchain.common.assembly.WalletManagerAssemblyInput +import com.tangem.blockchain.common.txhistory.getTransactionHistoryProvider internal object BitcoinCashWalletManagerAssembly : WalletManagerAssembly() { @@ -21,10 +21,10 @@ internal object BitcoinCashWalletManagerAssembly : WalletManagerAssembly() { @@ -22,7 +22,7 @@ internal object BitcoinWalletManagerAssembly : WalletManagerAssembly() { @@ -22,8 +22,8 @@ internal object DogecoinWalletManagerAssembly : WalletManagerAssembly() { @@ -15,9 +15,9 @@ internal object DucatusWalletManagerAssembly : WalletManagerAssembly() { @@ -21,7 +21,7 @@ internal object EthereumLikeWalletManagerAssembly : WalletManagerAssembly() { @@ -26,7 +26,7 @@ internal object EthereumWalletManagerAssembly : WalletManagerAssembly() { @@ -22,8 +22,8 @@ internal object LitecoinWalletManagerAssembly : WalletManagerAssembly() { @@ -22,8 +22,8 @@ internal object RavencoinWalletManagerAssembly : WalletManagerAssembly() { @@ -35,6 +36,7 @@ internal object TronWalletManagerAssembly : WalletManagerAssembly { + BitcoinTransactionHistoryProvider( + blockchain = this, + BlockBookApi( + config = BlockBookConfig.NowNodes(nowNodesCredentials = config.nowNodeCredentials), + blockchain = this, + ) + ) + } + + Blockchain.Ethereum, + Blockchain.EthereumTestnet, + Blockchain.Arbitrum, + Blockchain.Avalanche, + Blockchain.BSC, + Blockchain.Polygon, + Blockchain.EthereumPow, + Blockchain.Kava, + -> { + EthereumTransactionHistoryProvider( + blockchain = this, + BlockBookApi( + config = BlockBookConfig.NowNodes(nowNodesCredentials = config.nowNodeCredentials), + blockchain = this, + ) + ) + } + + Blockchain.Tron -> { + TronTransactionHistoryProvider( + blockchain = this, + BlockBookApi( + config = BlockBookConfig.NowNodes(nowNodesCredentials = config.nowNodeCredentials), + blockchain = this, + ) + ) + } + + else -> DefaultTransactionHistoryProvider + } + } else { + DefaultTransactionHistoryProvider + } +} diff --git a/blockchain/src/main/java/com/tangem/blockchain/network/blockbook/BlockBookNetworkProvider.kt b/blockchain/src/main/java/com/tangem/blockchain/network/blockbook/BlockBookNetworkProvider.kt index a48d9a2c4..877d4b3f6 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/network/blockbook/BlockBookNetworkProvider.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/network/blockbook/BlockBookNetworkProvider.kt @@ -83,7 +83,7 @@ class BlockBookNetworkProvider( override suspend fun getSignatureCount(address: String): Result { return try { val response = withContext(Dispatchers.IO) { api.getAddress(address) } - Result.Success(response.txs + response.unconfirmedTxs) + Result.Success(response.txs.plus(response.unconfirmedTxs ?: 0)) } catch (e: Exception) { Result.Failure(e.toBlockchainSdkError()) } diff --git a/blockchain/src/main/java/com/tangem/blockchain/network/blockbook/config/BlockBookConfig.kt b/blockchain/src/main/java/com/tangem/blockchain/network/blockbook/config/BlockBookConfig.kt index a9e76c8d7..dd480b2af 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/network/blockbook/config/BlockBookConfig.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/network/blockbook/config/BlockBookConfig.kt @@ -39,6 +39,7 @@ sealed class BlockBookConfig(val credentials: BlockBookCredentials) { Blockchain.Ethereum, Blockchain.Avalanche, Blockchain.EthereumPow, + Blockchain.Tron, -> "https://${prefix}-blockbook.${baseHost}" else -> error("BlockBookConfig.NowNodes don't support blockchain $blockchain") diff --git a/blockchain/src/main/java/com/tangem/blockchain/network/blockbook/network/responses/GetAddressResponse.kt b/blockchain/src/main/java/com/tangem/blockchain/network/blockbook/network/responses/GetAddressResponse.kt index c2d7cf78a..c96cedb33 100644 --- a/blockchain/src/main/java/com/tangem/blockchain/network/blockbook/network/responses/GetAddressResponse.kt +++ b/blockchain/src/main/java/com/tangem/blockchain/network/blockbook/network/responses/GetAddressResponse.kt @@ -6,7 +6,7 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class GetAddressResponse( @Json(name = "balance") val balance: String, - @Json(name = "unconfirmedTxs") val unconfirmedTxs: Int, + @Json(name = "unconfirmedTxs") val unconfirmedTxs: Int?, @Json(name = "txs") val txs: Int, @Json(name = "transactions") val transactions: List?, @Json(name = "page") val page: Int?, @@ -25,6 +25,11 @@ data class GetAddressResponse( @Json(name = "fees") val fees: String, @Json(name = "tokenTransfers") val tokenTransfers: List = emptyList(), @Json(name = "ethereumSpecific") val ethereumSpecific: EthereumSpecific? = null, + //** TRX specific fields **// + @Json(name = "fromAddress") val fromAddress: String?, + @Json(name = "toAddress") val toAddress: String?, + @Json(name = "contract_type") val contractType: Int?, + @Json(name = "contract_name") val contractAddress: String?, ) { @JsonClass(generateAdapter = true) @@ -45,7 +50,8 @@ data class GetAddressResponse( @Json(name = "type") val type: String?, @Json(name = "from") val from: String, @Json(name = "to") val to: String, - @Json(name = "contract") val contract: String, + @Json(name = "contract") val contract: String?, + @Json(name = "token") val token: String?, @Json(name = "name") val name: String?, @Json(name = "symbol") val symbol: String?, @Json(name = "decimals") val decimals: Int,