Updated on 2026-08-14
This commit is contained in:
parent
514eca0b00
commit
203b1c52cc
17 changed files with 152 additions and 29 deletions
|
|
@ -478,8 +478,6 @@ internal class DefaultSwapRepositoryV2 @Inject constructor(
|
|||
}
|
||||
|
||||
private val MEMO_RESTRICTED_NETWORKS = setOf(
|
||||
Blockchain.XRP.toNetworkId(),
|
||||
Blockchain.Stellar.toNetworkId(),
|
||||
Blockchain.InternetComputer.toNetworkId(),
|
||||
Blockchain.Casper.toNetworkId(),
|
||||
Blockchain.Algorand.toNetworkId(),
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ dependencies {
|
|||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.txhistory.models)
|
||||
implementation(projects.domain.walletManager)
|
||||
implementation(projects.domain.transaction)
|
||||
implementation(projects.domain.wallets.models)
|
||||
// endregion
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import com.tangem.domain.tokens.repository.CurrenciesRepository
|
|||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository
|
||||
import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository
|
||||
import com.tangem.domain.transaction.MemoValidatorFacade
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
|
|
@ -47,10 +48,12 @@ internal object TokensDataModule {
|
|||
@Singleton
|
||||
fun provideCurrencyChecksRepository(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
memoValidatorFacade: MemoValidatorFacade,
|
||||
coroutineDispatcherProvider: CoroutineDispatcherProvider,
|
||||
): CurrencyChecksRepository {
|
||||
return DefaultCurrencyChecksRepository(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
memoValidatorFacade = memoValidatorFacade,
|
||||
coroutineDispatchers = coroutineDispatcherProvider,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import com.tangem.domain.tokens.model.CurrencyAmount
|
|||
import com.tangem.domain.tokens.model.blockchains.UtxoAmountLimit
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning
|
||||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
||||
import com.tangem.domain.transaction.MemoValidatorFacade
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.extensions.isZero
|
||||
|
|
@ -23,6 +24,7 @@ import java.math.BigDecimal
|
|||
|
||||
internal class DefaultCurrencyChecksRepository(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val memoValidatorFacade: MemoValidatorFacade,
|
||||
private val coroutineDispatchers: CoroutineDispatcherProvider,
|
||||
) : CurrencyChecksRepository {
|
||||
|
||||
|
|
@ -112,6 +114,10 @@ internal class DefaultCurrencyChecksRepository(
|
|||
return if (manager is ReserveAmountProvider) manager.isAccountFunded(address) else true
|
||||
}
|
||||
|
||||
override suspend fun checkIfMemoRequired(network: Network, address: String): Boolean {
|
||||
return memoValidatorFacade.isMemoRequired(network, address)
|
||||
}
|
||||
|
||||
override suspend fun checkUtxoAmountLimit(
|
||||
userWalletId: UserWalletId,
|
||||
network: Network,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.data.transaction
|
||||
|
||||
import com.tangem.blockchain.common.memo.MemoState
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.transaction.MemoValidatorFacade
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal class DefaultMemoValidatorFacade(
|
||||
private val blockchainSDKFactory: BlockchainSDKFactory,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : MemoValidatorFacade {
|
||||
|
||||
override suspend fun isMemoRequired(network: Network, destinationAddress: String): Boolean =
|
||||
withContext(dispatchers.io) {
|
||||
val blockchain = network.toBlockchain()
|
||||
val factory = blockchainSDKFactory.getMemoValidatorFactorySync() ?: return@withContext false
|
||||
val validator = factory.create(blockchain)
|
||||
when (val result = validator.isMemoRequired(destinationAddress)) {
|
||||
is Result.Success -> result.data
|
||||
is Result.Failure -> false
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun validateMemo(network: Network, memo: String): Boolean = withContext(dispatchers.io) {
|
||||
val blockchain = network.toBlockchain()
|
||||
val factory = blockchainSDKFactory.getMemoValidatorFactorySync() ?: return@withContext true
|
||||
val validator = factory.create(blockchain)
|
||||
when (val result = validator.validateMemo(memo)) {
|
||||
is Result.Success -> when (result.data) {
|
||||
MemoState.Valid,
|
||||
MemoState.NotSupported,
|
||||
-> true
|
||||
MemoState.Invalid -> false
|
||||
}
|
||||
is Result.Failure -> true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,12 +6,10 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.blockchain.common.NameResolver
|
||||
import com.tangem.blockchain.common.ResolveAddressResult
|
||||
import com.tangem.blockchain.common.ReverseResolveAddressResult
|
||||
import com.tangem.blockchain.common.TransactionValidator
|
||||
import com.tangem.blockchain.common.memo.MemoState
|
||||
import com.tangem.blockchain.extensions.Result
|
||||
import com.tangem.blockchainsdk.utils.toBlockchain
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.transaction.MemoValidatorFacade
|
||||
import com.tangem.domain.transaction.WalletAddressServiceRepository
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.ParsedQrCode
|
||||
|
|
@ -21,6 +19,7 @@ import kotlinx.coroutines.withContext
|
|||
|
||||
class DefaultWalletAddressServiceRepository(
|
||||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val memoValidatorFacade: MemoValidatorFacade,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : WalletAddressServiceRepository {
|
||||
|
||||
|
|
@ -96,28 +95,11 @@ class DefaultWalletAddressServiceRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun validateMemo(userWalletId: UserWalletId, network: Network, memo: String): Boolean =
|
||||
withContext(dispatchers.io) {
|
||||
val walletManager = walletManagersFacade.getOrCreateWalletManager(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
) ?: return@withContext true
|
||||
override suspend fun validateMemo(network: Network, memo: String): Boolean =
|
||||
memoValidatorFacade.validateMemo(network, memo)
|
||||
|
||||
val memoStateResult = (walletManager as? TransactionValidator)?.validateMemo(memo)
|
||||
if (memoStateResult != null) {
|
||||
when (memoStateResult) {
|
||||
is Result.Success -> when (memoStateResult.data) {
|
||||
MemoState.NotSupported,
|
||||
MemoState.Valid,
|
||||
-> true
|
||||
MemoState.Invalid -> false
|
||||
}
|
||||
is Result.Failure -> true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
override suspend fun isMemoRequired(network: Network, destinationAddress: String): Boolean =
|
||||
memoValidatorFacade.isMemoRequired(network, destinationAddress)
|
||||
|
||||
override suspend fun parseSharedAddress(input: String, network: Network): ParsedQrCode {
|
||||
val blockchain = network.toBlockchain()
|
||||
|
|
|
|||
|
|
@ -3,15 +3,18 @@ package com.tangem.data.transaction.di
|
|||
import com.tangem.data.common.currency.ResponseCryptoCurrenciesFactory
|
||||
import com.tangem.data.transaction.DefaultFeeRepository
|
||||
import com.tangem.data.transaction.DefaultGaslessTransactionRepository
|
||||
import com.tangem.data.transaction.DefaultMemoValidatorFacade
|
||||
import com.tangem.data.transaction.DefaultTransactionRepository
|
||||
import com.tangem.data.transaction.DefaultWalletAddressServiceRepository
|
||||
import com.tangem.data.transaction.error.DefaultFeeErrorResolver
|
||||
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
||||
import com.tangem.datasource.api.gasless.GaslessTxServiceApi
|
||||
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
|
||||
import com.tangem.domain.transaction.GaslessTransactionRepository
|
||||
import com.tangem.domain.transaction.MemoValidatorFacade
|
||||
import com.tangem.domain.transaction.TransactionRepository
|
||||
import com.tangem.domain.transaction.WalletAddressServiceRepository
|
||||
import com.tangem.domain.transaction.error.FeeErrorResolver
|
||||
|
|
@ -52,14 +55,28 @@ internal object TransactionDataModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesMemoValidatorFacade(
|
||||
blockchainSDKFactory: BlockchainSDKFactory,
|
||||
coroutineDispatcherProvider: CoroutineDispatcherProvider,
|
||||
): MemoValidatorFacade {
|
||||
return DefaultMemoValidatorFacade(
|
||||
blockchainSDKFactory = blockchainSDKFactory,
|
||||
dispatchers = coroutineDispatcherProvider,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesWalletAddressServiceRepository(
|
||||
walletManagersFacade: WalletManagersFacade,
|
||||
memoValidatorFacade: MemoValidatorFacade,
|
||||
coroutineDispatcherProvider: CoroutineDispatcherProvider,
|
||||
): WalletAddressServiceRepository {
|
||||
return DefaultWalletAddressServiceRepository(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
memoValidatorFacade = memoValidatorFacade,
|
||||
dispatchers = coroutineDispatcherProvider,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,4 +11,5 @@ data class CryptoCurrencyCheck(
|
|||
val utxoAmountLimit: UtxoAmountLimit?,
|
||||
val isAccountFunded: Boolean,
|
||||
val rentWarning: CryptoCurrencyWarning.Rent?,
|
||||
val isMemoRequired: Boolean = false,
|
||||
)
|
||||
|
|
@ -45,6 +45,14 @@ class GetCurrencyCheckUseCase(
|
|||
} else {
|
||||
false
|
||||
}
|
||||
val isMemoRequired = if (recipientAddress != null) {
|
||||
currencyChecksRepository.checkIfMemoRequired(
|
||||
network = network,
|
||||
address = recipientAddress,
|
||||
)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
val utxoAmountLimit = if (currency is CryptoCurrency.Coin && amount != null && fee != null) {
|
||||
currencyChecksRepository.checkUtxoAmountLimit(
|
||||
userWalletId = userWalletId,
|
||||
|
|
@ -65,6 +73,7 @@ class GetCurrencyCheckUseCase(
|
|||
utxoAmountLimit = utxoAmountLimit,
|
||||
isAccountFunded = isAccountFunded,
|
||||
rentWarning = rentWarning,
|
||||
isMemoRequired = isMemoRequired,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ interface CurrencyChecksRepository {
|
|||
/** Returns true if account with [address] was reserved with minimum amount */
|
||||
suspend fun checkIfAccountFunded(userWalletId: UserWalletId, network: Network, address: String): Boolean
|
||||
|
||||
/** Returns true if a memo/destination tag is required for the given [address] on [network] */
|
||||
suspend fun checkIfMemoRequired(network: Network, address: String): Boolean
|
||||
|
||||
/** Checks if transaction amount is within the UTXO limit */
|
||||
suspend fun checkUtxoAmountLimit(
|
||||
userWalletId: UserWalletId,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.domain.transaction
|
||||
|
||||
import com.tangem.domain.models.network.Network
|
||||
|
||||
/**
|
||||
* Facade for memo validation operations.
|
||||
*/
|
||||
interface MemoValidatorFacade {
|
||||
|
||||
/**
|
||||
* Returns true if a memo/destination tag is required for the given [destinationAddress] on [network].
|
||||
* Returns false on network errors or for unsupported blockchains.
|
||||
*/
|
||||
suspend fun isMemoRequired(network: Network, destinationAddress: String): Boolean
|
||||
|
||||
/**
|
||||
* Returns true if [memo] is valid for the given [network], or if memo is not supported.
|
||||
* Returns true on errors (lenient fallback).
|
||||
*/
|
||||
suspend fun validateMemo(network: Network, memo: String): Boolean
|
||||
}
|
||||
|
|
@ -23,7 +23,9 @@ interface WalletAddressServiceRepository {
|
|||
|
||||
suspend fun validateAddress(userWalletId: UserWalletId, network: Network, address: String): Boolean
|
||||
|
||||
suspend fun validateMemo(userWalletId: UserWalletId, network: Network, memo: String): Boolean
|
||||
suspend fun validateMemo(network: Network, memo: String): Boolean
|
||||
|
||||
suspend fun isMemoRequired(network: Network, destinationAddress: String): Boolean
|
||||
|
||||
suspend fun parseSharedAddress(input: String, network: Network): ParsedQrCode
|
||||
}
|
||||
|
|
@ -22,7 +22,6 @@ class ValidateWalletMemoUseCase(
|
|||
): Either<ValidateMemoError, Unit> {
|
||||
return try {
|
||||
val isValidMemo = walletAddressServiceRepository.validateMemo(
|
||||
userWalletId = userWalletId,
|
||||
network = cryptoCurrency.network,
|
||||
memo = memo,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import com.tangem.domain.tokens.GetCurrencyCheckUseCase
|
|||
import com.tangem.domain.tokens.IsAmountSubtractAvailableUseCase
|
||||
import com.tangem.domain.tokens.model.warnings.CryptoCurrencyCheck
|
||||
import com.tangem.domain.transaction.usecase.ValidateTransactionUseCase
|
||||
import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase
|
||||
import com.tangem.domain.utils.convertToSdkAmount
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent
|
||||
import com.tangem.features.send.v2.api.SendNotificationsComponent.Params.NotificationData
|
||||
|
|
@ -68,6 +69,7 @@ internal class NotificationsModel @Inject constructor(
|
|||
private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase,
|
||||
private val getBalanceNotEnoughForFeeWarningUseCase: GetBalanceNotEnoughForFeeWarningUseCase,
|
||||
private val validateTransactionUseCase: ValidateTransactionUseCase,
|
||||
private val validateWalletMemoUseCase: ValidateWalletMemoUseCase,
|
||||
private val getTronFeeNotificationShowCountUseCase: GetTronFeeNotificationShowCountUseCase,
|
||||
private val incrementNotificationsShowCountUseCase: IncrementNotificationsShowCountUseCase,
|
||||
private val notificationsUpdateTrigger: SendNotificationsUpdateTrigger,
|
||||
|
|
@ -350,6 +352,10 @@ internal class NotificationsModel @Inject constructor(
|
|||
params.callback.onAmountReduceTo(reduceTo)
|
||||
},
|
||||
)
|
||||
addDestinationTagRequiredNotification(
|
||||
isMemoRequired = currencyCheck.isMemoRequired,
|
||||
memo = memo,
|
||||
)
|
||||
addHighFeeWarningNotification(
|
||||
enteredAmountValue = enteredAmount,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
|
|
@ -370,6 +376,21 @@ internal class NotificationsModel @Inject constructor(
|
|||
addTronNetworkFeesNotification()
|
||||
}
|
||||
|
||||
private suspend fun MutableList<NotificationUM>.addDestinationTagRequiredNotification(
|
||||
isMemoRequired: Boolean,
|
||||
memo: String?,
|
||||
) {
|
||||
if (!isMemoRequired || contains(NotificationUM.Error.DestinationTagRequired)) return
|
||||
val isMemoInvalid = memo.isNullOrEmpty() || validateWalletMemoUseCase(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrency = currency,
|
||||
memo = memo,
|
||||
).isLeft()
|
||||
if (isMemoInvalid) {
|
||||
add(NotificationUM.Error.DestinationTagRequired)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun MutableList<NotificationUM>.addTronNetworkFeesNotification() {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
val isTronToken = cryptoCurrency is CryptoCurrency.Token &&
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
# https://github.com/tangem/tangem-sdk-android/
|
||||
# https://github.com/tangem/vico
|
||||
|
||||
tangemBlockchainSdk = "develop-1455"
|
||||
tangemBlockchainSdk = "develop-1461"
|
||||
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
|
||||
tangemCardSdk = "develop-598"
|
||||
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.blockchainsdk
|
||||
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.blockchain.common.memo.MemoValidatorFactory
|
||||
|
||||
/**
|
||||
* Blockchain SDK components factory
|
||||
|
|
@ -14,4 +15,7 @@ interface BlockchainSDKFactory {
|
|||
|
||||
/** Get [WalletManagerFactory] synchronously */
|
||||
suspend fun getWalletManagerFactorySync(): WalletManagerFactory?
|
||||
|
||||
/** Get [MemoValidatorFactory] synchronously */
|
||||
suspend fun getMemoValidatorFactorySync(): MemoValidatorFactory?
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.blockchainsdk
|
|||
|
||||
import com.tangem.blockchain.common.BlockchainSdkConfig
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.blockchain.common.memo.MemoValidatorFactory
|
||||
import com.tangem.blockchainsdk.providers.BlockchainProvidersTypesManager
|
||||
import com.tangem.datasource.local.config.providers.models.ProviderModel
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -32,6 +33,7 @@ internal class DefaultBlockchainSDKFactory(
|
|||
private val mainScope = CoroutineScope(dispatchers.main)
|
||||
|
||||
private val walletManagerFactory: Flow<WalletManagerFactory?> = createWalletManagerFactory()
|
||||
private val memoValidatorFactory: Flow<MemoValidatorFactory?> = createMemoValidatorFactory()
|
||||
|
||||
override suspend fun init() {
|
||||
coroutineScope {
|
||||
|
|
@ -41,6 +43,8 @@ internal class DefaultBlockchainSDKFactory(
|
|||
|
||||
override suspend fun getWalletManagerFactorySync(): WalletManagerFactory? = walletManagerFactory.firstOrNull()
|
||||
|
||||
override suspend fun getMemoValidatorFactorySync(): MemoValidatorFactory? = memoValidatorFactory.firstOrNull()
|
||||
|
||||
private fun createWalletManagerFactory(): Flow<WalletManagerFactory?> {
|
||||
return combine(
|
||||
flow = flowOf(blockchainSdkConfig),
|
||||
|
|
@ -51,4 +55,14 @@ internal class DefaultBlockchainSDKFactory(
|
|||
// don't use Lazily because some features (WC) require initialized factory on app started
|
||||
.stateIn(scope = mainScope, started = SharingStarted.Eagerly, initialValue = null)
|
||||
}
|
||||
|
||||
private fun createMemoValidatorFactory(): Flow<MemoValidatorFactory?> {
|
||||
return combine(
|
||||
flow = flowOf(blockchainSdkConfig),
|
||||
flow2 = blockchainProvidersTypesManager.get(),
|
||||
) { config, providerTypes ->
|
||||
MemoValidatorFactory(config = config, blockchainProviderTypes = providerTypes)
|
||||
}
|
||||
.stateIn(scope = mainScope, started = SharingStarted.Eagerly, initialValue = null)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue