Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-19 14:22:53 +03:00
parent 4ad958be89
commit e0fb0e3587
7 changed files with 118 additions and 3 deletions

View file

@ -172,4 +172,13 @@ interface TangemTechApi {
@Header("Cache-Control") cacheControl: String = "max-age=600",
): ApiResponse<PromoBannerResponse>
// 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<Unit>
}

View file

@ -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,
}

View file

@ -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(

View file

@ -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,

View file

@ -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,
}

View file

@ -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<List<ByteArray>>
suspend fun sendTransactionHash(hash: String, transactionType: EventTransactionTypeDto)
}

View file

@ -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<TransactionData>, hashes: List<String>) {
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,