From e0fb0e3587ed71f5d4d11ee47890cf6e52b7e2cb Mon Sep 17 00:00:00 2001 From: Tangem Date: Wed, 19 Nov 2025 14:22:53 +0300 Subject: [PATCH] Updated on 2026-08-14 --- .../api/tangemTech/TangemTechApi.kt | 9 +++++ .../tangemTech/models/TransactionEventBody.kt | 23 +++++++++++ .../DefaultTransactionRepository.kt | 32 +++++++++++++++ .../transaction/di/TransactionDataModule.kt | 3 ++ .../models/EventTransactionTypeDto.kt | 11 +++++ .../transaction/TransactionRepository.kt | 3 ++ .../usecase/SendTransactionUseCase.kt | 40 +++++++++++++++++-- 7 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/TransactionEventBody.kt create mode 100644 domain/transaction/models/src/main/kotlin/com/tangem/domain/transaction/models/EventTransactionTypeDto.kt diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index e838002aa8..89e68bbe2e 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -172,4 +172,13 @@ interface TangemTechApi { @Header("Cache-Control") cacheControl: String = "max-age=600", ): ApiResponse // endregion + + /** + * Stores transaction hash in cache to prevent duplicate push + * notifications for yield operations (deposit, withdraw, send). + * Used when yield operations generate intermediate transactions + * that should not trigger notifications. + */ + @POST("v1/transaction-events") + suspend fun transactionEvents(@Body name: TransactionEventBody): ApiResponse } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/TransactionEventBody.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/TransactionEventBody.kt new file mode 100644 index 0000000000..806b775af1 --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/TransactionEventBody.kt @@ -0,0 +1,23 @@ +package com.tangem.datasource.api.tangemTech.models + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class TransactionEventBody( + @Json(name = "transactionId") val transactionId: String, + @Json(name = "operationType") val operationType: OperationType, +) + +@JsonClass(generateAdapter = false) +enum class OperationType { + + @Json(name = "YIELD_DEPOSIT") + YIELD_DEPOSIT, + + @Json(name = "YIELD_WITHDRAW") + YIELD_WITHDRAW, + + @Json(name = "YIELD_SEND") + YIELD_SEND, +} \ No newline at end of file diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt index 3ac0f16721..26cb2d893e 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/DefaultTransactionRepository.kt @@ -20,13 +20,19 @@ import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.nft.models.NFTAsset import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.blockchainsdk.utils.toBlockchain +import com.tangem.datasource.api.common.response.fold +import com.tangem.datasource.api.tangemTech.TangemTechApi +import com.tangem.datasource.api.tangemTech.models.OperationType +import com.tangem.datasource.api.tangemTech.models.TransactionEventBody import com.tangem.datasource.local.walletmanager.WalletManagersStore import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWalletId import com.tangem.domain.transaction.TransactionRepository +import com.tangem.domain.transaction.models.EventTransactionTypeDto import com.tangem.domain.walletmanager.WalletManagersFacade import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.coroutines.runCatching import kotlinx.coroutines.withContext import timber.log.Timber import java.math.BigDecimal @@ -34,6 +40,7 @@ import java.math.BigInteger @Suppress("LargeClass") internal class DefaultTransactionRepository( + private val tangemTechApi: TangemTechApi, private val walletManagersFacade: WalletManagersFacade, private val walletManagersStore: WalletManagersStore, private val dispatchers: CoroutineDispatcherProvider, @@ -415,6 +422,31 @@ internal class DefaultTransactionRepository( preparer.prepareAndSignMultiple(transactionData, signer) } + override suspend fun sendTransactionHash(hash: String, transactionType: EventTransactionTypeDto) { + runCatching(dispatchers.io) { + val operationType = when (transactionType) { + EventTransactionTypeDto.DEPOSIT -> OperationType.YIELD_DEPOSIT + EventTransactionTypeDto.WITHDRAW -> OperationType.YIELD_WITHDRAW + EventTransactionTypeDto.SEND -> OperationType.YIELD_SEND + } + val body = TransactionEventBody( + operationType = operationType, + transactionId = hash, + ) + val response = tangemTechApi.transactionEvents(body) + response.fold( + onSuccess = { + Timber.d("Successfully sent yield supply transaction hash: $hash") + }, + onError = { error -> + Timber.e(error, "Failed to send yield supply transaction hash: $hash") + }, + ) + }.onFailure { error -> + Timber.e(error, "Failed to send yield supply transaction hash: $hash") + } + } + private suspend fun getPreparer(network: Network, userWalletId: UserWalletId): TransactionPreparer { val blockchain = network.toBlockchain() val walletManager = walletManagersFacade.getOrCreateWalletManager( diff --git a/data/transaction/src/main/java/com/tangem/data/transaction/di/TransactionDataModule.kt b/data/transaction/src/main/java/com/tangem/data/transaction/di/TransactionDataModule.kt index 267e449c57..e6b6616813 100644 --- a/data/transaction/src/main/java/com/tangem/data/transaction/di/TransactionDataModule.kt +++ b/data/transaction/src/main/java/com/tangem/data/transaction/di/TransactionDataModule.kt @@ -4,6 +4,7 @@ import com.tangem.data.transaction.DefaultFeeRepository import com.tangem.data.transaction.DefaultTransactionRepository import com.tangem.data.transaction.DefaultWalletAddressServiceRepository import com.tangem.data.transaction.error.DefaultFeeErrorResolver +import com.tangem.datasource.api.tangemTech.TangemTechApi import com.tangem.datasource.local.walletmanager.WalletManagersStore import com.tangem.domain.demo.models.DemoConfig import com.tangem.domain.transaction.FeeRepository @@ -25,11 +26,13 @@ internal object TransactionDataModule { @Provides @Singleton fun providesTransactionRepository( + tangemTechApi: TangemTechApi, walletManagersFacade: WalletManagersFacade, walletManagersStore: WalletManagersStore, dispatchers: CoroutineDispatcherProvider, ): TransactionRepository { return DefaultTransactionRepository( + tangemTechApi = tangemTechApi, walletManagersFacade = walletManagersFacade, walletManagersStore = walletManagersStore, dispatchers = dispatchers, diff --git a/domain/transaction/models/src/main/kotlin/com/tangem/domain/transaction/models/EventTransactionTypeDto.kt b/domain/transaction/models/src/main/kotlin/com/tangem/domain/transaction/models/EventTransactionTypeDto.kt new file mode 100644 index 0000000000..b814d46717 --- /dev/null +++ b/domain/transaction/models/src/main/kotlin/com/tangem/domain/transaction/models/EventTransactionTypeDto.kt @@ -0,0 +1,11 @@ +package com.tangem.domain.transaction.models + +/** + * DTO representing the type of a transaction event on tangem backend to send + * info about transaction happens and its hash + */ +enum class EventTransactionTypeDto { + DEPOSIT, + WITHDRAW, + SEND, +} \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt index f6171137cd..efa2d59016 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/TransactionRepository.kt @@ -9,6 +9,7 @@ import com.tangem.blockchain.nft.models.NFTAsset import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.network.Network import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.transaction.models.EventTransactionTypeDto import java.math.BigDecimal import java.math.BigInteger @@ -123,4 +124,6 @@ interface TransactionRepository { userWalletId: UserWalletId, network: Network, ): com.tangem.blockchain.extensions.Result> + + suspend fun sendTransactionHash(hash: String, transactionType: EventTransactionTypeDto) } \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt index 6c0417c870..34a254668f 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/SendTransactionUseCase.kt @@ -3,6 +3,7 @@ package com.tangem.domain.transaction.usecase import arrow.core.Either import arrow.core.left import arrow.core.right +import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras import com.tangem.blockchain.common.BlockchainSdkError import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.TransactionSender @@ -10,21 +11,25 @@ import com.tangem.blockchain.common.TransactionSigner import com.tangem.blockchain.common.transaction.TransactionsSendResult import com.tangem.blockchain.extensions.Result import com.tangem.blockchain.network.ResultChecker +import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyEnterCallData +import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplyExitCallData +import com.tangem.blockchain.yieldsupply.providers.ethereum.yield.EthereumYieldSupplySendCallData import com.tangem.core.ui.format.bigdecimal.format import com.tangem.core.ui.format.bigdecimal.simple import com.tangem.domain.card.common.TapWorkarounds.isStart2Coin 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.demo.models.DemoConfig import com.tangem.domain.demo.DemoTransactionSender +import com.tangem.domain.demo.models.DemoConfig import com.tangem.domain.models.network.Network +import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.networks.single.SingleNetworkStatusFetcher import com.tangem.domain.transaction.TransactionRepository import com.tangem.domain.transaction.error.SendTransactionError import com.tangem.domain.transaction.error.parseWrappedError +import com.tangem.domain.transaction.models.EventTransactionTypeDto import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.models.wallet.UserWallet class SendTransactionUseCase( private val demoConfig: DemoConfig, @@ -100,7 +105,10 @@ class SendTransactionUseCase( ) } .fold( - ifRight = { result -> result.hashes.right() }, + ifRight = { result -> + processSentTransactionsHashes(txsData, result.hashes) + result.hashes.right() + }, ifLeft = { it.left() }, ) } @@ -114,6 +122,32 @@ class SendTransactionUseCase( .map { it.first() } } + private suspend fun processSentTransactionsHashes(transactions: List, hashes: List) { + transactions.forEachIndexed { ind, tx -> + sendHashToBackendIfNeeded(tx, hashes[ind]) + } + } + + /** + * Sends tx hash to backend for specific transaction types + */ + private suspend fun sendHashToBackendIfNeeded(transaction: TransactionData, txHash: String) { + (transaction as? TransactionData.Uncompiled)?.let { + val extras = it.extras + when (extras) { + is EthereumTransactionExtras -> { + val txType = when (extras.callData) { + is EthereumYieldSupplyEnterCallData -> EventTransactionTypeDto.DEPOSIT + is EthereumYieldSupplySendCallData -> EventTransactionTypeDto.SEND + is EthereumYieldSupplyExitCallData -> EventTransactionTypeDto.WITHDRAW + else -> return + } + transactionRepository.sendTransactionHash(txHash, txType) + } + } + } + } + private suspend fun sendDemo( userWallet: UserWallet, network: Network,