Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-21 18:34:38 +03:00
commit 438126dc82
665 changed files with 16174 additions and 6245 deletions

View file

@ -32,6 +32,7 @@ dependencies {
// region Tangem libraries
implementation(tangemDeps.blockchain) // android-library
implementation(tangemDeps.card.core)
implementation(tangemDeps.hot.core)
// endregion
// region DI

View file

@ -8,6 +8,7 @@ plugins {
dependencies {
// region Tangem libraries
implementation(tangemDeps.card.core)
implementation(tangemDeps.hot.core)
// endregion
// region Domain modules

View file

@ -1,8 +1,9 @@
package com.tangem.domain.wallets.models
import com.tangem.domain.models.MobileWallet
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.wallets.models.UserWallet.Cold
import com.tangem.hot.sdk.model.HotWalletId
import kotlinx.serialization.Serializable
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
@ -56,17 +57,22 @@ sealed interface UserWallet {
data class Hot(
override val name: String,
override val walletId: UserWalletId,
val isLocked: Boolean,
) : UserWallet
val hotWalletId: HotWalletId,
val wallets: List<MobileWallet>?,
val backedUp: Boolean,
) : UserWallet {
val isLocked: Boolean get() = wallets == null
}
}
@OptIn(ExperimentalContracts::class)
fun UserWallet.requireColdWallet(): Cold {
fun UserWallet.requireColdWallet(): UserWallet.Cold {
contract {
returns() implies (this@requireColdWallet is Cold)
returns() implies (this@requireColdWallet is UserWallet.Cold)
}
return this as? Cold
return this as? UserWallet.Cold
?: error("This user wallet is not a cold wallet")
}

View file

@ -0,0 +1,71 @@
package com.tangem.domain.wallets.builder
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.domain.models.MobileWallet
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.GenerateWalletNameUseCase
import com.tangem.hot.sdk.TangemHotSdk
import com.tangem.hot.sdk.model.DeriveWalletRequest
import com.tangem.hot.sdk.model.HotAuth
import com.tangem.hot.sdk.model.HotWalletId
import com.tangem.hot.sdk.model.UnlockHotWallet
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.withContext
class HotUserWalletBuilder @AssistedInject constructor(
@Assisted private val hotWalletId: HotWalletId,
private val hotSdk: TangemHotSdk,
private val generateWalletNameUseCase: GenerateWalletNameUseCase,
private val dispatcherProvider: CoroutineDispatcherProvider,
) {
suspend fun build(): UserWallet.Hot = withContext(dispatcherProvider.default) {
val allNetworks = Blockchain.entries // TODO use HotDerivationsRepository to get supported networks
val curves = allNetworks.map { it.getSupportedCurves() }.flatten().toSet()
val requests = curves.sortedBy { it.ordinal }.map { curve ->
val derivationPaths = allNetworks.filter { curve in it.getSupportedCurves() }
.mapNotNull { it.derivationPath(DerivationStyle.V3) }
DeriveWalletRequest.Request(
curve = curve,
paths = derivationPaths,
)
}
val derivationResult = hotSdk.derivePublicKey(
unlockHotWallet = UnlockHotWallet(
walletId = hotWalletId,
auth = HotAuth.NoAuth,
),
request = DeriveWalletRequest(
requests = requests,
),
)
val wallets = derivationResult.responses.map {
MobileWallet(
publicKey = it.seedKey.publicKey,
chainCode = it.seedKey.chainCode,
curve = it.curve,
derivedKeys = it.publicKeys,
)
}
UserWallet.Hot(
name = generateWalletNameUseCase.invokeForHot(),
walletId = UserWalletIdBuilder.walletPublicKey(wallets.first().publicKey),
hotWalletId = hotWalletId,
wallets = wallets,
backedUp = false,
)
}
@AssistedFactory
interface Factory {
fun create(hotWalletId: HotWalletId): HotUserWalletBuilder
}
}

View file

@ -0,0 +1,8 @@
package com.tangem.domain.wallets.repository
import com.tangem.domain.models.network.Network
interface HotDerivationsRepository {
fun getAllSupportedNetworks(): Set<Network>
}

View file

@ -21,6 +21,12 @@ class GenerateWalletNameUseCase(
return suggestedWalletName(defaultName, existingNames)
}
fun invokeForHot(): String {
val defaultName = "Wallet"
val existingNames = userWalletsListManager.userWalletsSync.map { it.name }.toSet()
return suggestedWalletName(defaultName, existingNames)
}
private fun suggestedWalletName(defaultName: String, existingNames: Set<String>): String {
val startIndex = 2
if (!existingNames.contains(defaultName)) {

View file

@ -1,7 +1,6 @@
package com.tangem.domain.wallets.usecase
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.extensions.toHexString
import com.tangem.common.services.Result
import com.tangem.domain.common.TwinCardNumber
import com.tangem.domain.common.TwinsHelper
@ -9,20 +8,16 @@ import com.tangem.domain.models.ArtworkModel
import com.tangem.domain.wallets.models.Artwork
import com.tangem.operations.attestation.ArtworkSize
import com.tangem.operations.attestation.CardArtworksProvider
import com.tangem.operations.attestation.OnlineCardVerifier
import com.tangem.sdk.api.featuretoggles.CardSdkFeatureToggles
/**
* Use case for getting card image url
*
* @property verifier online card verifier
* @property cardArtworksProvider card artworks provider
*
[REDACTED_AUTHOR]
*/
class GetCardImageUseCase(
private val verifier: OnlineCardVerifier,
private val cardArtworksProvider: CardArtworksProvider,
private val cardSdkFeatureToggles: CardSdkFeatureToggles,
) {
/**
@ -38,39 +33,17 @@ class GetCardImageUseCase(
firmwareVersion: FirmwareVersion,
size: ArtworkSize = ArtworkSize.SMALL,
): ArtworkModel {
return if (cardSdkFeatureToggles.isNewArtworkLoadingEnabled) {
val result = cardArtworksProvider.getArtwork(
cardId = cardId,
cardPublicKey = cardPublicKey,
manufacturerName = manufacturerName,
firmwareVersion = firmwareVersion,
size = size,
)
when (result) {
is Result.Failure -> ArtworkModel(null, getFallbackArtworkUrl(cardId))
is Result.Success -> ArtworkModel(result.data, getFallbackArtworkUrl(cardId))
}
} else {
ArtworkModel(null, getLegacyArtwork(cardId, cardPublicKey))
}
}
val result = cardArtworksProvider.getArtwork(
cardId = cardId,
cardPublicKey = cardPublicKey,
manufacturerName = manufacturerName,
firmwareVersion = firmwareVersion,
size = size,
)
private suspend fun getLegacyArtwork(cardId: String, cardPublicKey: ByteArray): String {
return when (val result = verifier.getCardInfo(cardId, cardPublicKey)) {
is Result.Success -> {
val artworkId = result.data.artwork?.id
if (artworkId.isNullOrEmpty()) {
getFallbackArtworkUrl(cardId)
} else {
CardArtworksProvider.getUrlForArtwork(
cardId = cardId,
cardPublicKey = cardPublicKey.toHexString(),
artworkId = artworkId,
)
}
}
is Result.Failure -> getFallbackArtworkUrl(cardId)
return when (result) {
is Result.Failure -> ArtworkModel(null, getFallbackArtworkUrl(cardId))
is Result.Success -> ArtworkModel(result.data, getFallbackArtworkUrl(cardId))
}
}