diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index ce371a36e9..ac3eeb6f5d 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -13,8 +13,10 @@
+
+
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/di/CustomTokenInteractorModule.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/di/CustomTokenInteractorModule.kt
index 236a990d22..4ae3926d84 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/di/CustomTokenInteractorModule.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/di/CustomTokenInteractorModule.kt
@@ -1,7 +1,6 @@
package com.tangem.tap.features.customtoken.impl.di
import com.tangem.datasource.api.tangemTech.TangemTechApi
-import com.tangem.lib.crypto.DerivationManager
import com.tangem.tap.features.customtoken.impl.data.DefaultCustomTokenRepository
import com.tangem.tap.features.customtoken.impl.domain.CustomTokenInteractor
import com.tangem.tap.features.customtoken.impl.domain.DefaultCustomTokenInteractor
@@ -26,7 +25,6 @@ internal object CustomTokenInteractorModule {
tangemTechApi: TangemTechApi,
appCoroutineDispatcherProvider: AppCoroutineDispatcherProvider,
reduxStateHolder: AppStateHolder,
- derivationManager: DerivationManager,
): CustomTokenInteractor {
return DefaultCustomTokenInteractor(
featureRepository = DefaultCustomTokenRepository(
@@ -34,7 +32,6 @@ internal object CustomTokenInteractorModule {
dispatchers = appCoroutineDispatcherProvider,
reduxStateHolder = reduxStateHolder,
),
- derivationManager = derivationManager,
reduxStateHolder = reduxStateHolder,
)
}
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/CustomTokenInteractor.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/CustomTokenInteractor.kt
index 1b5b0fa9cf..3b70cf9f06 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/CustomTokenInteractor.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/CustomTokenInteractor.kt
@@ -1,8 +1,8 @@
package com.tangem.tap.features.customtoken.impl.domain
import com.tangem.blockchain.common.Blockchain
+import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
-import com.tangem.tap.features.wallet.models.Currency
/**
* Custom token interactor
@@ -14,6 +14,6 @@ interface CustomTokenInteractor {
/** Find token by [address] and [blockchain] */
suspend fun findToken(address: String, blockchain: Blockchain): FoundToken
- /** Save token [currency] with contact address [address] */
- suspend fun saveToken(currency: Currency, address: String)
+ /** Save token [customCurrency] */
+ suspend fun saveToken(customCurrency: CustomCurrency)
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt
index c161e1f7ea..a92ac13e11 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/domain/DefaultCustomTokenInteractor.kt
@@ -1,34 +1,40 @@
package com.tangem.tap.features.customtoken.impl.domain
import com.tangem.blockchain.common.Blockchain
+import com.tangem.common.CompletionResult
+import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.guard
+import com.tangem.common.extensions.toMapKey
import com.tangem.common.flatMap
+import com.tangem.crypto.hdWallet.DerivationPath
+import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.extensions.toNetworkId
+import com.tangem.domain.common.util.hasDerivation
+import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.domain.models.scan.ScanResponse
-import com.tangem.lib.crypto.DerivationManager
-import com.tangem.lib.crypto.models.Currency.NativeToken
-import com.tangem.lib.crypto.models.Currency.NonNativeToken
+import com.tangem.operations.derivation.ExtendedPublicKeysMap
+import com.tangem.tap.*
+import com.tangem.tap.common.extensions.dispatchDebugErrorNotification
+import com.tangem.tap.common.extensions.dispatchOnMain
+import com.tangem.tap.common.redux.global.GlobalAction
+import com.tangem.tap.domain.TapError
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
+import com.tangem.tap.features.tokens.legacy.redux.TokensMiddleware
import com.tangem.tap.features.wallet.models.Currency
import com.tangem.tap.proxy.AppStateHolder
-import com.tangem.tap.scope
-import com.tangem.tap.userWalletsListManager
-import com.tangem.tap.walletCurrenciesManager
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.delay
import timber.log.Timber
/**
* Default implementation of custom token interactor
*
* @property featureRepository feature repository
- * @property derivationManager derivation manager
* @property reduxStateHolder redux state holder
*
[REDACTED_AUTHOR]
*/
class DefaultCustomTokenInteractor(
private val featureRepository: CustomTokenRepository,
- private val derivationManager: DerivationManager,
private val reduxStateHolder: AppStateHolder,
) : CustomTokenInteractor {
@@ -39,58 +45,111 @@ class DefaultCustomTokenInteractor(
)
}
- override suspend fun saveToken(currency: Currency, address: String) {
- val hasDerivation = derivationManager.hasDerivation(
- networkId = currency.blockchain.toNetworkId(),
- derivationPath = requireNotNull(currency.derivationPath),
- )
+ override suspend fun saveToken(customCurrency: CustomCurrency) {
+ val scanResponse = reduxStateHolder.scanResponse ?: return
- if (!hasDerivation) {
- derivationManager.deriveMissingBlockchains(
- when (currency) {
- is Currency.Blockchain -> NativeToken(
- id = requireNotNull(currency.coinId),
- name = currency.currencyName,
- symbol = currency.currencySymbol,
- networkId = currency.blockchain.toNetworkId(),
- )
-
- is Currency.Token -> NonNativeToken(
- id = requireNotNull(currency.coinId),
- name = currency.currencyName,
- symbol = currency.currencySymbol,
- networkId = currency.blockchain.toNetworkId(),
- contractAddress = address,
- decimalCount = currency.decimals,
- )
- },
- )
+ val currency = Currency.fromCustomCurrency(customCurrency)
+ val isNeedToDerive = isNeedToDerive(scanResponse, currency)
+ if (isNeedToDerive) {
+ deriveMissingBlockchains(scanResponse = scanResponse, currencyList = listOf(currency)) {
+ submitAdd(scanResponse = it, currency = currency)
+ }
+ } else {
+ submitAdd(scanResponse, currency)
}
-
- submitAdd(
- scanResponse = requireNotNull(reduxStateHolder.scanResponse),
- currency = currency,
- )
}
- private fun submitAdd(scanResponse: ScanResponse, currency: Currency) {
+ private fun isNeedToDerive(scanResponse: ScanResponse, currency: Currency): Boolean {
+ return currency.derivationPath?.let { !scanResponse.hasDerivation(currency.blockchain, it) } ?: false
+ }
+
+ private suspend fun deriveMissingBlockchains(
+ scanResponse: ScanResponse,
+ currencyList: List,
+ onSuccess: suspend (ScanResponse) -> Unit,
+ ) {
+ val derivationDataList = listOfNotNull(
+ getDerivations(EllipticCurve.Secp256k1, scanResponse, currencyList),
+ getDerivations(EllipticCurve.Ed25519, scanResponse, currencyList),
+ )
+
+ val derivations = derivationDataList.associate(TokensMiddleware.DerivationData::derivations)
+ if (derivations.isEmpty()) {
+ onSuccess(scanResponse)
+ return
+ }
+
+ when (val result = tangemSdkManager.derivePublicKeys(cardId = null, derivations = derivations)) {
+ is CompletionResult.Success -> {
+ val newDerivedKeys = result.data.entries
+ val oldDerivedKeys = scanResponse.derivedKeys
+
+ val walletKeys = (newDerivedKeys.keys + oldDerivedKeys.keys).toSet()
+
+ val updatedDerivedKeys = walletKeys.associateWith { walletKey ->
+ val oldDerivations = ExtendedPublicKeysMap(oldDerivedKeys[walletKey] ?: emptyMap())
+ val newDerivations = newDerivedKeys[walletKey] ?: ExtendedPublicKeysMap(emptyMap())
+ ExtendedPublicKeysMap(oldDerivations + newDerivations)
+ }
+
+ val updatedScanResponse = scanResponse.copy(derivedKeys = updatedDerivedKeys)
+ store.dispatchOnMain(GlobalAction.SaveScanResponse(updatedScanResponse))
+ delay(DELAY_SDK_DIALOG_CLOSE)
+
+ onSuccess(updatedScanResponse)
+ }
+ is CompletionResult.Failure -> {
+ store.dispatchDebugErrorNotification(TapError.CustomError("Error adding tokens"))
+ }
+ }
+ }
+
+ private fun getDerivations(
+ curve: EllipticCurve,
+ scanResponse: ScanResponse,
+ currencyList: List,
+ ): TokensMiddleware.DerivationData? {
+ val wallet = scanResponse.card.wallets.firstOrNull { it.curve == curve } ?: return null
+
+ val manageTokensCandidates = currencyList.map { it.blockchain }.distinct().filter {
+ it.getSupportedCurves().contains(curve)
+ }.mapNotNull {
+ it.derivationPath(scanResponse.card.derivationStyle)
+ }
+
+ val customTokensCandidates = currencyList.filter {
+ it.blockchain.getSupportedCurves().contains(curve)
+ }.mapNotNull { it.derivationPath }.map { DerivationPath(it) }
+
+ val bothCandidates = (manageTokensCandidates + customTokensCandidates).distinct()
+ if (bothCandidates.isEmpty()) return null
+
+ val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey()
+ val alreadyDerivedKeys: ExtendedPublicKeysMap =
+ scanResponse.derivedKeys[mapKeyOfWalletPublicKey] ?: ExtendedPublicKeysMap(emptyMap())
+ val alreadyDerivedPaths = alreadyDerivedKeys.keys.toList()
+
+ val toDerive = bothCandidates.filterNot { alreadyDerivedPaths.contains(it) }
+ if (toDerive.isEmpty()) return null
+
+ return TokensMiddleware.DerivationData(derivations = mapKeyOfWalletPublicKey to toDerive)
+ }
+
+ private suspend fun submitAdd(scanResponse: ScanResponse, currency: Currency) {
val selectedUserWallet = userWalletsListManager.selectedUserWalletSync.guard {
Timber.e("Unable to add currencies, no user wallet selected")
return
}
- scope.launch {
- userWalletsListManager.update(
- userWalletId = selectedUserWallet.walletId,
- update = { userWallet ->
- userWallet.copy(scanResponse = scanResponse)
- },
- )
- .flatMap { updatedUserWallet ->
- walletCurrenciesManager.addCurrencies(
- userWallet = updatedUserWallet,
- currenciesToAdd = listOf(currency),
- )
- }
- }
+
+ userWalletsListManager.update(
+ userWalletId = selectedUserWallet.walletId,
+ update = { userWallet -> userWallet.copy(scanResponse = scanResponse) },
+ )
+ .flatMap { updatedUserWallet ->
+ walletCurrenciesManager.addCurrencies(
+ userWallet = updatedUserWallet,
+ currenciesToAdd = listOf(currency),
+ )
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenAnalyticsSender.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenAnalyticsSender.kt
index e9641c119f..55886b316a 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenAnalyticsSender.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenAnalyticsSender.kt
@@ -1,8 +1,8 @@
package com.tangem.tap.features.customtoken.impl.presentation.viewmodels
import com.tangem.core.analytics.api.AnalyticsEventHandler
+import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.tap.common.analytics.events.ManageTokens
-import com.tangem.tap.features.wallet.models.Currency
/** Analytics sender for tokens list screen */
class AddCustomTokenAnalyticsSender(private val analyticsEventHandler: AnalyticsEventHandler) {
@@ -11,25 +11,7 @@ class AddCustomTokenAnalyticsSender(private val analyticsEventHandler: Analytics
analyticsEventHandler.send(ManageTokens.CustomToken.ScreenOpened)
}
- fun sendWhenAddTokenButtonClicked(currency: Currency, address: String) {
- analyticsEventHandler.send(
- when (currency) {
- is Currency.Blockchain -> {
- ManageTokens.CustomToken.TokenWasAdded.Blockchain(
- derivationPath = currency.derivationPath,
- blockchain = currency.blockchain,
- )
- }
-
- is Currency.Token -> {
- ManageTokens.CustomToken.TokenWasAdded.Token(
- symbol = currency.currencySymbol,
- derivationPath = currency.derivationPath,
- blockchain = currency.blockchain,
- contractAddress = address,
- )
- }
- },
- )
+ fun sendWhenAddTokenButtonClicked(customCurrency: CustomCurrency) {
+ analyticsEventHandler.send(ManageTokens.CustomToken.TokenWasAdded(customCurrency))
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt
index 3ea81b5d7c..74084af835 100644
--- a/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt
+++ b/app/src/main/java/com/tangem/tap/features/customtoken/impl/presentation/viewmodels/AddCustomTokenViewModel.kt
@@ -18,6 +18,7 @@ import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.AddCustomTokenError
import com.tangem.domain.common.TapWorkarounds.derivationStyle
import com.tangem.domain.common.extensions.*
+import com.tangem.domain.features.addCustomToken.CustomCurrency
import com.tangem.tap.domain.model.WalletDataModel
import com.tangem.tap.features.customtoken.impl.domain.CustomTokenInteractor
import com.tangem.tap.features.customtoken.impl.domain.models.FoundToken
@@ -198,6 +199,7 @@ internal class AddCustomTokenViewModel @Inject constructor(
(blockchain.isEvm() || blockchain.canHandleTokens()) &&
reduxStateHolder.scanResponse?.card?.supportedBlockchains()?.contains(blockchain) == true
}
+ .sortedBy(Blockchain::fullName)
.map(::createNetworkSelectorItem)
}
@@ -321,7 +323,9 @@ internal class AddCustomTokenViewModel @Inject constructor(
}
private fun isDerivationPathSelected(): Boolean {
- return uiState.form.derivationPathSelectorField?.selectedItem?.blockchain != Blockchain.Unknown
+ return with(uiState.form.derivationPathSelectorField?.selectedItem?.blockchain) {
+ this != null && this != Blockchain.Unknown
+ }
}
private fun updateWarnings() {
@@ -516,20 +520,16 @@ internal class AddCustomTokenViewModel @Inject constructor(
}
private fun getDerivationPath(): DerivationPath? {
- val isNotDerivationPathSelected = !isDerivationPathSelected()
- val network = if (isNotDerivationPathSelected) {
- uiState.form.networkSelectorField.selectedItem.blockchain
+ val derivationStyle = if (!isDerivationPathSelected()) {
+ reduxStateHolder.scanResponse?.card?.derivationStyle
} else {
- uiState.form.derivationPathSelectorField?.selectedItem?.blockchain
+ DerivationStyle.LEGACY
}
- return network?.derivationPath(
- style = if (isNotDerivationPathSelected) {
- reduxStateHolder.scanResponse?.card?.derivationStyle
- } else {
- DerivationStyle.LEGACY
- },
- )
+ return when (val derivationNetwork = uiState.form.derivationPathSelectorField?.selectedItem?.blockchain) {
+ Blockchain.Unknown -> uiState.form.networkSelectorField.selectedItem.blockchain
+ else -> derivationNetwork
+ }?.derivationPath(derivationStyle)
}
private inner class ActionsHandler(private val featureRouter: CustomTokenRouter) {
@@ -626,34 +626,32 @@ internal class AddCustomTokenViewModel @Inject constructor(
fun onAddCustomTokenClick() {
if (!isNetworkSelected()) return
- val address = uiState.form.contractAddressInputField.value
val currency = when (getCustomTokenType()) {
CustomTokenType.TOKEN -> {
- Currency.Token(
+ CustomCurrency.CustomToken(
token = Token(
name = uiState.form.tokenNameInputField.value,
symbol = uiState.form.tokenSymbolInputField.value,
- contractAddress = address,
+ contractAddress = uiState.form.contractAddressInputField.value,
decimals = requireNotNull(uiState.form.decimalsInputField.value.toIntOrNull()),
id = foundToken?.id,
),
- blockchain = uiState.form.networkSelectorField.selectedItem.blockchain,
- derivationPath = getDerivationPath()?.rawPath,
+ network = uiState.form.networkSelectorField.selectedItem.blockchain,
+ derivationPath = getDerivationPath(),
)
}
-
CustomTokenType.BLOCKCHAIN -> {
- Currency.Blockchain(
- blockchain = uiState.form.networkSelectorField.selectedItem.blockchain,
- derivationPath = getDerivationPath()?.rawPath,
+ CustomCurrency.CustomBlockchain(
+ network = uiState.form.networkSelectorField.selectedItem.blockchain,
+ derivationPath = getDerivationPath(),
)
}
}
- analyticsSender.sendWhenAddTokenButtonClicked(currency, address)
+ analyticsSender.sendWhenAddTokenButtonClicked(currency)
- viewModelScope.launch(dispatchers.main) {
- runCatching(dispatchers.io) { featureInteractor.saveToken(currency, address) }
+ viewModelScope.launch(dispatchers.io) {
+ runCatching { featureInteractor.saveToken(currency) }
.onSuccess { featureRouter.openWalletScreen() }
.onFailure(Timber::e)
}
diff --git a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/RenameWalletDialogContent.kt b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/RenameWalletDialogContent.kt
index 64b69bad75..7bc58f301a 100644
--- a/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/RenameWalletDialogContent.kt
+++ b/app/src/main/java/com/tangem/tap/features/walletSelector/ui/components/RenameWalletDialogContent.kt
@@ -1,11 +1,7 @@
package com.tangem.tap.features.walletSelector.ui.components
import androidx.compose.foundation.layout.Column
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
+import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
@@ -26,7 +22,7 @@ internal fun RenameWalletDialogContent(dialog: DialogModel.RenameWalletDialog) {
TextInputDialog(
fieldValue = value,
confirmButton = DialogButton(
- title = stringResource(id = R.string.common_save),
+ title = stringResource(id = R.string.common_ok),
enabled = value.text.isNotEmpty() && value.text != dialog.currentName,
onClick = { dialog.onConfirm(value.text) },
),
diff --git a/domain/models/src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt b/domain/models/src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt
index 63195654a4..7578791134 100644
--- a/domain/models/src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt
+++ b/domain/models/src/main/kotlin/com/tangem/domain/models/scan/CardDTO.kt
@@ -310,8 +310,8 @@ data class CardDTO(
internal fun fromSdkStatus(sdkStatus: Card.BackupStatus?): BackupStatus? {
return when (sdkStatus) {
is Card.BackupStatus.NoBackup -> NoBackup
- is Card.BackupStatus.CardLinked -> CardLinked(sdkStatus.cardCount)
- is Card.BackupStatus.Active -> Active(sdkStatus.cardCount)
+ is Card.BackupStatus.CardLinked -> CardLinked(sdkStatus.cardsCount)
+ is Card.BackupStatus.Active -> Active(sdkStatus.cardsCount)
null -> null
}
}
diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml
index c1f74aa37d..933e43fd14 100644
--- a/gradle/dependencies.toml
+++ b/gradle/dependencies.toml
@@ -75,7 +75,7 @@ reactiveNetwork = "3.0.8"
# region Tangem
tangemBlockchainSdk = "develop-222"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
-tangemCardSdk = "develop-242"
+tangemCardSdk = "develop-248"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds
# endregion Tangem