diff --git a/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt b/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt index 4dc9cefd1a..5708a79d8b 100644 --- a/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt +++ b/app/src/main/java/com/tangem/tap/data/DefaultTangemPayStorage.kt @@ -2,6 +2,9 @@ package com.tangem.tap.data import android.content.Context import com.squareup.moshi.Moshi +import com.tangem.data.pay.entity.WithdrawStoreData +import com.tangem.data.pay.util.WithdrawStateConverter +import com.tangem.data.pay.util.WithdrawStoreDataConverter import com.tangem.datasource.di.NetworkMoshi import com.tangem.datasource.local.preferences.AppPreferencesStore import com.tangem.datasource.local.preferences.PreferencesKeys @@ -11,6 +14,7 @@ import com.tangem.datasource.local.preferences.utils.getSyncOrNull import com.tangem.datasource.local.preferences.utils.store import com.tangem.datasource.local.visa.TangemPayStorage import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.TangemPayWithdrawState import com.tangem.domain.visa.model.TangemPayAuthTokens import com.tangem.sdk.storage.AndroidSecureStorageV2 import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -41,6 +45,8 @@ internal class DefaultTangemPayStorage @Inject constructor( } private val tokensAdapter by lazy { moshi.adapter(TangemPayAuthTokens::class.java) } + private val withdrawStoreDataConverter by lazy { WithdrawStoreDataConverter() } + private val withdrawStateConverter by lazy { WithdrawStateConverter() } override suspend fun storeCustomerWalletAddress(userWalletId: UserWalletId, customerWalletAddress: String) { withContext(dispatcherProvider.io) { @@ -131,10 +137,12 @@ internal class DefaultTangemPayStorage @Inject constructor( return appPreferencesStore.getSyncOrNull(PreferencesKeys.getTangemPayCheckCustomerByWalletId(userWalletId)) } - override suspend fun storeWithdrawOrder(userWalletId: UserWalletId, orderId: String) { + override suspend fun storeWithdrawOrder(userWalletId: UserWalletId, data: TangemPayWithdrawState) { appPreferencesStore.editData { mutablePreferences -> - val orders = mutablePreferences.getObjectMap(PreferencesKeys.TANGEM_PAY_WITHDRAW_ORDERS_KEY) - .plus(createWithdrawOrderIdKey(userWalletId) to orderId) + val orders = mutablePreferences.getObjectMap( + PreferencesKeys.TANGEM_PAY_WITHDRAW_ORDERS_KEY, + ) + .plus(createWithdrawOrderIdKey(userWalletId) to withdrawStoreDataConverter.convert(data)) mutablePreferences.setObjectMap( key = PreferencesKeys.TANGEM_PAY_WITHDRAW_ORDERS_KEY, value = orders, @@ -142,14 +150,19 @@ internal class DefaultTangemPayStorage @Inject constructor( } } - override suspend fun getWithdrawOrderId(userWalletId: UserWalletId): String? { - val orders = appPreferencesStore.getObjectMapSync(PreferencesKeys.TANGEM_PAY_WITHDRAW_ORDERS_KEY) - return orders[createWithdrawOrderIdKey(userWalletId)] + override suspend fun getWithdrawOrder(userWalletId: UserWalletId): TangemPayWithdrawState? { + val orders = appPreferencesStore.getObjectMapSync( + PreferencesKeys.TANGEM_PAY_WITHDRAW_ORDERS_KEY, + ) + val data = orders[createWithdrawOrderIdKey(userWalletId)] ?: return null + return withdrawStateConverter.convert(data) } override suspend fun deleteWithdrawOrder(userWalletId: UserWalletId) { appPreferencesStore.editData { mutablePreferences -> - val orders = mutablePreferences.getObjectMap(PreferencesKeys.TANGEM_PAY_WITHDRAW_ORDERS_KEY) + val orders = mutablePreferences.getObjectMap( + PreferencesKeys.TANGEM_PAY_WITHDRAW_ORDERS_KEY, + ) .minus(createWithdrawOrderIdKey(userWalletId)) mutablePreferences.setObjectMap( key = PreferencesKeys.TANGEM_PAY_WITHDRAW_ORDERS_KEY, diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/OrderResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/OrderResponse.kt index a476b5dc1b..48cc8fc4fa 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/OrderResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/pay/models/response/OrderResponse.kt @@ -27,6 +27,7 @@ data class OrderResponse( @Json(name = "emboss_name") val embossName: String?, @Json(name = "product_instance_id") val productInstanceId: String?, @Json(name = "payment_account_id") val paymentAccountId: String?, + @Json(name = "transaction_hash") val transactionHash: String?, ) } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayStorage.kt b/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayStorage.kt index 591bc9f6be..bd29637e98 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayStorage.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/visa/TangemPayStorage.kt @@ -1,6 +1,7 @@ package com.tangem.datasource.local.visa import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.TangemPayWithdrawState import com.tangem.domain.visa.model.TangemPayAuthTokens @Suppress("TooManyFunctions") @@ -28,9 +29,9 @@ interface TangemPayStorage { suspend fun storeCheckCustomerWalletResult(userWalletId: UserWalletId, isPaeraCustomer: Boolean) suspend fun checkCustomerWalletResult(userWalletId: UserWalletId): Boolean? - suspend fun storeWithdrawOrder(userWalletId: UserWalletId, orderId: String) + suspend fun storeWithdrawOrder(userWalletId: UserWalletId, data: TangemPayWithdrawState) - suspend fun getWithdrawOrderId(userWalletId: UserWalletId): String? + suspend fun getWithdrawOrder(userWalletId: UserWalletId): TangemPayWithdrawState? suspend fun deleteWithdrawOrder(userWalletId: UserWalletId) diff --git a/data/visa/build.gradle.kts b/data/visa/build.gradle.kts index 3c9ef9ff07..67768d9958 100644 --- a/data/visa/build.gradle.kts +++ b/data/visa/build.gradle.kts @@ -36,6 +36,7 @@ dependencies { implementation(projects.domain.walletManager) implementation(projects.domain.quotes) implementation(projects.domain.common) + implementation(projects.features.swap.domain) /** Feature API - remove after removing [HotWalletFeatureToggles] */ implementation(projects.features.hotWallet.api) diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt index 500e5fa50d..5868e4c0c6 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt @@ -45,7 +45,7 @@ internal interface TangemPayDataModule { @Binds @Singleton - fun bindTangemPaySwapRepository(repository: DefaultTangemPaySwapRepository): TangemPaySwapRepository + fun bindTangemPaySwapRepository(repository: DefaultTangemPayWithdrawRepository): TangemPayWithdrawRepository @Binds @Singleton diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/entity/WithdrawStoreData.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/entity/WithdrawStoreData.kt new file mode 100644 index 0000000000..cdad87d12b --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/entity/WithdrawStoreData.kt @@ -0,0 +1,19 @@ +package com.tangem.data.pay.entity + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = false) +data class WithdrawStoreData( + @Json(name = "orderId") val orderId: String, + @Json(name = "exchangeData") val exchangeData: ExchangeStoreData?, +) + +@JsonClass(generateAdapter = false) +data class ExchangeStoreData( + @Json(name = "txId") val txId: String, + @Json(name = "fromNetwork") val fromNetwork: String, + @Json(name = "fromAddress") val fromAddress: String, + @Json(name = "payInAddress") val payInAddress: String, + @Json(name = "payInExtraId") val payInExtraId: String?, +) \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCustomerOrderRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCustomerOrderRepository.kt index 11afd970e8..f479794766 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCustomerOrderRepository.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultCustomerOrderRepository.kt @@ -2,8 +2,8 @@ package com.tangem.data.pay.repository import arrow.core.Either import com.tangem.datasource.api.pay.TangemPayApi -import com.tangem.datasource.local.visa.TangemPayStorage import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.model.OrderData import com.tangem.domain.pay.model.OrderStatus import com.tangem.domain.pay.repository.CustomerOrderRepository import com.tangem.domain.visa.error.VisaApiError @@ -12,35 +12,23 @@ import javax.inject.Inject internal class DefaultCustomerOrderRepository @Inject constructor( private val tangemPayApi: TangemPayApi, private val requestHelper: TangemPayRequestPerformer, - private val tangemPayStorage: TangemPayStorage, ) : CustomerOrderRepository { - override suspend fun getOrderStatus( - userWalletId: UserWalletId, - orderId: String, - ): Either { + override suspend fun getOrderData(userWalletId: UserWalletId, orderId: String): Either { return requestHelper.performRequest(userWalletId) { authHeader -> tangemPayApi.getOrder(authHeader = authHeader, orderId = orderId) }.map { response -> - when (response.result?.status) { + val status = when (response.result?.status) { null -> OrderStatus.UNKNOWN OrderStatus.NEW.apiName -> OrderStatus.NEW OrderStatus.PROCESSING.apiName -> OrderStatus.PROCESSING OrderStatus.COMPLETED.apiName -> OrderStatus.COMPLETED else -> OrderStatus.CANCELED } + OrderData( + status = status, + withdrawTxHash = response.result?.data?.transactionHash?.ifEmpty { null }, + ) } } - - override suspend fun hasWithdrawOrder(userWalletId: UserWalletId): Boolean { - val orderId = tangemPayStorage.getWithdrawOrderId(userWalletId) - if (orderId == null) return false - - val status = getOrderStatus(userWalletId, orderId).getOrNull() - - val hasActiveOrder = status == OrderStatus.NEW || status == OrderStatus.PROCESSING - if (!hasActiveOrder) tangemPayStorage.deleteWithdrawOrder(userWalletId) - - return hasActiveOrder - } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPaySwapRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPaySwapRepository.kt deleted file mode 100644 index 6bc1774f28..0000000000 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPaySwapRepository.kt +++ /dev/null @@ -1,99 +0,0 @@ -package com.tangem.data.pay.repository - -import arrow.core.Either -import arrow.core.left -import com.tangem.core.error.UniversalError -import com.tangem.data.common.quote.QuotesFetcher -import com.tangem.datasource.api.pay.TangemPayApi -import com.tangem.datasource.api.pay.models.request.WithdrawDataRequest -import com.tangem.datasource.api.pay.models.request.WithdrawRequest -import com.tangem.datasource.local.visa.TangemPayStorage -import com.tangem.domain.models.currency.CryptoCurrency -import com.tangem.domain.models.wallet.UserWallet -import com.tangem.domain.pay.WithdrawalResult -import com.tangem.domain.pay.WithdrawalSignatureResult -import com.tangem.domain.pay.datasource.TangemPayAuthDataSource -import com.tangem.domain.pay.repository.TangemPaySwapRepository -import com.tangem.domain.visa.error.VisaApiError -import com.tangem.utils.extensions.addHexPrefix -import java.math.BigDecimal -import java.math.RoundingMode -import java.util.Currency -import java.util.Locale -import javax.inject.Inject - -@Suppress("LongParameterList") -internal class DefaultTangemPaySwapRepository @Inject constructor( - private val tangemPayApi: TangemPayApi, - private val requestHelper: TangemPayRequestPerformer, - private val authDataSource: TangemPayAuthDataSource, - private val quotesFetcher: QuotesFetcher, - private val tangemPayStorage: TangemPayStorage, -) : TangemPaySwapRepository { - - override suspend fun withdraw( - userWallet: UserWallet, - receiverAddress: String, - cryptoAmount: BigDecimal, - cryptoCurrencyId: CryptoCurrency.RawID, - ): Either { - val amountInCents = getAmountInCents(cryptoAmount, cryptoCurrencyId) - if (amountInCents.isNullOrEmpty()) return Either.Left(VisaApiError.WithdrawalDataError) - return requestHelper.performRequest(userWallet.walletId) { authHeader -> - val request = WithdrawDataRequest(amountInCents = amountInCents, recipientAddress = receiverAddress) - tangemPayApi.getWithdrawData(authHeader = authHeader, body = request) - }.map { data -> - val result = data.result ?: return VisaApiError.WithdrawalDataError.left() - val signatureResult = authDataSource.getWithdrawalSignature( - userWallet = userWallet, - hash = result.hash, - ).getOrNull() - - return when (signatureResult) { - is WithdrawalSignatureResult.Cancelled -> { - Either.Right(WithdrawalResult.Cancelled) - } - is WithdrawalSignatureResult.Success -> { - requestHelper.performRequest(userWallet.walletId) { authHeader -> - val request = WithdrawRequest( - amountInCents = amountInCents, - recipientAddress = receiverAddress, - adminSalt = result.salt, - senderAddress = result.senderAddress, - adminSignature = signatureResult.signature.addHexPrefix(), - ) - tangemPayApi.withdraw(authHeader = authHeader, body = request) - } - .mapLeft { return Either.Left(VisaApiError.WithdrawError) } - .map { response -> - val orderId = response.result?.orderId - if (orderId != null) tangemPayStorage.storeWithdrawOrder(userWallet.walletId, orderId) - WithdrawalResult.Success - } - } - null -> return Either.Left(VisaApiError.SignWithdrawError) - } - } - } - - private suspend fun getAmountInCents(cryptoAmount: BigDecimal, cryptoCurrencyId: CryptoCurrency.RawID): String? { - val fiatRate = getFiatRate(cryptoCurrencyId) ?: return null - val amountInDollars = cryptoAmount.multiply(fiatRate) - val defaultFractionDigits = Currency.getInstance(Locale.US).defaultFractionDigits - return amountInDollars - .setScale(defaultFractionDigits, RoundingMode.HALF_UP) - .movePointRight(defaultFractionDigits) - .longValueExact() - .toString() - } - - private suspend fun getFiatRate(cryptoCurrencyId: CryptoCurrency.RawID): BigDecimal? { - val quotes = quotesFetcher.fetch( - fiatCurrencyId = Currency.getInstance(Locale.US).currencyCode, - currencyId = cryptoCurrencyId.value, - field = QuotesFetcher.Field.PRICE, - ).getOrNull() - - return quotes?.quotes[cryptoCurrencyId.value]?.price - } -} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayWithdrawRepository.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayWithdrawRepository.kt new file mode 100644 index 0000000000..cea8e61d60 --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/repository/DefaultTangemPayWithdrawRepository.kt @@ -0,0 +1,301 @@ +package com.tangem.data.pay.repository + +import arrow.core.Either +import arrow.core.left +import arrow.core.right +import com.tangem.core.error.UniversalError +import com.tangem.data.common.quote.QuotesFetcher +import com.tangem.datasource.api.pay.TangemPayApi +import com.tangem.datasource.api.pay.models.request.WithdrawDataRequest +import com.tangem.datasource.api.pay.models.request.WithdrawRequest +import com.tangem.datasource.api.pay.models.response.WithdrawResponse +import com.tangem.datasource.local.visa.TangemPayStorage +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.pay.TangemPayWithdrawExchangeState +import com.tangem.domain.pay.TangemPayWithdrawState +import com.tangem.domain.pay.WithdrawalResult +import com.tangem.domain.pay.WithdrawalSignatureResult +import com.tangem.domain.pay.datasource.TangemPayAuthDataSource +import com.tangem.domain.pay.model.OrderData +import com.tangem.domain.pay.model.OrderStatus +import com.tangem.domain.pay.repository.CustomerOrderRepository +import com.tangem.domain.pay.repository.TangemPayWithdrawRepository +import com.tangem.domain.visa.error.VisaApiError +import com.tangem.feature.swap.domain.api.SwapRepository +import com.tangem.feature.swap.domain.models.ExpressDataError +import com.tangem.utils.extensions.addHexPrefix +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.delay +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import timber.log.Timber +import java.math.BigDecimal +import java.math.RoundingMode +import java.util.Currency +import java.util.Locale +import javax.inject.Inject +import kotlin.collections.set +import kotlin.coroutines.cancellation.CancellationException +import kotlin.time.Duration.Companion.seconds + +private const val TAG = "TangemPaySwapRepository" + +@Suppress("LongParameterList") +internal class DefaultTangemPayWithdrawRepository @Inject constructor( + private val tangemPayApi: TangemPayApi, + private val requestHelper: TangemPayRequestPerformer, + private val authDataSource: TangemPayAuthDataSource, + private val quotesFetcher: QuotesFetcher, + private val tangemPayStorage: TangemPayStorage, + private val swapRepository: SwapRepository, + private val orderRepository: CustomerOrderRepository, +) : TangemPayWithdrawRepository { + + private val withdrawPollingScope = CoroutineScope(Dispatchers.IO + SupervisorJob()) + private val withdrawPollingJobs = mutableMapOf() + private val withdrawPollingMutex = Mutex() + + override suspend fun withdraw( + userWallet: UserWallet, + receiverAddress: String, + cryptoAmount: BigDecimal, + cryptoCurrencyId: CryptoCurrency.RawID, + exchangeData: TangemPayWithdrawExchangeState, + ): Either { + val amountInCents = getAmountInCents(cryptoAmount, cryptoCurrencyId) + if (amountInCents.isNullOrEmpty()) return Either.Left(VisaApiError.WithdrawalDataError) + return requestHelper.performRequest(userWallet.walletId) { authHeader -> + val request = WithdrawDataRequest(amountInCents = amountInCents, recipientAddress = receiverAddress) + tangemPayApi.getWithdrawData(authHeader = authHeader, body = request) + }.map { data -> + val result = data.result ?: return VisaApiError.WithdrawalDataError.left() + val signatureResult = authDataSource.getWithdrawalSignature( + userWallet = userWallet, + hash = result.hash, + ).getOrNull() + + return when (signatureResult) { + is WithdrawalSignatureResult.Cancelled -> { + Either.Right(WithdrawalResult.Cancelled) + } + is WithdrawalSignatureResult.Success -> { + requestHelper.performRequest(userWallet.walletId) { authHeader -> + val request = WithdrawRequest( + amountInCents = amountInCents, + recipientAddress = receiverAddress, + adminSalt = result.salt, + senderAddress = result.senderAddress, + adminSignature = signatureResult.signature.addHexPrefix(), + ) + tangemPayApi.withdraw(authHeader = authHeader, body = request) + } + .mapLeft { return Either.Left(VisaApiError.WithdrawError) } + .map { response -> + processWithdrawResult(response, userWallet, exchangeData) + WithdrawalResult.Success + } + } + null -> return Either.Left(VisaApiError.SignWithdrawError) + } + } + } + + private suspend fun processWithdrawResult( + response: WithdrawResponse, + userWallet: UserWallet, + exchangeData: TangemPayWithdrawExchangeState, + ) { + val orderId = response.result?.orderId + if (orderId != null) { + val orderData = orderRepository + .getOrderData(userWalletId = userWallet.walletId, orderId = orderId).getOrNull() + val withdrawTxHash = orderData?.withdrawTxHash + val storeData = TangemPayWithdrawState( + orderId = orderId, + exchangeData = exchangeData, + ) + if (orderData != null && !withdrawTxHash.isNullOrEmpty()) { + finalizeWithdraw( + userWallet = userWallet, + withdrawTxHash = withdrawTxHash, + orderId = orderId, + exchangeData = exchangeData, + order = orderData, + ).onLeft { + tangemPayStorage.storeWithdrawOrder(userWalletId = userWallet.walletId, data = storeData) + } + } else { + tangemPayStorage.storeWithdrawOrder(userWalletId = userWallet.walletId, data = storeData) + } + } + } + + private suspend fun finalizeWithdraw( + userWallet: UserWallet, + withdrawTxHash: String, + orderId: String, + exchangeData: TangemPayWithdrawExchangeState, + order: OrderData, + ): Either { + return swapRepository.exchangeSent( + userWallet = userWallet, + txId = exchangeData.txId, + fromNetwork = exchangeData.fromNetwork, + fromAddress = exchangeData.fromAddress, + payInAddress = exchangeData.payInAddress, + txHash = withdrawTxHash, + payInExtraId = exchangeData.payInExtraId, + ) + .onRight { + val isActive = order.status == OrderStatus.NEW || order.status == OrderStatus.PROCESSING + if (!isActive) { + tangemPayStorage.deleteWithdrawOrder(userWalletId = userWallet.walletId) + } else { + tangemPayStorage.storeWithdrawOrder( + userWalletId = userWallet.walletId, + data = TangemPayWithdrawState(orderId = orderId, exchangeData = null), + ) + } + } + .onLeft { error -> + Timber.tag(TAG).e(error.toString()) + } + } + + override suspend fun hasWithdrawOrder(userWallet: UserWallet): Boolean { + val orderExchangeData = tangemPayStorage.getWithdrawOrder(userWallet.walletId) + if (orderExchangeData == null) return false + + val exchangeData = orderExchangeData.exchangeData + val orderData = orderRepository + .getOrderData(userWallet.walletId, orderId = orderExchangeData.orderId).getOrNull() + val withdrawTxHash = orderData?.withdrawTxHash + + if (exchangeData != null && orderData != null && withdrawTxHash != null) { + finalizeWithdraw( + userWallet = userWallet, + withdrawTxHash = withdrawTxHash, + orderId = orderExchangeData.orderId, + exchangeData = exchangeData, + order = orderData, + ) + } + + return orderData?.status == OrderStatus.NEW || orderData?.status == OrderStatus.PROCESSING + } + + override suspend fun pollWithdrawOrderIfNeeds(userWallet: UserWallet): Either { + val storeData = tangemPayStorage.getWithdrawOrder(userWallet.walletId) ?: return Unit.right() + val exchangeData = storeData.exchangeData ?: return Unit.right() + + val orderId = storeData.orderId + val order = orderRepository + .getOrderData(userWalletId = userWallet.walletId, orderId = orderId).getOrNull() + ?: return Unit.right() + + val txHash = order.withdrawTxHash + + if (!txHash.isNullOrEmpty()) { + finalizeWithdraw( + userWallet = userWallet, + withdrawTxHash = txHash, + orderId = storeData.orderId, + exchangeData = exchangeData, + order = order, + ).onLeft { + startWithdrawOrderPolling( + userWallet = userWallet, + orderId = orderId, + storeData = storeData, + exchangeData = exchangeData, + ) + } + } else { + startWithdrawOrderPolling( + userWallet = userWallet, + orderId = orderId, + storeData = storeData, + exchangeData = exchangeData, + ) + } + + return Unit.right() + } + + private suspend fun startWithdrawOrderPolling( + userWallet: UserWallet, + orderId: String, + storeData: TangemPayWithdrawState, + exchangeData: TangemPayWithdrawExchangeState, + ) { + withdrawPollingMutex.withLock { + if (withdrawPollingJobs.containsKey(orderId)) return + + val pollingJob = withdrawPollingScope.launch { + try { + while (isActive && withdrawPollingJobs.containsKey(orderId)) { + delay(duration = 5.seconds) + + val orderData = orderRepository + .getOrderData(userWalletId = userWallet.walletId, orderId = orderId) + orderData.onRight { order -> + if (order.status != OrderStatus.NEW && order.status != OrderStatus.PROCESSING) { + tangemPayStorage.deleteWithdrawOrder(userWallet.walletId) + withdrawPollingJobs.remove(key = orderId) + return@launch + } + val txHash = order.withdrawTxHash + if (!txHash.isNullOrEmpty()) { + finalizeWithdraw( + userWallet = userWallet, + withdrawTxHash = txHash, + orderId = storeData.orderId, + exchangeData = exchangeData, + order = order, + ).onRight { + withdrawPollingJobs.remove(key = orderId) + return@launch + } + } + }.onLeft { error -> + Timber.tag(TAG).e("error ${error.errorCode}") + } + } + } catch (exception: CancellationException) { + throw exception + } catch (exception: Exception) { + Timber.tag(TAG).e(exception) + withdrawPollingMutex.withLock { withdrawPollingJobs.remove(orderId) } + } + } + withdrawPollingJobs[orderId] = pollingJob + } + } + + private suspend fun getAmountInCents(cryptoAmount: BigDecimal, cryptoCurrencyId: CryptoCurrency.RawID): String? { + val fiatRate = getFiatRate(cryptoCurrencyId) ?: return null + val amountInDollars = cryptoAmount.multiply(fiatRate) + val defaultFractionDigits = Currency.getInstance(Locale.US).defaultFractionDigits + return amountInDollars + .setScale(defaultFractionDigits, RoundingMode.HALF_UP) + .movePointRight(defaultFractionDigits) + .longValueExact() + .toString() + } + + private suspend fun getFiatRate(cryptoCurrencyId: CryptoCurrency.RawID): BigDecimal? { + val quotes = quotesFetcher.fetch( + fiatCurrencyId = Currency.getInstance(Locale.US).currencyCode, + currencyId = cryptoCurrencyId.value, + field = QuotesFetcher.Field.PRICE, + ).getOrNull() + + return quotes?.quotes[cryptoCurrencyId.value]?.price + } +} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/usecase/DefaultTangemPayWithdrawUseCase.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/usecase/DefaultTangemPayWithdrawUseCase.kt index 36b85985ad..73f569a219 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/usecase/DefaultTangemPayWithdrawUseCase.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/usecase/DefaultTangemPayWithdrawUseCase.kt @@ -4,14 +4,15 @@ import arrow.core.Either import com.tangem.core.error.UniversalError import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.pay.TangemPayWithdrawExchangeState import com.tangem.domain.pay.WithdrawalResult -import com.tangem.domain.pay.repository.TangemPaySwapRepository +import com.tangem.domain.pay.repository.TangemPayWithdrawRepository import com.tangem.domain.tangempay.TangemPayWithdrawUseCase import java.math.BigDecimal import javax.inject.Inject internal class DefaultTangemPayWithdrawUseCase @Inject constructor( - private val repository: TangemPaySwapRepository, + private val repository: TangemPayWithdrawRepository, ) : TangemPayWithdrawUseCase { override suspend fun invoke( @@ -19,12 +20,14 @@ internal class DefaultTangemPayWithdrawUseCase @Inject constructor( cryptoAmount: BigDecimal, cryptoCurrencyId: CryptoCurrency.RawID, receiverCexAddress: String, + exchangeData: TangemPayWithdrawExchangeState, ): Either { return repository.withdraw( userWallet = userWallet, cryptoAmount = cryptoAmount, receiverAddress = receiverCexAddress, cryptoCurrencyId = cryptoCurrencyId, + exchangeData = exchangeData, ) } } \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/util/WithdrawStateConverter.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/util/WithdrawStateConverter.kt new file mode 100644 index 0000000000..5bcefd3b8e --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/util/WithdrawStateConverter.kt @@ -0,0 +1,22 @@ +package com.tangem.data.pay.util + +import com.tangem.data.pay.entity.WithdrawStoreData +import com.tangem.domain.pay.TangemPayWithdrawExchangeState +import com.tangem.domain.pay.TangemPayWithdrawState +import com.tangem.utils.converter.Converter + +class WithdrawStateConverter : Converter { + + override fun convert(value: WithdrawStoreData): TangemPayWithdrawState = TangemPayWithdrawState( + orderId = value.orderId, + exchangeData = value.exchangeData?.let { exchangeData -> + TangemPayWithdrawExchangeState( + txId = exchangeData.txId, + fromNetwork = exchangeData.fromNetwork, + fromAddress = exchangeData.fromAddress, + payInAddress = exchangeData.payInAddress, + payInExtraId = exchangeData.payInExtraId, + ) + }, + ) +} \ No newline at end of file diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/util/WithdrawStoreDataConverter.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/util/WithdrawStoreDataConverter.kt new file mode 100644 index 0000000000..e46044b735 --- /dev/null +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/util/WithdrawStoreDataConverter.kt @@ -0,0 +1,22 @@ +package com.tangem.data.pay.util + +import com.tangem.data.pay.entity.ExchangeStoreData +import com.tangem.data.pay.entity.WithdrawStoreData +import com.tangem.domain.pay.TangemPayWithdrawState +import com.tangem.utils.converter.Converter + +class WithdrawStoreDataConverter : Converter { + + override fun convert(value: TangemPayWithdrawState): WithdrawStoreData = WithdrawStoreData( + orderId = value.orderId, + exchangeData = value.exchangeData?.let { exchangeData -> + ExchangeStoreData( + txId = exchangeData.txId, + fromNetwork = exchangeData.fromNetwork, + fromAddress = exchangeData.fromAddress, + payInAddress = exchangeData.payInAddress, + payInExtraId = exchangeData.payInExtraId, + ) + }, + ) +} \ No newline at end of file diff --git a/domain/visa/build.gradle.kts b/domain/visa/build.gradle.kts index 3dfad027a7..559774d3e2 100644 --- a/domain/visa/build.gradle.kts +++ b/domain/visa/build.gradle.kts @@ -24,6 +24,7 @@ dependencies { implementation(projects.domain.core) implementation(projects.domain.tokens.models) implementation(projects.domain.wallets.models) + implementation(projects.features.swap.domain) /** Feature API - remove after removing [TangemPayFeatureToggles] */ implementation(projects.features.tangempay.details.api) diff --git a/domain/visa/models/src/main/kotlin/com/tangem/domain/pay/TangemPayWithdrawState.kt b/domain/visa/models/src/main/kotlin/com/tangem/domain/pay/TangemPayWithdrawState.kt new file mode 100644 index 0000000000..a1424c3acd --- /dev/null +++ b/domain/visa/models/src/main/kotlin/com/tangem/domain/pay/TangemPayWithdrawState.kt @@ -0,0 +1,14 @@ +package com.tangem.domain.pay + +data class TangemPayWithdrawState( + val orderId: String, + val exchangeData: TangemPayWithdrawExchangeState?, +) + +data class TangemPayWithdrawExchangeState( + val txId: String, + val fromNetwork: String, + val fromAddress: String, + val payInAddress: String, + val payInExtraId: String?, +) \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderData.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderData.kt new file mode 100644 index 0000000000..42b7001564 --- /dev/null +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/model/OrderData.kt @@ -0,0 +1,6 @@ +package com.tangem.domain.pay.model + +data class OrderData( + val status: OrderStatus, + val withdrawTxHash: String?, +) \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/CustomerOrderRepository.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/CustomerOrderRepository.kt index 9ae1fd414b..09b25edc3a 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/CustomerOrderRepository.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/CustomerOrderRepository.kt @@ -2,12 +2,10 @@ package com.tangem.domain.pay.repository import arrow.core.Either import com.tangem.domain.models.wallet.UserWalletId -import com.tangem.domain.pay.model.OrderStatus +import com.tangem.domain.pay.model.OrderData import com.tangem.domain.visa.error.VisaApiError interface CustomerOrderRepository { - suspend fun getOrderStatus(userWalletId: UserWalletId, orderId: String): Either - - suspend fun hasWithdrawOrder(userWalletId: UserWalletId): Boolean + suspend fun getOrderData(userWalletId: UserWalletId, orderId: String): Either } \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/TangemPaySwapRepository.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/TangemPayWithdrawRepository.kt similarity index 58% rename from domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/TangemPaySwapRepository.kt rename to domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/TangemPayWithdrawRepository.kt index fed66aab7d..18b09d331e 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/TangemPaySwapRepository.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/repository/TangemPayWithdrawRepository.kt @@ -4,15 +4,22 @@ import arrow.core.Either import com.tangem.core.error.UniversalError import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.pay.TangemPayWithdrawExchangeState import com.tangem.domain.pay.WithdrawalResult +import com.tangem.domain.visa.error.VisaApiError import java.math.BigDecimal -interface TangemPaySwapRepository { +interface TangemPayWithdrawRepository { suspend fun withdraw( userWallet: UserWallet, receiverAddress: String, cryptoAmount: BigDecimal, cryptoCurrencyId: CryptoCurrency.RawID, + exchangeData: TangemPayWithdrawExchangeState, ): Either + + suspend fun hasWithdrawOrder(userWallet: UserWallet): Boolean + + suspend fun pollWithdrawOrderIfNeeds(userWallet: UserWallet): Either } \ No newline at end of file diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt index 7ff408d1c1..647e6ad412 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/pay/usecase/TangemPayMainScreenCustomerInfoUseCase.kt @@ -137,13 +137,13 @@ class TangemPayMainScreenCustomerInfoUseCase( userWalletId: UserWalletId, orderId: String, ): Either { - return customerOrderRepository.getOrderStatus(userWalletId, orderId = orderId) + return customerOrderRepository.getOrderData(userWalletId, orderId = orderId) .fold( ifLeft = { error -> error.mapErrorForCustomer().left() }, - ifRight = { orderStatus -> - when (orderStatus) { + ifRight = { orderData -> + when (orderData.status) { // Kyc is passed and user waits for order creation -> no need to get customer info OrderStatus.NEW, OrderStatus.PROCESSING, @@ -154,7 +154,7 @@ class TangemPayMainScreenCustomerInfoUseCase( kycStatus = CustomerInfo.KycStatus.APPROVED, cardInfo = null, ), - orderStatus = orderStatus, + orderStatus = orderData.status, ).right() // Order was created/cancelled -> clear order id and get customer info @@ -164,11 +164,11 @@ class TangemPayMainScreenCustomerInfoUseCase( -> { onboardingRepository.clearOrderId(userWalletId) // If order was cancelled -> start order creation - if (orderStatus == OrderStatus.CANCELED) onboardingRepository.createOrder(userWalletId) + if (orderData.status == OrderStatus.CANCELED) onboardingRepository.createOrder(userWalletId) onboardingRepository.getCustomerInfo(userWalletId = userWalletId) .mapLeft { it.mapErrorForCustomer() } .map { customerInfo -> - MainScreenCustomerInfo(info = customerInfo, orderStatus = orderStatus) + MainScreenCustomerInfo(info = customerInfo, orderStatus = orderData.status) } } } diff --git a/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/TangemPayWithdrawUseCase.kt b/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/TangemPayWithdrawUseCase.kt index e2abfd227f..d98264599f 100644 --- a/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/TangemPayWithdrawUseCase.kt +++ b/domain/visa/src/main/kotlin/com/tangem/domain/tangempay/TangemPayWithdrawUseCase.kt @@ -4,6 +4,7 @@ import arrow.core.Either import com.tangem.core.error.UniversalError import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.wallet.UserWallet +import com.tangem.domain.pay.TangemPayWithdrawExchangeState import com.tangem.domain.pay.WithdrawalResult import java.math.BigDecimal @@ -14,5 +15,6 @@ interface TangemPayWithdrawUseCase { cryptoAmount: BigDecimal, cryptoCurrencyId: CryptoCurrency.RawID, receiverCexAddress: String, + exchangeData: TangemPayWithdrawExchangeState, ): Either } \ No newline at end of file diff --git a/features/swap/domain/build.gradle.kts b/features/swap/domain/build.gradle.kts index b74d66f7e2..02e0992d2b 100644 --- a/features/swap/domain/build.gradle.kts +++ b/features/swap/domain/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { implementation(projects.domain.express.models) implementation(projects.domain.account) implementation(projects.domain.account.status) + implementation(projects.domain.visa.models) implementation(projects.features.swap.domain.api) implementation(projects.features.swap.domain.models) diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index 060390b7a6..cbaa2d6509 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -36,6 +36,7 @@ import com.tangem.domain.models.network.Network import com.tangem.domain.models.quote.QuoteStatus import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.TangemPayWithdrawExchangeState import com.tangem.domain.quotes.QuotesRepository import com.tangem.domain.quotes.multi.MultiQuoteStatusFetcher import com.tangem.domain.tokens.* @@ -1030,6 +1031,7 @@ internal class SwapInteractorImpl @AssistedInject constructor( exchangeData.transaction as? ExpressTransactionModel.CEX ?: return SwapTransactionState.Error.UnknownError if (isTangemPayWithdrawal) { + val networkAddress = currencyToSend.value.networkAddress return SwapTransactionState.TangemPayWithdrawalData( cryptoAmount = amount.value, cryptoCurrencyId = requireNotNull(currencyToSend.currency.id.rawCurrencyId), @@ -1056,6 +1058,13 @@ internal class SwapInteractorImpl @AssistedInject constructor( txExternalId = exchangeDataCex.externalTxId, averageDuration = null, ), + exchangeData = TangemPayWithdrawExchangeState( + txId = exchangeDataCex.txId, + fromNetwork = currencyToSend.currency.network.backendId, + fromAddress = networkAddress?.defaultAddress?.value.orEmpty(), + payInAddress = exchangeData.transaction.txTo, + payInExtraId = exchangeDataCex.txExtraId, + ), ) } diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapTransactionState.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapTransactionState.kt index 4619adf9ab..b1380f6a3a 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapTransactionState.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/models/ui/SwapTransactionState.kt @@ -3,6 +3,7 @@ package com.tangem.feature.swap.domain.models.ui import com.tangem.domain.models.account.Account import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.pay.TangemPayWithdrawExchangeState import com.tangem.domain.transaction.error.SendTransactionError import com.tangem.feature.swap.domain.models.ExpressDataError import com.tangem.feature.swap.domain.models.SwapAmount @@ -31,6 +32,7 @@ sealed class SwapTransactionState { val toAmount: String?, val toAmountValue: BigDecimal?, val storeData: StoreTransactionData, + val exchangeData: TangemPayWithdrawExchangeState, ) : SwapTransactionState() { data class StoreTransactionData( diff --git a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt index a90bd3640a..fb4e74e1ff 100644 --- a/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt +++ b/features/swap/impl/src/main/java/com/tangem/feature/swap/model/SwapModel.kt @@ -1136,6 +1136,7 @@ internal class SwapModel @Inject constructor( cryptoAmount = swapTransactionState.cryptoAmount, cryptoCurrencyId = swapTransactionState.cryptoCurrencyId, receiverCexAddress = swapTransactionState.cexAddress, + exchangeData = swapTransactionState.exchangeData, ) .onLeft { startLoadingQuotesFromLastState() diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt index aa37f99d4a..793549861d 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayDetailsModel.kt @@ -26,8 +26,8 @@ import com.tangem.domain.models.wallet.UserWallet import com.tangem.domain.pay.TangemPayCryptoCurrencyFactory import com.tangem.domain.pay.model.TangemPayCardBalance import com.tangem.domain.pay.model.TangemPayTopUpData -import com.tangem.domain.pay.repository.CustomerOrderRepository import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository +import com.tangem.domain.pay.repository.TangemPayWithdrawRepository import com.tangem.domain.tangempay.TangemPayAnalyticsEvents import com.tangem.domain.visa.model.TangemPayCardFrozenState import com.tangem.domain.visa.model.TangemPayTxHistoryItem @@ -79,7 +79,7 @@ internal class TangemPayDetailsModel @Inject constructor( private val cardDetailsEventListener: CardDetailsEventListener, private val txHistoryUpdateListener: TangemPayTxHistoryUpdateListener, private val tangemPayCryptoCurrencyFactory: TangemPayCryptoCurrencyFactory, - private val orderRepository: CustomerOrderRepository, + private val tangemPayWithdrawRepository: TangemPayWithdrawRepository, private val getUserWalletUseCase: GetUserWalletUseCase, private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase, private val expressTransactionsEventListener: ExpressTransactionsEventListener, @@ -124,6 +124,7 @@ internal class TangemPayDetailsModel @Inject constructor( modelScope.launch { expressTransactionsEventListener.send(ExpressTransactionsEvent.Update) } + subscribeToWithdrawOrder() } fun onPause() { @@ -148,6 +149,14 @@ internal class TangemPayDetailsModel @Inject constructor( .launchIn(modelScope) } + private fun subscribeToWithdrawOrder() { + modelScope.launch { + val userWallet = userWallet ?: getUserWalletUseCase(params.userWalletId).getOrNull() + ?: return@launch + tangemPayWithdrawRepository.pollWithdrawOrderIfNeeds(userWallet) + } + } + override fun onClickPinCode() { analytics.send(TangemPayAnalyticsEvents.PinCodeClicked()) if (!params.config.isPinSet) { @@ -278,24 +287,28 @@ internal class TangemPayDetailsModel @Inject constructor( if (currentBalance == null || depositAddress == null) { showBottomSheetError(TangemPayDetailsErrorType.Withdraw) } else { - modelScope.launch { - val hasActiveWithdrawal = orderRepository.hasWithdrawOrder(userWalletId = params.userWalletId) - if (hasActiveWithdrawal) { - showBottomSheetError(TangemPayDetailsErrorType.WithdrawInProgress) - } else { - val userWallet = userWallet ?: getUserWalletUseCase(params.userWalletId).getOrNull() - val currency = cryptoCurrency ?: userWallet?.let { - tangemPayCryptoCurrencyFactory.create(userWallet = userWallet, chainId = params.config.chainId) - .getOrNull() - } - if (currency != null) { - uiMessageSender.send( - message = TangemPayMessagesFactory.createWithdrawWarning( - onGotItClick = { onConfirmWithdrawal(currency, currentBalance, depositAddress) }, - ), - ) + val userWallet = userWallet ?: getUserWalletUseCase(params.userWalletId).getOrNull() + if (userWallet == null) { + showBottomSheetError(TangemPayDetailsErrorType.Withdraw) + } else { + modelScope.launch { + val hasActiveWithdrawal = tangemPayWithdrawRepository.hasWithdrawOrder(userWallet = userWallet) + if (hasActiveWithdrawal) { + showBottomSheetError(TangemPayDetailsErrorType.WithdrawInProgress) } else { - showBottomSheetError(TangemPayDetailsErrorType.Withdraw) + val currency = cryptoCurrency ?: tangemPayCryptoCurrencyFactory.create( + userWallet = userWallet, + chainId = params.config.chainId, + ).getOrNull() + if (currency != null) { + uiMessageSender.send( + message = TangemPayMessagesFactory.createWithdrawWarning( + onGotItClick = { onConfirmWithdrawal(currency, currentBalance, depositAddress) }, + ), + ) + } else { + showBottomSheetError(TangemPayDetailsErrorType.Withdraw) + } } } }