diff --git a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt index 45734e3796..77c2518104 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/TransactionDomainModule.kt @@ -188,4 +188,12 @@ internal object TransactionDomainModule { ): SignUseCase { return SignUseCase(cardSdkConfigRepository, walletManagersFacade) } + + @Provides + @Singleton + fun provideCreateNFTTransferTransactionUseCase( + transactionRepository: TransactionRepository, + ): CreateNFTTransferTransactionUseCase { + return CreateNFTTransferTransactionUseCase(transactionRepository) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/BigIntegerAdapter.kt b/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/BigIntegerAdapter.kt new file mode 100644 index 0000000000..00fca862ea --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/common/adapter/BigIntegerAdapter.kt @@ -0,0 +1,13 @@ +package com.tangem.datasource.api.common.adapter + +import com.squareup.moshi.FromJson +import com.squareup.moshi.ToJson +import java.math.BigInteger + +internal class BigIntegerAdapter { + @FromJson + fun fromJson(value: String) = BigInteger(value) + + @ToJson + fun toJson(value: BigInteger) = value.toString() +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/di/MoshiModule.kt b/core/datasource/src/main/java/com/tangem/datasource/di/MoshiModule.kt index fad4677392..37ea5507c5 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/di/MoshiModule.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/di/MoshiModule.kt @@ -6,10 +6,7 @@ import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory import com.tangem.blockchain.nft.models.NFTAsset import com.tangem.blockchain.nft.models.NFTCollection import com.tangem.common.json.MoshiJsonConverter -import com.tangem.datasource.api.common.adapter.BigDecimalAdapter -import com.tangem.datasource.api.common.adapter.DateTimeAdapter -import com.tangem.datasource.api.common.adapter.LocalDateAdapter -import com.tangem.datasource.api.common.adapter.addStakeKitEnumFallbackAdapters +import com.tangem.datasource.api.common.adapter.* import com.tangem.datasource.local.config.providers.models.ProviderModel import com.tangem.datasource.local.network.entity.NetworkStatusDM import com.tangem.domain.models.scan.serialization.* @@ -38,6 +35,7 @@ class MoshiModule { .withDefaultValue(ProviderModel.UnsupportedType), ) .add(BigDecimalAdapter()) + .add(BigIntegerAdapter()) .add(LocalDateAdapter()) .add(DateTimeAdapter()) .add(VisaActivationRemoteState.jsonAdapter) @@ -73,6 +71,7 @@ class MoshiModule { val adapters = MoshiJsonConverter.getTangemSdkAdapters() + listOf( BigDecimalAdapter(), + BigIntegerAdapter(), WalletDerivedKeysMapAdapter(), ScanResponseDerivedKeysMapAdapter(), ByteArrayKeyAdapter(), @@ -100,6 +99,7 @@ class MoshiModule { adapters = MoshiJsonConverter.getTangemSdkAdapters() + listOf( BigDecimalAdapter(), + BigIntegerAdapter(), WalletDerivedKeysMapAdapter(), ScanResponseDerivedKeysMapAdapter(), ByteArrayKeyAdapter(), diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkAssetConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkAssetConverter.kt index db95b8ce76..be1c8f0aeb 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkAssetConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkAssetConverter.kt @@ -4,14 +4,17 @@ import com.tangem.domain.models.StatusSource import com.tangem.domain.nft.models.NFTAsset import com.tangem.domain.nft.models.NFTSalePrice import com.tangem.domain.tokens.model.Network -import com.tangem.utils.converter.Converter +import com.tangem.utils.converter.TwoWayConverter import com.tangem.blockchain.nft.models.NFTAsset as SdkNFTAsset -object NFTSdkAssetConverter : Converter, NFTAsset> { +object NFTSdkAssetConverter : TwoWayConverter, NFTAsset> { override fun convert(value: Pair): NFTAsset { val (network, asset) = value val assetId = NFTSdkAssetIdentifierConverter.convert(asset.identifier) val collectionId = NFTSdkCollectionIdentifierConverter.convert(asset.collectionIdentifier) + val salePrice = asset.salePrice?.let { NFTSdkAssetSalePriceConverter(assetId).convert(it) } + ?: NFTSalePrice.Empty(assetId) + return NFTAsset( id = assetId, collectionId = collectionId, @@ -20,13 +23,9 @@ object NFTSdkAssetConverter : Converter, NFTAsset> { owner = asset.owner, name = asset.name, description = asset.description, - salePrice = asset.salePrice?.let { - NFTSalePrice.Value( - assetId = assetId, - value = it.value, - symbol = it.symbol, - ) - } ?: NFTSalePrice.Empty(assetId = assetId), + amount = asset.amount, + decimals = asset.decimals, + salePrice = salePrice, rarity = asset.rarity?.let { NFTAsset.Rarity( rank = it.rank, @@ -48,4 +47,42 @@ object NFTSdkAssetConverter : Converter, NFTAsset> { source = StatusSource.CACHE, ) } + + override fun convertBack(value: NFTAsset): Pair { + val assetId = NFTSdkAssetIdentifierConverter.convertBack(value.id) + val collectionId = NFTSdkCollectionIdentifierConverter.convertBack(value.collectionId) + val salePrice = (value.salePrice as? NFTSalePrice.Value)?.let { + NFTSdkAssetSalePriceConverter(value.id).convertBack(it) + } + return value.network to SdkNFTAsset( + identifier = assetId, + collectionIdentifier = collectionId, + blockchainId = value.network.id.value, + contractType = value.contractType, + owner = value.owner, + name = value.name, + description = value.description, + amount = value.amount, + decimals = value.decimals, + salePrice = salePrice, + rarity = value.rarity?.let { + SdkNFTAsset.Rarity( + rank = it.rank, + label = it.label, + ) + }, + media = value.media?.let { + SdkNFTAsset.Media( + url = it.url, + mimetype = it.mimetype, + ) + }, + traits = value.traits.map { + SdkNFTAsset.Trait( + name = it.name, + value = it.value, + ) + }, + ) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkAssetIdentifierConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkAssetIdentifierConverter.kt index 090e711d37..bd93af8f19 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkAssetIdentifierConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkAssetIdentifierConverter.kt @@ -9,6 +9,7 @@ object NFTSdkAssetIdentifierConverter : TwoWayConverter NFTAsset.Identifier.EVM( tokenId = value.tokenId, tokenAddress = value.tokenAddress, + contractType = NFTAsset.Identifier.EVM.ContractType.valueOf(value.contractType.name), ) is SdkNFTAsset.Identifier.TON -> NFTAsset.Identifier.TON( tokenAddress = value.tokenAddress, @@ -24,6 +25,7 @@ object NFTSdkAssetIdentifierConverter : TwoWayConverter SdkNFTAsset.Identifier.EVM( tokenId = value.tokenId, tokenAddress = value.tokenAddress, + contractType = SdkNFTAsset.Identifier.EVM.ContractType.valueOf(value.contractType.name), ) is NFTAsset.Identifier.TON -> SdkNFTAsset.Identifier.TON( tokenAddress = value.tokenAddress, diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkAssetSalePriceConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkAssetSalePriceConverter.kt new file mode 100644 index 0000000000..252753005a --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/local/nft/converter/NFTSdkAssetSalePriceConverter.kt @@ -0,0 +1,25 @@ +package com.tangem.datasource.local.nft.converter + +import com.tangem.domain.nft.models.NFTAsset +import com.tangem.domain.nft.models.NFTSalePrice +import com.tangem.utils.converter.TwoWayConverter +import com.tangem.blockchain.nft.models.NFTAsset.SalePrice as SDKSalePrice + +internal class NFTSdkAssetSalePriceConverter( + private val assetId: NFTAsset.Identifier, +) : TwoWayConverter { + override fun convert(value: SDKSalePrice): NFTSalePrice.Value { + return NFTSalePrice.Value( + assetId = assetId, + value = value.value, + symbol = value.symbol, + ) + } + + override fun convertBack(value: NFTSalePrice.Value): SDKSalePrice { + return SDKSalePrice( + symbol = value.symbol, + value = value.value, + ) + } +} \ 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 38c3cb363b..e76e34479a 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 @@ -17,6 +17,7 @@ import com.tangem.blockchain.common.* import com.tangem.blockchain.common.smartcontract.SmartContractCallData import com.tangem.blockchain.common.smartcontract.SmartContractCallDataProviderFactory import com.tangem.blockchain.common.transaction.Fee +import com.tangem.blockchain.nft.models.NFTAsset import com.tangem.blockchainsdk.utils.fromNetworkId import com.tangem.datasource.local.walletmanager.WalletManagersStore import com.tangem.domain.tokens.model.CryptoCurrency @@ -30,6 +31,7 @@ import timber.log.Timber import java.math.BigDecimal import java.math.BigInteger +@Suppress("LargeClass") internal class DefaultTransactionRepository( private val walletManagersFacade: WalletManagersFacade, private val walletManagersStore: WalletManagersStore, @@ -144,6 +146,53 @@ internal class DefaultTransactionRepository( ) } + override suspend fun createNFTTransferTransaction( + ownerAddress: String, + nftAsset: NFTAsset, + fee: Fee?, + memo: String?, + destinationAddress: String, + userWalletId: UserWalletId, + network: Network, + ): TransactionData.Uncompiled = withContext(coroutineDispatcherProvider.io) { + val blockchain = Blockchain.fromId(network.id.value) + + val nftTransferCallData = SmartContractCallDataProviderFactory.getNFTTransferCallData( + destinationAddress = destinationAddress, + ownerAddress = ownerAddress, + nftAsset = nftAsset, + blockchain = blockchain, + ) + + val extras = if (nftTransferCallData != null) { + createTransactionDataExtras( + callData = nftTransferCallData, + network = network, + nonce = null, + gasLimit = null, + ) + } else { + null + } + + return@withContext createTransaction( + amount = Amount( + value = nftAsset.amount?.toBigDecimal() ?: error("Invalid amount"), + token = Token( + symbol = blockchain.currency, + contractAddress = "", + decimals = nftAsset.decimals ?: error("Invalid decimals"), + ), + ), + fee = fee, + memo = memo, + destination = destinationAddress, + userWalletId = userWalletId, + network = network, + txExtras = extras, + ) + } + override suspend fun validateTransaction( amount: Amount, fee: Fee?, diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/serialization/BigIntegerSerializer.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/serialization/BigIntegerSerializer.kt new file mode 100644 index 0000000000..93394b1e0c --- /dev/null +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/serialization/BigIntegerSerializer.kt @@ -0,0 +1,21 @@ +package com.tangem.domain.core.serialization + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import java.math.BigInteger + +object BigIntegerSerializer : KSerializer { + + override val descriptor = PrimitiveSerialDescriptor(serialName = "BigInteger", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: BigInteger) { + encoder.encodeString(value.toString()) + } + + override fun deserialize(decoder: Decoder): BigInteger { + return BigInteger(decoder.decodeString()) + } +} \ No newline at end of file diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/serialization/SerializedBigInteger.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/serialization/SerializedBigInteger.kt new file mode 100644 index 0000000000..8fbbda7eea --- /dev/null +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/serialization/SerializedBigInteger.kt @@ -0,0 +1,6 @@ +package com.tangem.domain.core.serialization + +import kotlinx.serialization.Serializable +import java.math.BigInteger + +typealias SerializedBigInteger = @Serializable(with = BigIntegerSerializer::class) BigInteger \ No newline at end of file diff --git a/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTAsset.kt b/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTAsset.kt index 2c75ead2f4..2658873319 100644 --- a/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTAsset.kt +++ b/domain/nft/models/src/main/kotlin/com/tangem/domain/nft/models/NFTAsset.kt @@ -1,5 +1,6 @@ package com.tangem.domain.nft.models +import com.tangem.domain.core.serialization.SerializedBigInteger import com.tangem.domain.models.StatusSource import com.tangem.domain.tokens.model.Network import kotlinx.serialization.Serializable @@ -13,6 +14,8 @@ data class NFTAsset( val owner: String?, val name: String?, val description: String?, + val amount: SerializedBigInteger?, + val decimals: Int?, val salePrice: NFTSalePrice, val rarity: Rarity?, val media: Media?, @@ -44,10 +47,17 @@ data class NFTAsset( @Serializable data class EVM( - val tokenId: String, val tokenAddress: String, + val tokenId: SerializedBigInteger, + val contractType: ContractType, ) : Identifier() { - override val stringValue: String = "${tokenAddress}_$tokenId" + override val stringValue: String = "${contractType}_$tokenId" + + enum class ContractType { + ERC1155, + ERC721, + Unknown, + } } @Serializable diff --git a/domain/transaction/build.gradle.kts b/domain/transaction/build.gradle.kts index dbae9c5373..30cedbb7c9 100644 --- a/domain/transaction/build.gradle.kts +++ b/domain/transaction/build.gradle.kts @@ -14,6 +14,7 @@ dependencies { implementation(projects.core.utils) implementation(projects.core.ui) + implementation(projects.core.datasource) /** Tangem SDKs */ implementation(tangemDeps.card.core) @@ -22,10 +23,11 @@ dependencies { } implementation(tangemDeps.blockchain) - implementation(projects.domain.models) - implementation(projects.domain.legacy) implementation(projects.libs.blockchainSdk) implementation(projects.libs.crypto) + + implementation(projects.domain.models) + implementation(projects.domain.legacy) implementation(projects.domain.wallets.models) implementation(projects.domain.tokens) implementation(projects.domain.tokens.models) 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 5b7499c85a..2149911b09 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 @@ -5,6 +5,7 @@ import com.tangem.blockchain.common.smartcontract.SmartContractCallData import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.TransactionSendResult import com.tangem.blockchain.common.transaction.TransactionsSendResult +import com.tangem.blockchain.nft.models.NFTAsset import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.Network import com.tangem.domain.wallets.models.UserWalletId @@ -34,6 +35,17 @@ interface TransactionRepository { network: Network, ): TransactionData.Uncompiled + @Suppress("LongParameterList") + suspend fun createNFTTransferTransaction( + ownerAddress: String, + nftAsset: NFTAsset, + fee: Fee?, + memo: String?, + destinationAddress: String, + userWalletId: UserWalletId, + network: Network, + ): TransactionData.Uncompiled + @Suppress("LongParameterList") suspend fun createApprovalTransaction( amount: Amount, diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateApprovalTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateApprovalTransactionUseCase.kt index 903025cb16..d15b407307 100644 --- a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateApprovalTransactionUseCase.kt +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateApprovalTransactionUseCase.kt @@ -34,4 +34,23 @@ class CreateApprovalTransactionUseCase( fee = fee, ) } + + @Suppress("LongParameterList") + suspend operator fun invoke( + cryptoCurrency: CryptoCurrency.Token, + userWalletId: UserWalletId, + amount: BigDecimal?, + contractAddress: String, + spenderAddress: String, + ) = Either.catch { + transactionRepository.createApprovalTransaction( + amount = BigDecimal.ZERO.convertToSdkAmount(cryptoCurrency), + approvalAmount = amount?.convertToSdkAmount(cryptoCurrency), + contractAddress = contractAddress, + spenderAddress = spenderAddress, + userWalletId = userWalletId, + network = cryptoCurrency.network, + fee = null, + ) + } } \ No newline at end of file diff --git a/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateNFTTransferTransactionUseCase.kt b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateNFTTransferTransactionUseCase.kt new file mode 100644 index 0000000000..9cf4aafd1c --- /dev/null +++ b/domain/transaction/src/main/java/com/tangem/domain/transaction/usecase/CreateNFTTransferTransactionUseCase.kt @@ -0,0 +1,59 @@ +package com.tangem.domain.transaction.usecase + +import arrow.core.Either +import com.tangem.blockchain.common.transaction.Fee +import com.tangem.blockchain.nft.models.NFTAsset +import com.tangem.domain.tokens.model.Network +import com.tangem.domain.transaction.TransactionRepository +import com.tangem.domain.wallets.models.UserWalletId + +class CreateNFTTransferTransactionUseCase( + private val transactionRepository: TransactionRepository, +) { + /** + * [REDACTED_TODO_COMMENT] + */ + @Suppress("LongParameterList") + suspend operator fun invoke( + ownerAddress: String, + nftAsset: NFTAsset, + fee: Fee, + memo: String?, + destinationAddress: String, + userWalletId: UserWalletId, + network: Network, + ) = Either.catch { + transactionRepository.createNFTTransferTransaction( + ownerAddress = ownerAddress, + nftAsset = nftAsset, + destinationAddress = destinationAddress, + fee = fee, + memo = memo, + userWalletId = userWalletId, + network = network, + ) + } + + /** + * [REDACTED_TODO_COMMENT] + */ + @Suppress("LongParameterList") + suspend operator fun invoke( + ownerAddress: String, + nftAsset: NFTAsset, + memo: String?, + destinationAddress: String, + userWalletId: UserWalletId, + network: Network, + ) = Either.catch { + transactionRepository.createNFTTransferTransaction( + ownerAddress = ownerAddress, + nftAsset = nftAsset, + destinationAddress = destinationAddress, + fee = null, + memo = memo, + userWalletId = userWalletId, + network = network, + ) + } +} \ No newline at end of file diff --git a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/factory/NFTDetailsUMFactory.kt b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/factory/NFTDetailsUMFactory.kt index afe6f46072..9a8e41a51f 100644 --- a/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/factory/NFTDetailsUMFactory.kt +++ b/features/nft/impl/src/main/kotlin/com/tangem/features/nft/details/entity/factory/NFTDetailsUMFactory.kt @@ -89,7 +89,7 @@ internal class NFTDetailsUMFactory( ), NFTAssetUM.BlockItem( title = resourceReference(R.string.nft_details_token_id), - value = id.tokenId, + value = id.tokenId.toString(), ), NFTAssetUM.BlockItem( title = resourceReference(R.string.nft_details_chain), diff --git a/features/send-v2/impl/build.gradle.kts b/features/send-v2/impl/build.gradle.kts index 9629c4825b..dcd5503302 100644 --- a/features/send-v2/impl/build.gradle.kts +++ b/features/send-v2/impl/build.gradle.kts @@ -26,6 +26,7 @@ dependencies { implementation(projects.core.analytics) implementation(projects.core.configToggles) implementation(projects.core.navigation) + implementation(projects.core.datasource) api(projects.core.pagination) /** Tangem SDK */ diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt index 8b0a2cad19..0cdba71372 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/model/SendConfirmModel.kt @@ -433,6 +433,7 @@ internal class SendConfirmModel @Inject constructor( ).filter { it.second is CommonSendRoute.Confirm }.onEach { (state, _) -> val amountUM = state.amountUM as? AmountState.Data val confirmUM = state.confirmUM + val isReadyToSend = confirmUM is ConfirmUM.Content && !confirmUM.isSending params.callback.onResult( state.copy( navigationUM = NavigationUM.Content( @@ -462,9 +463,9 @@ internal class SendConfirmModel @Inject constructor( } else -> resourceReference(R.string.common_send) }, - iconResId = R.drawable.ic_tangem_24.takeIf { confirmUM is ConfirmUM.Content }, + iconResId = R.drawable.ic_tangem_24.takeIf { isReadyToSend }, isEnabled = confirmUM.isPrimaryButtonEnabled, - isHapticClick = confirmUM is ConfirmUM.Content && !confirmUM.isSending, + isHapticClick = isReadyToSend, onClick = { when (confirmUM) { is ConfirmUM.Success -> appRouter.pop() diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt index 1adf375c6e..19424360b9 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/confirm/model/NFTSendConfirmModel.kt @@ -2,6 +2,7 @@ package com.tangem.features.send.v2.sendnft.confirm.model import android.os.SystemClock import arrow.core.getOrElse +import com.tangem.blockchain.common.TransactionData import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.di.ModelScoped @@ -12,9 +13,14 @@ import com.tangem.core.navigation.share.ShareManager import com.tangem.core.navigation.url.UrlOpener import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.wrappedList +import com.tangem.datasource.local.nft.converter.NFTSdkAssetConverter import com.tangem.domain.settings.IsSendTapHelpEnabledUseCase import com.tangem.domain.settings.NeverShowTapHelpUseCase +import com.tangem.domain.transaction.usecase.CreateNFTTransferTransactionUseCase +import com.tangem.domain.transaction.usecase.SendTransactionUseCase +import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase import com.tangem.features.send.v2.common.CommonSendRoute +import com.tangem.features.send.v2.common.SendBalanceUpdater import com.tangem.features.send.v2.common.ui.state.ConfirmUM import com.tangem.features.send.v2.common.ui.state.NavigationUM import com.tangem.features.send.v2.impl.R @@ -37,6 +43,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.transformer.update import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch +import timber.log.Timber import java.math.BigDecimal import javax.inject.Inject @@ -49,21 +56,32 @@ internal class NFTSendConfirmModel @Inject constructor( private val appRouter: AppRouter, private val isSendTapHelpEnabledUseCase: IsSendTapHelpEnabledUseCase, private val neverShowTapHelpUseCase: NeverShowTapHelpUseCase, + private val createNFTTransferTransactionUseCase: CreateNFTTransferTransactionUseCase, + private val sendTransactionUseCase: SendTransactionUseCase, + private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, private val notificationsUpdateTrigger: NotificationsUpdateTrigger, private val sendFeeCheckReloadTrigger: SendFeeCheckReloadTrigger, private val sendFeeCheckReloadListener: SendFeeCheckReloadListener, private val urlOpener: UrlOpener, private val shareManager: ShareManager, private val analyticsEventHandler: AnalyticsEventHandler, + sendBalanceUpdaterFactory: SendBalanceUpdater.Factory, ) : Model(), NFTSendConfirmClickIntents { private val params: NFTSendConfirmComponent.Params = paramsContainer.require() + private val userWallet = params.userWallet private val cryptoCurrencyStatus = params.cryptoCurrencyStatus + private val cryptoCurrency = cryptoCurrencyStatus.currency private val _uiState = MutableStateFlow(params.state) val uiState = _uiState.asStateFlow() + private val sendBalanceUpdater = sendBalanceUpdaterFactory.create( + cryptoCurrency = cryptoCurrency, + userWallet = userWallet, + ) + private val destinationUM get() = uiState.value.destinationUM as? DestinationUM.Content private val feeUM @@ -191,34 +209,63 @@ internal class NFTSendConfirmModel @Inject constructor( } private fun verifyAndSendTransaction() { - // TODO: + val destination = destinationUM?.addressTextField?.value ?: return + val memo = destinationUM?.memoTextField?.value + val fee = feeSelectorUM?.selectedFee ?: return + val ownerAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value ?: return + + val sdkNFTAsset = NFTSdkAssetConverter.convertBack(params.nftAsset) + + modelScope.launch { + createNFTTransferTransactionUseCase( + ownerAddress = ownerAddress, + nftAsset = sdkNFTAsset.second, + fee = fee, + memo = memo, + destinationAddress = destination, + userWalletId = userWallet.walletId, + network = cryptoCurrency.network, + ).fold( + ifLeft = { error -> + Timber.e(error) + _uiState.update(NFTSendConfirmSendingStateTransformer(isSending = false)) + // alertFactory.getGenericErrorState { + // onFailedTxEmailClick(error.localizedMessage.orEmpty()) + // } + }, + ifRight = { txData -> + sendTransaction(txData) + }, + ) + } } - // private suspend fun sendTransaction(txData: TransactionData.Uncompiled) { - // val result = sendTransactionUseCase( - // txData = txData, - // userWallet = userWallet, - // network = cryptoCurrency.network, - // ) - // - // _uiState.update(NFTSendConfirmSendingStateTransformer(isSending = false)) - // - // result.fold( - // ifLeft = { error -> - // // alertFactory.getSendTransactionErrorState( - // // error = error, - // // popBack = appRouter::pop, - // // onFailedTxEmailClick = ::onFailedTxEmailClick, - // // ) - // analyticsEventHandler.send(SendAnalyticEvents.TransactionError(cryptoCurrency.symbol)) - // }, - // ifRight = { - // updateTransactionStatus(txData) - // sendBalanceUpdater.scheduleUpdates() - // // sendAnalyticHelper.sendSuccessAnalytics(cryptoCurrency, uiState.value) - // }, - // ) - // } + private suspend fun sendTransaction(txData: TransactionData.Uncompiled) { + val result = sendTransactionUseCase( + txData = txData, + userWallet = params.userWallet, + network = params.cryptoCurrencyStatus.currency.network, + ) + + _uiState.update(NFTSendConfirmSendingStateTransformer(isSending = false)) + + result.fold( + ifLeft = { error -> + Timber.e(error.toString()) + // alertFactory.getSendTransactionErrorState( + // error = error, + // popBack = appRouter::pop, + // onFailedTxEmailClick = ::onFailedTxEmailClick, + // ) + // analyticsEventHandler.send(SendAnalyticEvents.TransactionError(cryptoCurrency.symbol)) + }, + ifRight = { + updateTransactionStatus(txData) + sendBalanceUpdater.scheduleUpdates() + // sendAnalyticHelper.sendSuccessAnalytics(cryptoCurrency, uiState.value) + }, + ) + } private fun subscribeOnCheckFeeResultUpdates() { sendFeeCheckReloadListener.checkReloadResultFlow.onEach { isFeeResultSuccess -> @@ -232,13 +279,13 @@ internal class NFTSendConfirmModel @Inject constructor( }.launchIn(modelScope) } - // private suspend fun updateTransactionStatus(txData: TransactionData.Uncompiled) { - // val txUrl = getExplorerTransactionUrlUseCase( - // userWalletId = userWallet.walletId, - // network = cryptoCurrency.network, - // ).getOrElse { "" } - // _uiState.update(NFTSendConfirmSentStateTransformer(txData, txUrl)) - // } + private fun updateTransactionStatus(txData: TransactionData.Uncompiled) { + val txUrl = getExplorerTransactionUrlUseCase( + txHash = txData.hash.orEmpty(), + networkId = cryptoCurrency.network.id, + ).getOrElse { "" } + _uiState.update(NFTSendConfirmSentStateTransformer(txData, txUrl)) + } private fun updateConfirmNotifications() { modelScope.launch { @@ -272,6 +319,7 @@ internal class NFTSendConfirmModel @Inject constructor( transform = { state, route -> state to route }, ).onEach { (state, _) -> val confirmUM = state.confirmUM + val isReadyToSend = confirmUM is ConfirmUM.Content && !confirmUM.isSending params.callback.onResult( state.copy( navigationUM = NavigationUM.Content( @@ -301,9 +349,9 @@ internal class NFTSendConfirmModel @Inject constructor( } else -> resourceReference(R.string.common_send) }, - iconResId = R.drawable.ic_tangem_24.takeIf { confirmUM is ConfirmUM.Content }, + iconResId = R.drawable.ic_tangem_24.takeIf { isReadyToSend }, isEnabled = confirmUM.isPrimaryButtonEnabled, - isHapticClick = confirmUM is ConfirmUM.Content && !confirmUM.isSending, + isHapticClick = isReadyToSend, onClick = { when (confirmUM) { is ConfirmUM.Success -> appRouter.pop() diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/model/NFTSendModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/model/NFTSendModel.kt index 65d322cf3f..71fb3be509 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/model/NFTSendModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/sendnft/model/NFTSendModel.kt @@ -3,11 +3,13 @@ package com.tangem.features.send.v2.sendnft.model import androidx.compose.runtime.Stable import arrow.core.Either import arrow.core.getOrElse +import arrow.core.left import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.navigation.Router +import com.tangem.datasource.local.nft.converter.NFTSdkAssetConverter import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.common.util.cardTypesResolver @@ -17,6 +19,8 @@ import com.tangem.domain.tokens.GetFeePaidCryptoCurrencyStatusSyncUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.transaction.error.GetFeeError +import com.tangem.domain.transaction.usecase.CreateNFTTransferTransactionUseCase +import com.tangem.domain.transaction.usecase.GetFeeUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.features.send.v2.api.NFTSendComponent @@ -52,6 +56,8 @@ internal class NFTSendModel @Inject constructor( private val getCryptoCurrenciesUseCase: GetCryptoCurrenciesUseCase, private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase, private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, + private val createNFTTransferTransactionUseCase: CreateNFTTransferTransactionUseCase, + private val getFeeUseCase: GetFeeUseCase, ) : Model(), SendNFTComponentCallback { val params: NFTSendComponent.Params = paramsContainer.require() @@ -92,20 +98,29 @@ internal class NFTSendModel @Inject constructor( } suspend fun loadFee(): Either { - TODO() - // val destinationUM = uiState.value.destinationUM as? DestinationUM.Content ?: error("Invalid destination") - // val ownerAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value - // ?: error("Invalid owner address") - // val enteredDestinationAddress = destinationUM.addressTextField.value - // val nftAsset = null // NFTSdkAssetConverter.convertBack(params.nftAsset).second + val destinationUM = uiState.value.destinationUM as? DestinationUM.Content ?: error("Invalid destination") + val ownerAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value + ?: error("Invalid owner address") + val enteredDestinationAddress = destinationUM.addressTextField.value + val enteredMemo = destinationUM.memoTextField?.value + val nftAsset = NFTSdkAssetConverter.convertBack(params.nftAsset).second - // getNFTTransferFeeUseCase( - // ownerAddress = ownerAddress, - // destinationAddress = enteredDestinationAddress, - // nftAsset = nftAsset, - // userWallet = userWallet, - // cryptoCurrency = cryptoCurrency, - // ) + val nftSendTransaction = createNFTTransferTransactionUseCase( + ownerAddress = ownerAddress, + nftAsset = nftAsset, + memo = enteredMemo, + destinationAddress = enteredDestinationAddress, + userWalletId = userWallet.walletId, + network = cryptoCurrency.network, + ).getOrElse { + return GetFeeError.DataError(it).left() + } + + return getFeeUseCase( + transactionData = nftSendTransaction, + network = cryptoCurrency.network, + userWallet = userWallet, + ) } private fun initAppCurrency() { diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt index 3bbd7b2f6e..0acd1468a2 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingFeeTransactionLoader.kt @@ -192,7 +192,6 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor( cryptoCurrency = tokenCurrency, userWalletId = userWallet.walletId, amount = amount, - fee = null, contractAddress = tokenCurrency.contractAddress, spenderAddress = validatorAddress, ).getOrElse { diff --git a/gradle/tangem_dependencies.toml b/gradle/tangem_dependencies.toml index a0bc01da42..ac75068c2a 100644 --- a/gradle/tangem_dependencies.toml +++ b/gradle/tangem_dependencies.toml @@ -5,7 +5,7 @@ # https://github.com/tangem/tangem-sdk-android/ # https://github.com/tangem/vico -tangemBlockchainSdk = "develop-1042" +tangemBlockchainSdk = "develop-1046" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds tangemCardSdk = "develop-455" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^