Updated on 2026-08-14

This commit is contained in:
Tangem 2025-07-24 17:48:38 +03:00
parent 85803abc7f
commit 095c755362
194 changed files with 830 additions and 473 deletions

View file

@ -1,40 +0,0 @@
package com.tangem.domain.common
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.common.extensions.calculateHashCode
@JsonClass(generateAdapter = true)
@Deprecated("Use Network model")
data class BlockchainNetwork(
@Json(name = "blockchain")
val blockchain: Blockchain,
@Json(name = "derivationPath")
val derivationPath: String?,
@Json(name = "tokens")
val tokens: List<Token>,
) {
constructor(blockchain: Blockchain, derivationStyleProvider: DerivationStyleProvider) : this(
blockchain = blockchain,
derivationPath = blockchain.derivationPath(derivationStyleProvider.getDerivationStyle())?.rawPath,
tokens = emptyList(),
)
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as BlockchainNetwork
if (blockchain != other.blockchain) return false
return derivationPath == other.derivationPath
}
override fun hashCode(): Int = calculateHashCode(
blockchain.hashCode(),
derivationPath?.hashCode() ?: 0,
)
}

View file

@ -1,50 +0,0 @@
package com.tangem.domain.common
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
@Suppress("TooManyFunctions")
interface CardTypesResolver {
fun isTangemNote(): Boolean
fun isTangemWallet(): Boolean
fun isShibaWallet(): Boolean
fun isWhiteWallet(): Boolean
fun isWallet2(): Boolean
fun isVisaWallet(): Boolean
fun isRing(): Boolean
fun isTangemTwins(): Boolean
fun isStart2Coin(): Boolean
fun isDevKit(): Boolean
fun isSingleWallet(): Boolean
fun isSingleWalletWithToken(): Boolean
fun isMultiwalletAllowed(): Boolean
fun getBlockchain(): Blockchain
fun getPrimaryToken(): Token?
fun isReleaseFirmwareType(): Boolean
fun getRemainingSignatures(): Int?
fun getCardId(): String
fun isTestCard(): Boolean
fun isAttestationFailed(): Boolean
fun hasWalletSignedHashes(): Boolean
}

View file

@ -1,30 +0,0 @@
package com.tangem.domain.common
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.domain.common.TapWorkarounds.isWallet2
import com.tangem.domain.models.scan.CardDTO
interface DerivationStyleProvider {
fun getDerivationStyle(): DerivationStyle?
}
internal class TangemDerivationStyleProvider(
private val card: CardDTO,
) : DerivationStyleProvider {
override fun getDerivationStyle(): DerivationStyle? {
return when {
!card.settings.isHDWalletAllowed -> null
firstBatchesOfWallet1(card) -> DerivationStyle.V1
card.isWallet2 -> DerivationStyle.V3
else -> DerivationStyle.V2
}
}
private fun firstBatchesOfWallet1(card: CardDTO): Boolean {
return card.batchId == "AC01" || card.batchId == "AC02" || card.batchId == "CB95"
}
}
internal class TangemHotDerivationStyleProvider : DerivationStyleProvider {
override fun getDerivationStyle(): DerivationStyle? = DerivationStyle.V3
}

View file

@ -1,138 +0,0 @@
package com.tangem.domain.common
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.common.card.EllipticCurve
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.card.WalletData
import com.tangem.domain.common.TapWorkarounds.getTangemNoteBlockchain
import com.tangem.domain.common.TapWorkarounds.isStart2Coin
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.TapWorkarounds.isWallet2
import com.tangem.domain.common.visa.VisaUtilities
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ProductType
import com.tangem.operations.attestation.Attestation
@Suppress("TooManyFunctions")
internal class TangemCardTypesResolver(
private val card: CardDTO,
private val productType: ProductType,
private val walletData: WalletData?,
) : CardTypesResolver {
override fun isTangemNote(): Boolean = productType == ProductType.Note
override fun isTangemWallet(): Boolean {
return card.settings.isBackupAllowed && card.settings.isHDWalletAllowed &&
card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable
}
override fun isShibaWallet(): Boolean {
return card.firmwareVersion.compareTo(FirmwareVersion.KeysImportAvailable) == 0
}
override fun isWhiteWallet(): Boolean {
return walletData == null && card.firmwareVersion <= FirmwareVersion.HDWalletAvailable
}
override fun isWallet2(): Boolean = card.isWallet2
override fun isVisaWallet(): Boolean = productType == ProductType.Visa
override fun isRing(): Boolean {
return productType == ProductType.Ring
}
override fun isTangemTwins(): Boolean = productType == ProductType.Twins
override fun isStart2Coin(): Boolean = card.isStart2Coin
override fun isDevKit(): Boolean = card.batchId == DEV_KIT_CARD_BATCH_ID
override fun isSingleWallet(): Boolean = !isMultiwalletAllowed() && !isSingleWalletWithToken() && !isVisaWallet()
override fun isSingleWalletWithToken(): Boolean = walletData?.token != null && !isMultiwalletAllowed()
override fun isMultiwalletAllowed(): Boolean {
return !isTangemTwins() &&
!card.isStart2Coin &&
!isTangemNote() &&
!isVisaWallet() &&
(multiWalletAvailable() || card.wallets.firstOrNull()?.curve == EllipticCurve.Secp256k1)
}
private fun multiWalletAvailable() = card.firmwareVersion >= FirmwareVersion.MultiWalletAvailable
override fun getBlockchain(): Blockchain {
return when (productType) {
ProductType.Start2Coin -> if (card.isTestCard) Blockchain.BitcoinTestnet else Blockchain.Bitcoin
ProductType.Visa -> VisaUtilities.visaBlockchain
else -> {
val blockchainName: String = walletData?.blockchain
?: if (productType == ProductType.Note) {
return card.getTangemNoteBlockchain() ?: Blockchain.Unknown
} else {
return Blockchain.Unknown
}
Blockchain.fromBlockchainName(blockchainName)
}
}
}
override fun getPrimaryToken(): Token? {
val cardToken = walletData?.token ?: return null
return Token(
cardToken.name,
cardToken.symbol,
cardToken.contractAddress,
cardToken.decimals,
)
}
override fun isReleaseFirmwareType(): Boolean = card.firmwareVersion.type == FirmwareVersion.FirmwareType.Release
override fun getRemainingSignatures(): Int? = card.wallets.firstOrNull()?.remainingSignatures
override fun getCardId(): String {
return if (isTangemTwins()) {
card.getTwinCardIdForUser()
} else {
card.cardId
}
}
override fun isTestCard(): Boolean = card.isTestCard
override fun isAttestationFailed(): Boolean = card.attestation.status == Attestation.Status.Failed
override fun hasWalletSignedHashes(): Boolean {
return card.wallets.any {
val totalSignedHashes = it.totalSignedHashes ?: 0
totalSignedHashes > 0
}
}
private fun Blockchain.Companion.fromBlockchainName(blockchainName: String): Blockchain {
// workaround for BSC (BNB) notes cards
return when (blockchainName) {
"BINANCE" -> {
Blockchain.BSC
}
"BINANCE/test" -> {
Blockchain.BSCTestnet
}
"CARDANO" -> {
Blockchain.Cardano
}
else -> {
Blockchain.fromId(blockchainName)
}
}
}
private companion object {
const val DEV_KIT_CARD_BATCH_ID = "CB83"
}
}

View file

@ -1,81 +0,0 @@
package com.tangem.domain.common
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.card.FirmwareVersion
import com.tangem.domain.common.visa.VisaUtilities
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.scan.CardDTO
import java.util.Locale
/**
[REDACTED_AUTHOR]
*/
object TapWorkarounds {
private const val START_2_COIN_ISSUER = "start2coin"
private const val TEST_CARD_BATCH = "99FF"
private const val TEST_CARD_ID_STARTS_WITH = "FF99"
private val backupRequiredFirmwareVersion = FirmwareVersion(major = 6, minor = 21)
private val demoConfig = DemoConfig()
val CardDTO.isTangemTwins: Boolean
get() = TwinsHelper.getTwinCardNumber(cardId) != null
val CardDTO.isStart2Coin: Boolean
get() = isStart2CoinIssuer(issuer.name)
val CardDTO.isWallet2: Boolean
get() = firmwareVersion >= FirmwareVersion.Ed25519Slip0010Available && settings.isKeysImportAllowed
val CardDTO.isVisa: Boolean
get() = VisaUtilities.isVisaCard(this)
val CardDTO.isTestCard: Boolean
get() = batchId == TEST_CARD_BATCH && cardId.startsWith(TEST_CARD_ID_STARTS_WITH)
// for cards 6.21 and higher backup is not skippable
val CardDTO.canSkipBackup: Boolean
get() = this.firmwareVersion < backupRequiredFirmwareVersion || demoConfig.isDemoCardId(cardId)
val CardDTO.useOldStyleDerivation: Boolean
get() = batchId == "AC01" || batchId == "AC02" || batchId == "CB95"
val CardDTO.isExcluded: Boolean
get() {
val excludedBatch = excludedBatches.contains(batchId)
val excludedIssuerName = excludedIssuers.contains(issuer.name.uppercase(Locale.ROOT))
return excludedBatch || excludedIssuerName
}
val CardDTO.isNotSupportedInThatRelease: Boolean
get() = false
private val tangemNoteBatches = mapOf(
"AB01" to Blockchain.Bitcoin,
"AB02" to Blockchain.Ethereum,
"AB03" to Blockchain.Cardano,
"AB04" to Blockchain.Dogecoin,
"AB05" to Blockchain.BSC,
"AB06" to Blockchain.XRP,
"AB07" to Blockchain.Bitcoin,
"AB08" to Blockchain.Ethereum,
"AB09" to Blockchain.Bitcoin, // new batches for 3.34
"AB10" to Blockchain.Ethereum,
"AB11" to Blockchain.Bitcoin,
"AB12" to Blockchain.Ethereum,
)
// TODO try remove if it's possible because we can configure on init sdk com.tangem.common.core.Config
private val excludedBatches = listOf("0027", "0030", "0031", "0035", "DA88", "AF56")
private val excludedIssuers = listOf("TTM BANK")
@Deprecated(
"Now blockchain is read form files (CardTypesResolver.getBlockchain), " +
"but for previously saved cards this method is still used",
)
fun CardDTO.getTangemNoteBlockchain(): Blockchain? = tangemNoteBatches[batchId]
fun isStart2CoinIssuer(cardIssuer: String?): Boolean {
return cardIssuer?.lowercase(Locale.US) == START_2_COIN_ISSUER
}
}

View file

@ -1,33 +0,0 @@
package com.tangem.domain.common.configs
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.card.EllipticCurve
import com.tangem.common.card.FirmwareVersion
import com.tangem.domain.models.scan.CardDTO
sealed interface CardConfig {
val mandatoryCurves: List<EllipticCurve>
fun primaryCurve(blockchain: Blockchain): EllipticCurve?
companion object {
fun createConfig(cardDTO: CardDTO): CardConfig {
if (cardDTO.firmwareVersion >= FirmwareVersion.Ed25519Slip0010Available) {
return Wallet2CardConfig
}
if (cardDTO.settings.isBackupAllowed && cardDTO.settings.isHDWalletAllowed &&
cardDTO.firmwareVersion >= FirmwareVersion.MultiWalletAvailable
) {
return MultiWalletCardConfig
}
if (cardDTO.supportedCurves.size == 1 &&
cardDTO.supportedCurves.contains(EllipticCurve.Ed25519)
) {
return EdSingleCurrencyCardConfig
}
return GenericCardConfig(cardDTO.settings.maxWalletsCount)
}
}
}

View file

@ -1,22 +0,0 @@
package com.tangem.domain.common.configs
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.card.EllipticCurve
import timber.log.Timber
object EdSingleCurrencyCardConfig : CardConfig {
override val mandatoryCurves: List<EllipticCurve> = listOf(EllipticCurve.Ed25519)
override fun primaryCurve(blockchain: Blockchain): EllipticCurve? {
return when {
blockchain.getSupportedCurves().contains(EllipticCurve.Ed25519) -> {
EllipticCurve.Ed25519
}
else -> {
Timber.e("Unsupported blockchain, curve not found")
null
}
}
}
}

View file

@ -1,33 +0,0 @@
package com.tangem.domain.common.configs
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.card.EllipticCurve
import timber.log.Timber
class GenericCardConfig(maxWalletCount: Int) : CardConfig {
override val mandatoryCurves: List<EllipticCurve> = buildList {
add(EllipticCurve.Secp256k1)
if (maxWalletCount > 1) {
add(EllipticCurve.Ed25519)
}
}
/**
* Old logic to determine primary curve for blockchain
*/
override fun primaryCurve(blockchain: Blockchain): EllipticCurve? {
return when {
blockchain.getSupportedCurves().contains(EllipticCurve.Secp256k1) -> {
EllipticCurve.Secp256k1
}
blockchain.getSupportedCurves().contains(EllipticCurve.Ed25519) -> {
EllipticCurve.Ed25519
}
else -> {
Timber.e("Unsupported blockchain, curve not found")
null
}
}
}
}

View file

@ -1,35 +0,0 @@
package com.tangem.domain.common.configs
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.card.EllipticCurve
import timber.log.Timber
object MultiWalletCardConfig : CardConfig {
override val mandatoryCurves: List<EllipticCurve>
get() = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bls12381G2Aug,
)
/**
* Old logic to determine primary curve for blockchain in TangemWallet
*/
override fun primaryCurve(blockchain: Blockchain): EllipticCurve? {
return when {
blockchain.getSupportedCurves().contains(EllipticCurve.Secp256k1) -> {
EllipticCurve.Secp256k1
}
blockchain.getSupportedCurves().contains(EllipticCurve.Ed25519) -> {
EllipticCurve.Ed25519
}
blockchain.getSupportedCurves().contains(EllipticCurve.Bls12381G2Aug) -> {
EllipticCurve.Bls12381G2Aug
}
else -> {
Timber.e("Unsupported blockchain, curve not found")
null
}
}
}
}

View file

@ -1,212 +0,0 @@
package com.tangem.domain.common.configs
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.card.EllipticCurve
import timber.log.Timber
data object Wallet2CardConfig : CardConfig {
override val mandatoryCurves: List<EllipticCurve>
get() = listOf(
EllipticCurve.Secp256k1,
EllipticCurve.Ed25519,
EllipticCurve.Bls12381G2Aug,
EllipticCurve.Bip0340,
EllipticCurve.Ed25519Slip0010,
)
/**
* Logic to determine primary curve for blockchain in TangemWallet 2.0
* Order is important here
*/
override fun primaryCurve(blockchain: Blockchain): EllipticCurve? {
// order is important, new curve is preferred for wallet 2
// TODO Comment old logic without direct mapping until tests and release
// return when {
// blockchain.getSupportedCurves().contains(EllipticCurve.Ed25519Slip0010) -> {
// EllipticCurve.Ed25519Slip0010
// }
// blockchain.getSupportedCurves().contains(EllipticCurve.Secp256k1) -> {
// EllipticCurve.Secp256k1
// }
// blockchain.getSupportedCurves().contains(EllipticCurve.Bls12381G2Aug) -> {
// EllipticCurve.Bls12381G2Aug
// }
// // only for support cardano on Wallet2
// blockchain.getSupportedCurves().contains(EllipticCurve.Ed25519) -> {
// EllipticCurve.Ed25519
// }
// else -> {
// Timber.e("Unsupported blockchain, curve not found")
// null
// }
// }
val curve = getPrimaryCurveForBlockchain(blockchain)
// check curve supports
if (!blockchain.getSupportedCurves().contains(curve)) {
Timber.e("Unsupported curve $curve for blockchain $blockchain")
return null
}
return curve
}
@Suppress("LongMethod", "CyclomaticComplexMethod")
private fun getPrimaryCurveForBlockchain(blockchain: Blockchain): EllipticCurve? {
return when (blockchain) {
Blockchain.Unknown -> null
Blockchain.Arbitrum -> EllipticCurve.Secp256k1
Blockchain.ArbitrumTestnet -> EllipticCurve.Secp256k1
Blockchain.Avalanche -> EllipticCurve.Secp256k1
Blockchain.AvalancheTestnet -> EllipticCurve.Secp256k1
Blockchain.Binance -> EllipticCurve.Secp256k1
Blockchain.BinanceTestnet -> EllipticCurve.Secp256k1
Blockchain.BSC -> EllipticCurve.Secp256k1
Blockchain.BSCTestnet -> EllipticCurve.Secp256k1
Blockchain.Bitcoin -> EllipticCurve.Secp256k1
Blockchain.BitcoinTestnet -> EllipticCurve.Secp256k1
Blockchain.BitcoinCash -> EllipticCurve.Secp256k1
Blockchain.BitcoinCashTestnet -> EllipticCurve.Secp256k1
Blockchain.Cardano -> EllipticCurve.Ed25519
Blockchain.Cosmos -> EllipticCurve.Secp256k1
Blockchain.CosmosTestnet -> EllipticCurve.Secp256k1
Blockchain.Dogecoin -> EllipticCurve.Secp256k1
Blockchain.Ducatus -> EllipticCurve.Secp256k1
Blockchain.Ethereum -> EllipticCurve.Secp256k1
Blockchain.EthereumTestnet -> EllipticCurve.Secp256k1
Blockchain.EthereumClassic -> EllipticCurve.Secp256k1
Blockchain.EthereumClassicTestnet -> EllipticCurve.Secp256k1
Blockchain.Fantom -> EllipticCurve.Secp256k1
Blockchain.FantomTestnet -> EllipticCurve.Secp256k1
Blockchain.Litecoin -> EllipticCurve.Secp256k1
Blockchain.Near -> EllipticCurve.Ed25519Slip0010
Blockchain.NearTestnet -> EllipticCurve.Ed25519Slip0010
Blockchain.Polkadot -> EllipticCurve.Ed25519Slip0010
Blockchain.PolkadotTestnet -> EllipticCurve.Ed25519Slip0010
Blockchain.Kava -> EllipticCurve.Secp256k1
Blockchain.KavaTestnet -> EllipticCurve.Secp256k1
Blockchain.Kusama -> EllipticCurve.Ed25519Slip0010
Blockchain.Polygon -> EllipticCurve.Secp256k1
Blockchain.PolygonTestnet -> EllipticCurve.Secp256k1
Blockchain.RSK -> EllipticCurve.Secp256k1
Blockchain.Sei -> EllipticCurve.Secp256k1
Blockchain.SeiTestnet -> EllipticCurve.Secp256k1
Blockchain.Stellar -> EllipticCurve.Ed25519Slip0010
Blockchain.StellarTestnet -> EllipticCurve.Ed25519Slip0010
Blockchain.Solana -> EllipticCurve.Ed25519Slip0010
Blockchain.SolanaTestnet -> EllipticCurve.Ed25519Slip0010
Blockchain.Tezos -> EllipticCurve.Ed25519Slip0010
Blockchain.Tron -> EllipticCurve.Secp256k1
Blockchain.TronTestnet -> EllipticCurve.Secp256k1
Blockchain.XRP -> EllipticCurve.Secp256k1
Blockchain.Gnosis -> EllipticCurve.Secp256k1
Blockchain.Dash -> EllipticCurve.Secp256k1
Blockchain.Optimism -> EllipticCurve.Secp256k1
Blockchain.OptimismTestnet -> EllipticCurve.Secp256k1
Blockchain.Dischain -> EllipticCurve.Secp256k1
Blockchain.EthereumPow -> EllipticCurve.Secp256k1
Blockchain.EthereumPowTestnet -> EllipticCurve.Secp256k1
Blockchain.Kaspa -> EllipticCurve.Secp256k1
Blockchain.Telos -> EllipticCurve.Secp256k1
Blockchain.TelosTestnet -> EllipticCurve.Secp256k1
Blockchain.TON -> EllipticCurve.Ed25519Slip0010
Blockchain.TONTestnet -> EllipticCurve.Ed25519Slip0010
Blockchain.Ravencoin -> EllipticCurve.Secp256k1
Blockchain.RavencoinTestnet -> EllipticCurve.Secp256k1
Blockchain.TerraV1 -> EllipticCurve.Secp256k1
Blockchain.TerraV2 -> EllipticCurve.Secp256k1
Blockchain.Cronos -> EllipticCurve.Secp256k1
Blockchain.AlephZero -> EllipticCurve.Ed25519Slip0010
Blockchain.AlephZeroTestnet -> EllipticCurve.Ed25519Slip0010
Blockchain.OctaSpace -> EllipticCurve.Secp256k1
Blockchain.OctaSpaceTestnet -> EllipticCurve.Secp256k1
Blockchain.Chia -> EllipticCurve.Bls12381G2Aug
Blockchain.ChiaTestnet -> EllipticCurve.Bls12381G2Aug
Blockchain.Decimal -> EllipticCurve.Secp256k1
Blockchain.DecimalTestnet -> EllipticCurve.Secp256k1
Blockchain.XDC -> EllipticCurve.Secp256k1
Blockchain.XDCTestnet -> EllipticCurve.Secp256k1
Blockchain.VeChain -> EllipticCurve.Secp256k1
Blockchain.VeChainTestnet -> EllipticCurve.Secp256k1
Blockchain.Aptos -> EllipticCurve.Ed25519Slip0010
Blockchain.AptosTestnet -> EllipticCurve.Ed25519Slip0010
Blockchain.Playa3ull -> EllipticCurve.Secp256k1
Blockchain.Shibarium -> EllipticCurve.Secp256k1
Blockchain.ShibariumTestnet -> EllipticCurve.Secp256k1
Blockchain.Algorand -> EllipticCurve.Ed25519Slip0010
Blockchain.AlgorandTestnet -> EllipticCurve.Ed25519Slip0010
Blockchain.Hedera -> EllipticCurve.Ed25519Slip0010
Blockchain.HederaTestnet -> EllipticCurve.Ed25519Slip0010
Blockchain.Aurora -> EllipticCurve.Secp256k1
Blockchain.AuroraTestnet -> EllipticCurve.Secp256k1
Blockchain.Areon -> EllipticCurve.Secp256k1
Blockchain.AreonTestnet -> EllipticCurve.Secp256k1
Blockchain.PulseChain -> EllipticCurve.Secp256k1
Blockchain.PulseChainTestnet -> EllipticCurve.Secp256k1
Blockchain.ZkSyncEra -> EllipticCurve.Secp256k1
Blockchain.ZkSyncEraTestnet -> EllipticCurve.Secp256k1
Blockchain.Nexa -> EllipticCurve.Secp256k1
Blockchain.NexaTestnet -> EllipticCurve.Secp256k1
Blockchain.Moonbeam -> EllipticCurve.Secp256k1
Blockchain.MoonbeamTestnet -> EllipticCurve.Secp256k1
Blockchain.Manta -> EllipticCurve.Secp256k1
Blockchain.MantaTestnet -> EllipticCurve.Secp256k1
Blockchain.PolygonZkEVM -> EllipticCurve.Secp256k1
Blockchain.PolygonZkEVMTestnet -> EllipticCurve.Secp256k1
Blockchain.Radiant -> EllipticCurve.Secp256k1
Blockchain.Base -> EllipticCurve.Secp256k1
Blockchain.BaseTestnet -> EllipticCurve.Secp256k1
Blockchain.Moonriver -> EllipticCurve.Secp256k1
Blockchain.MoonriverTestnet -> EllipticCurve.Secp256k1
Blockchain.Mantle -> EllipticCurve.Secp256k1
Blockchain.MantleTestnet -> EllipticCurve.Secp256k1
Blockchain.Fact0rn -> EllipticCurve.Secp256k1
Blockchain.Flare -> EllipticCurve.Secp256k1
Blockchain.FlareTestnet -> EllipticCurve.Secp256k1
Blockchain.Taraxa -> EllipticCurve.Secp256k1
Blockchain.TaraxaTestnet -> EllipticCurve.Secp256k1
Blockchain.Koinos -> EllipticCurve.Secp256k1
Blockchain.KoinosTestnet -> EllipticCurve.Secp256k1
Blockchain.Joystream -> EllipticCurve.Ed25519Slip0010
Blockchain.Bittensor -> EllipticCurve.Ed25519Slip0010
Blockchain.Filecoin -> EllipticCurve.Secp256k1
Blockchain.Blast -> EllipticCurve.Secp256k1
Blockchain.BlastTestnet -> EllipticCurve.Secp256k1
Blockchain.Cyber -> EllipticCurve.Secp256k1
Blockchain.CyberTestnet -> EllipticCurve.Secp256k1
Blockchain.InternetComputer -> EllipticCurve.Secp256k1
Blockchain.Sui -> EllipticCurve.Ed25519Slip0010
Blockchain.SuiTestnet -> EllipticCurve.Ed25519Slip0010
Blockchain.EnergyWebChain -> EllipticCurve.Secp256k1
Blockchain.EnergyWebChainTestnet -> EllipticCurve.Secp256k1
Blockchain.EnergyWebX -> EllipticCurve.Ed25519Slip0010
Blockchain.EnergyWebXTestnet -> EllipticCurve.Ed25519Slip0010
Blockchain.Casper -> EllipticCurve.Secp256k1
Blockchain.CasperTestnet -> EllipticCurve.Secp256k1
Blockchain.Core -> EllipticCurve.Secp256k1
Blockchain.CoreTestnet -> EllipticCurve.Secp256k1
Blockchain.Xodex -> EllipticCurve.Secp256k1
Blockchain.Canxium -> EllipticCurve.Secp256k1
Blockchain.Chiliz -> EllipticCurve.Secp256k1
Blockchain.ChilizTestnet -> EllipticCurve.Secp256k1
Blockchain.Clore -> EllipticCurve.Secp256k1
Blockchain.VanarChain -> EllipticCurve.Secp256k1
Blockchain.VanarChainTestnet -> EllipticCurve.Secp256k1
Blockchain.OdysseyChain -> EllipticCurve.Secp256k1
Blockchain.OdysseyChainTestnet -> EllipticCurve.Secp256k1
Blockchain.Bitrock -> EllipticCurve.Secp256k1
Blockchain.BitrockTestnet -> EllipticCurve.Secp256k1
Blockchain.Sonic -> EllipticCurve.Secp256k1
Blockchain.SonicTestnet -> EllipticCurve.Secp256k1
Blockchain.ApeChain -> EllipticCurve.Secp256k1
Blockchain.ApeChainTestnet -> EllipticCurve.Secp256k1
Blockchain.KaspaTestnet -> EllipticCurve.Secp256k1
Blockchain.Alephium -> EllipticCurve.Secp256k1
Blockchain.AlephiumTestnet -> EllipticCurve.Secp256k1
Blockchain.Scroll -> EllipticCurve.Secp256k1
Blockchain.ScrollTestnet -> EllipticCurve.Secp256k1
Blockchain.ZkLinkNova -> EllipticCurve.Secp256k1
Blockchain.ZkLinkNovaTestnet -> EllipticCurve.Secp256k1
Blockchain.Pepecoin -> EllipticCurve.Secp256k1
Blockchain.PepecoinTestnet -> EllipticCurve.Secp256k1
}
}
}

View file

@ -1,157 +0,0 @@
package com.tangem.domain.common.extensions
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
import com.tangem.common.card.EllipticCurve
import com.tangem.common.card.FirmwareVersion
import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.configs.CardConfig
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.wallet.UserWallet
// TODO: refactor [REDACTED_JIRA]
/**
[REDACTED_AUTHOR]
*/
val FirmwareVersion.Companion.SolanaTokensAvailable
get() = FirmwareVersion(4, 52)
fun UserWallet.supportedBlockchains(excludedBlockchains: ExcludedBlockchains): List<Blockchain> {
return when (this) {
is UserWallet.Cold -> {
scanResponse.card.supportedBlockchains(
excludedBlockchains = excludedBlockchains,
cardTypesResolver = this.cardTypesResolver,
)
}
is UserWallet.Hot -> {
Blockchain.entries.filter { it !in excludedBlockchains }
}
}
}
fun CardDTO.supportedBlockchains(
cardTypesResolver: CardTypesResolver,
excludedBlockchains: ExcludedBlockchains,
): List<Blockchain> {
val supportedBlockchains = if (firmwareVersion < FirmwareVersion.MultiWalletAvailable) {
Blockchain.fromCurve(EllipticCurve.Secp256k1).toMutableList()
} else if (!cardTypesResolver.isWallet2() && !cardTypesResolver.isTangemWallet()) {
// need for old multiwallet that supports only secp256k1
wallets.flatMap { Blockchain.fromCurve(it.curve) }.distinct().toMutableList()
} else {
// multiwallet supports all blockchains, move this logic to config
Blockchain.entries.toMutableList()
}
return supportedBlockchains
.filter { isTestCard == it.isTestnet() }
.filter { it !in excludedBlockchains }
}
fun CardDTO.supportedTokens(
cardTypesResolver: CardTypesResolver,
excludedBlockchains: ExcludedBlockchains,
): List<Blockchain> {
val tokensSupportedByBlockchain = supportedBlockchains(cardTypesResolver, excludedBlockchains)
.filter { it.canHandleTokens() }
.toMutableList()
val tokensSupportedByCard = when {
firmwareVersion >= FirmwareVersion.SolanaTokensAvailable -> tokensSupportedByBlockchain
else -> {
tokensSupportedByBlockchain.apply {
remove(Blockchain.Solana)
remove(Blockchain.SolanaTestnet)
}
}
}
return tokensSupportedByCard.filter { isTestCard == it.isTestnet() }
}
fun UserWallet.canHandleToken(supportedTokens: List<Blockchain>, blockchain: Blockchain): Boolean {
return when (this) {
is UserWallet.Cold -> {
scanResponse.card.canHandleToken(
supportedTokens = supportedTokens,
blockchain = blockchain,
cardTypesResolver = scanResponse.cardTypesResolver,
)
}
is UserWallet.Hot -> blockchain in supportedTokens
}
}
fun UserWallet.canHandleToken(blockchain: Blockchain, excludedBlockchains: ExcludedBlockchains): Boolean {
return when (this) {
is UserWallet.Cold -> {
scanResponse.card.canHandleToken(
blockchain = blockchain,
excludedBlockchains = excludedBlockchains,
cardTypesResolver = scanResponse.cardTypesResolver,
)
}
is UserWallet.Hot -> blockchain !in excludedBlockchains
}
}
/**
* The same as [CardDTO.supportedTokens] but with supportedTokens input, if previously calculated
*/
fun CardDTO.canHandleToken(
supportedTokens: List<Blockchain>,
blockchain: Blockchain,
cardTypesResolver: CardTypesResolver,
): Boolean {
val cardConfig = CardConfig.createConfig(this)
val primaryCurveForBlockchain = cardConfig.primaryCurve(blockchain)
val isContainsBlockchain = supportedTokens.contains(blockchain)
val isWalletForCurveExists = wallets.any { it.curve == primaryCurveForBlockchain }
// fixme: check for first wallets with 1 curve and remove condition
return if (cardTypesResolver.isTangemWallet() || cardTypesResolver.isWallet2()) {
// if there's no wallet on card for blockchain with given curve
isContainsBlockchain && isWalletForCurveExists
} else {
isContainsBlockchain
}
}
fun CardDTO.canHandleToken(
blockchain: Blockchain,
cardTypesResolver: CardTypesResolver,
excludedBlockchains: ExcludedBlockchains,
): Boolean {
val cardConfig = CardConfig.createConfig(this)
val primaryCurveForBlockchain = cardConfig.primaryCurve(blockchain)
val isContainsBlockchain = blockchain in supportedTokens(cardTypesResolver, excludedBlockchains)
val isWalletForCurveExists = wallets.any { it.curve == primaryCurveForBlockchain }
// fixme: check for first wallets with 1 curve and remove condition
return if (cardTypesResolver.isTangemWallet() || cardTypesResolver.isWallet2()) {
// if there's no wallet on card for blockchain with given curve
isContainsBlockchain && isWalletForCurveExists
} else {
isContainsBlockchain
}
}
fun CardDTO.canHandleBlockchain(
blockchain: Blockchain,
cardTypesResolver: CardTypesResolver,
excludedBlockchains: ExcludedBlockchains,
): Boolean {
val cardConfig = CardConfig.createConfig(this)
val primaryCurveForBlockchain = cardConfig.primaryCurve(blockchain)
val isContainsBlockchain =
blockchain in supportedBlockchains(cardTypesResolver, excludedBlockchains)
val isWalletForCurveExists = wallets.any { it.curve == primaryCurveForBlockchain }
// fixme: check for first wallets with 1 curve and remove condition
return if (cardTypesResolver.isTangemWallet() || cardTypesResolver.isWallet2()) {
// if there's no wallet on card for blockchain with given curve
isContainsBlockchain && isWalletForCurveExists
} else {
isContainsBlockchain
}
}

View file

@ -1,151 +0,0 @@
package com.tangem.domain.common.extensions
import com.tangem.blockchain.blockchains.cardano.CardanoUtils
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toMapKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
import com.tangem.domain.common.configs.CardConfig
import com.tangem.domain.common.configs.Wallet2CardConfig
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
fun WalletManagerFactory.makeWalletManagerForApp(
scanResponse: ScanResponse,
blockchain: Blockchain,
derivationParams: DerivationParams?,
): WalletManager? {
val card = scanResponse.card
val cardConfig = CardConfig.createConfig(card)
if (card.isTestCard && blockchain.getTestnetVersion() == null) return null
val supportedCurves = blockchain.getSupportedCurves()
val wallets = card.wallets.filter { wallet -> supportedCurves.contains(wallet.curve) }
val wallet = selectWallet(
wallets = wallets,
cardConfig = cardConfig,
blockchain = blockchain,
) ?: return null
val environmentBlockchain =
if (card.isTestCard) blockchain.getTestnetVersion()!! else blockchain
val seedKey = wallet.extendedPublicKey
return when {
scanResponse.cardTypesResolver.isTangemTwins() && scanResponse.secondTwinPublicKey != null -> {
createTwinWalletManager(
walletPublicKey = wallet.publicKey,
pairPublicKey = scanResponse.secondTwinPublicKey!!.hexToBytes(),
blockchain = environmentBlockchain,
curve = wallet.curve,
)
}
scanResponse.card.settings.isHDWalletAllowed && seedKey != null && derivationParams != null -> {
val derivedKeys = scanResponse.derivedKeys[wallet.publicKey.toMapKey()]
val derivationPath = derivationParams.getPath(blockchain)
val publicKey = makePublicKey(
seedKey = wallet.publicKey,
blockchain = blockchain,
derivationPath = derivationPath ?: return null,
derivedWalletKeys = derivedKeys ?: return null,
isWallet2 = scanResponse.cardTypesResolver.isWallet2(),
) ?: return null
createWalletManager(
blockchain = environmentBlockchain,
publicKey = publicKey,
curve = wallet.curve,
)
}
else -> {
createLegacyWalletManager(
blockchain = environmentBlockchain,
walletPublicKey = wallet.publicKey,
curve = wallet.curve,
)
}
}
}
fun makePublicKey(
seedKey: ByteArray,
blockchain: Blockchain,
derivationPath: DerivationPath,
derivedWalletKeys: Map<DerivationPath, ExtendedPublicKey>,
isWallet2: Boolean,
): Wallet.PublicKey? {
val derivedKey = derivedWalletKeys[derivationPath] ?: return null
val derivationKey = Wallet.HDKey(
path = derivationPath,
extendedPublicKey = derivedKey,
)
// we should generate second key for cardano
// because cardano address generation for wallet2 requires keys from 2 derivations
// https://developers.cardano.org/docs/get-started/cardano-serialization-lib/generating-keys/
if (blockchain == Blockchain.Cardano && isWallet2) {
val extendedDerivationPath = CardanoUtils.extendedDerivationPath(derivationPath)
val secondDerivedKey = derivedWalletKeys[extendedDerivationPath] ?: error("No derivation found")
val secondDerivationKey = Wallet.HDKey(secondDerivedKey, extendedDerivationPath)
return Wallet.PublicKey(
seedKey = seedKey,
derivationType = Wallet.PublicKey.DerivationType.Double(derivationKey, secondDerivationKey),
)
}
return Wallet.PublicKey(
seedKey = seedKey,
derivationType = Wallet.PublicKey.DerivationType.Plain(derivationKey),
)
}
private fun getDerivationParams(card: CardDTO): DerivationParams? {
return if (!card.settings.isHDWalletAllowed) {
null
} else if (card.useOldStyleDerivation) {
DerivationParams.Default(DerivationStyle.LEGACY)
} else {
DerivationParams.Default(DerivationStyle.NEW)
}
}
fun WalletManagerFactory.makePrimaryWalletManager(scanResponse: ScanResponse): WalletManager? {
val blockchain = if (scanResponse.card.isTestCard) {
scanResponse.cardTypesResolver.getBlockchain().getTestnetVersion() ?: return null
} else {
scanResponse.cardTypesResolver.getBlockchain()
}
val derivationParams = getDerivationParams(scanResponse.card)
return makeWalletManagerForApp(
scanResponse = scanResponse,
blockchain = blockchain,
derivationParams = derivationParams,
)
}
private fun selectWallet(
wallets: List<CardDTO.Wallet>,
cardConfig: CardConfig,
blockchain: Blockchain,
): CardDTO.Wallet? {
return if (cardConfig is Wallet2CardConfig) {
val primaryCurve = cardConfig.primaryCurve(blockchain)
wallets.firstOrNull { it.curve == primaryCurve }
} else {
when (wallets.size) {
0 -> null
1 -> wallets[0]
else -> wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: wallets[0]
}
}
}

View file

@ -1,105 +0,0 @@
package com.tangem.domain.common.util
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.toMapKey
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.common.*
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
import com.tangem.domain.common.TapWorkarounds.isTestCard
import com.tangem.domain.common.configs.CardConfig
import com.tangem.domain.common.configs.Wallet2CardConfig
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWallet
val ScanResponse.cardTypesResolver: CardTypesResolver
get() = TangemCardTypesResolver(
card = card,
productType = productType,
walletData = walletData,
)
val UserWallet.derivationStyleProvider: DerivationStyleProvider
get() = when (this) {
is UserWallet.Cold -> this.scanResponse.derivationStyleProvider
is UserWallet.Hot -> TangemHotDerivationStyleProvider()
}
val ScanResponse.derivationStyleProvider: DerivationStyleProvider
get() = card.derivationStyleProvider
val CardDTO.derivationStyleProvider: DerivationStyleProvider
get() = TangemDerivationStyleProvider(this)
val UserWallet.Cold.cardTypesResolver: CardTypesResolver
get() = scanResponse.cardTypesResolver
fun ScanResponse.twinsIsTwinned(): Boolean = card.isTangemTwins && walletData != null && secondTwinPublicKey != null
fun ScanResponse.hasDerivation(blockchain: Blockchain, rawDerivationPath: String): Boolean {
return hasDerivation(blockchain, DerivationPath(rawDerivationPath))
}
private fun ScanResponse.hasDerivation(blockchain: Blockchain, derivationPath: DerivationPath): Boolean {
val isTestnet = card.isTestCard || blockchain.isTestnet()
val config = CardConfig.createConfig(card)
return if (config is Wallet2CardConfig) {
// new logic for wallet2
val primaryCurve = config.primaryCurve(blockchain)
primaryCurve?.let { hasDerivation(it, derivationPath) } == true
} else {
// leave logic for legacy wallets
when {
Blockchain.secp256k1Blockchains(isTestnet).contains(blockchain) -> {
hasDerivation(EllipticCurve.Secp256k1, derivationPath)
}
Blockchain.ed25519Blockchains(isTestnet).contains(blockchain) -> {
hasDerivation(EllipticCurve.Ed25519, derivationPath)
}
else -> false
}
}
}
private fun ScanResponse.hasDerivation(curve: EllipticCurve, derivationPath: DerivationPath): Boolean {
val foundWallet = card.wallets.firstOrNull { it.curve == curve }
?: return false
val extendedPublicKeysMap = derivedKeys[foundWallet.publicKey.toMapKey()] ?: return false
val extendedPublicKey = extendedPublicKeysMap[derivationPath]
return extendedPublicKey != null
}
/**
* Get total cards count in wallets set for this [ScanResponse] card
*
* @return null if wallet is not multi-currency or total cards count
*/
fun ScanResponse.getCardsCount(): Int? {
if (cardTypesResolver.isTangemTwins()) return 2
if (!cardTypesResolver.isMultiwalletAllowed()) return null
return when (val status = card.backupStatus) {
is CardDTO.BackupStatus.Active -> status.cardCount + 1
is CardDTO.BackupStatus.NoBackup,
is CardDTO.BackupStatus.CardLinked,
null, // Multi-currency wallet without backup function. Example, 4.12
-> 1
}
}
/**
* Get backup cards count for this [ScanResponse] card
*
* @return null if wallet is not multi-currency or total cards count
*/
fun ScanResponse.getBackupCardsCount(): Int? {
return if (cardTypesResolver.isMultiwalletAllowed()) {
when (val status = card.backupStatus) {
is CardDTO.BackupStatus.Active -> status.cardCount
else -> 0
}
} else {
null
}
}

View file

@ -1,17 +0,0 @@
package com.tangem.domain.common.util
import com.tangem.domain.models.wallet.UserWallet
/**
* Get total cards count in wallets set for a card that was saved in [UserWallet]
*
* @return null if wallet is not multi-currency or total cards count
*/
fun UserWallet.Cold.getCardsCount(): Int? = scanResponse.getCardsCount()
/**
* Get backup cards count for a card that was saved in [UserWallet]
*
* @return null if wallet is not multi-currency or total cards count
*/
fun UserWallet.Cold.getBackupCardsCount(): Int? = scanResponse.getBackupCardsCount()

View file

@ -1,30 +0,0 @@
package com.tangem.domain.common.visa
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.derivation.DerivationStyle
import com.tangem.common.card.FirmwareVersion
import com.tangem.domain.models.scan.CardDTO
private const val VISA_BATCH_START = "AE"
private const val VISA_BATCH_START_2 = "FFFC"
object VisaUtilities {
const val tokenId = "tether"
val visaBlockchain = Blockchain.Polygon
val visaDefaultDerivationPath
get() = visaBlockchain.derivationPath(DerivationStyle.V3)
fun visaDefaultDerivationPath(style: DerivationStyle) = visaBlockchain.derivationPath(style)
fun isVisaCard(card: CardDTO): Boolean {
return isVisaCard(card.firmwareVersion.doubleValue, card.batchId)
}
fun isVisaCard(firmwareVersion: Double, batchId: String): Boolean {
return firmwareVersion in FirmwareVersion.visaRange &&
(batchId.startsWith(VISA_BATCH_START) || batchId.startsWith(VISA_BATCH_START_2))
}
}

View file

@ -1,64 +0,0 @@
package com.tangem.domain.common.visa
import arrow.core.Either
import arrow.core.raise.catch
import arrow.core.raise.either
import com.tangem.blockchain.common.address.Address
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.card.EllipticCurve
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.visa.error.VisaActivationError
object VisaWalletPublicKeyUtility {
fun validateExtendedPublicKey(
targetAddress: String,
extendedPublicKey: ExtendedPublicKey,
): Either<VisaActivationError, Unit> = either {
validatePublicKey(
targetAddress = targetAddress,
publicKey = extendedPublicKey.publicKey,
).bind()
}
fun findKeyWithoutDerivation(targetAddress: String, card: CardDTO): Either<VisaActivationError, ByteArray> =
either {
val wallet = findWalletOnSecp256k1(card).bind()
validatePublicKey(
targetAddress = targetAddress,
publicKey = wallet.publicKey,
).bind()
wallet.publicKey
}
fun generateAddressOnSecp256k1(walletPublicKey: ByteArray): Either<VisaActivationError, Address> = either {
val addresses = catch(
block = {
VisaUtilities.visaBlockchain.makeAddresses(
walletPublicKey = walletPublicKey,
pairPublicKey = null,
curve = EllipticCurve.Secp256k1,
)
},
catch = { raise(VisaActivationError.FailedToCreateAddress) },
)
addresses.firstOrNull { it.type == AddressType.Default } ?: raise(VisaActivationError.FailedToCreateAddress)
}
private fun findWalletOnSecp256k1(card: CardDTO): Either<VisaActivationError, CardDTO.Wallet> = either {
card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: raise(VisaActivationError.MissingWallet)
}
private fun validatePublicKey(targetAddress: String, publicKey: ByteArray): Either<VisaActivationError, Unit> =
either {
val address = generateAddressOnSecp256k1(publicKey).bind()
if (address.value.lowercase() != targetAddress.lowercase()) {
raise(VisaActivationError.AddressNotMatched)
}
}
}

View file

@ -1,748 +0,0 @@
package com.tangem.domain.walletmanager
import arrow.core.Either
import arrow.core.raise.either
import arrow.core.raise.ensureNotNull
import arrow.core.right
import com.tangem.blockchain.blockchains.solana.RentProvider
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.address.Address
import com.tangem.blockchain.common.address.AddressType
import com.tangem.blockchain.common.address.EstimationFeeAddressFactory
import com.tangem.blockchain.common.pagination.Page
import com.tangem.blockchain.common.smartcontract.SmartContractCallDataProviderFactory
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchain.common.trustlines.AssetRequirementsManager
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.blockchain.nft.models.NFTAsset
import com.tangem.blockchain.nft.models.NFTCollection
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryRequest
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult
import com.tangem.blockchainsdk.utils.toBlockchain
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.datasource.local.walletmanager.WalletManagersStore
import com.tangem.domain.common.util.hasDerivation
import com.tangem.domain.demo.DemoConfig
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.TxInfo
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.domain.txhistory.models.PaginationWrapper
import com.tangem.domain.txhistory.models.TxHistoryState
import com.tangem.domain.walletmanager.model.RentData
import com.tangem.domain.walletmanager.model.SmartContractMethod
import com.tangem.domain.walletmanager.model.TokenInfo
import com.tangem.domain.walletmanager.utils.*
import com.tangem.domain.walletmanager.utils.WalletManagerFactory
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.math.BigDecimal
import java.util.EnumSet
@Suppress("LargeClass", "TooManyFunctions")
// FIXME: Move to its own module and make internal
@Deprecated("Inject the WalletManagerFacade interface using DI instead")
class DefaultWalletManagersFacade(
private val walletManagersStore: WalletManagersStore,
private val userWalletsStore: UserWalletsStore,
private val assetLoader: AssetLoader,
private val dispatchers: CoroutineDispatcherProvider,
blockchainSDKFactory: BlockchainSDKFactory,
) : WalletManagersFacade {
private val demoConfig by lazy { DemoConfig() }
private val resultFactory by lazy { UpdateWalletManagerResultFactory() }
private val walletManagerFactory by lazy { WalletManagerFactory(blockchainSDKFactory) }
private val sdkTokenConverter by lazy { SdkTokenConverter() }
private val txHistoryStateConverter by lazy { SdkTransactionHistoryStateConverter() }
private val sdkPageConverter by lazy { SdkPageConverter() }
private val cryptoCurrencyTypeConverter by lazy { CryptoCurrencyTypeConverter() }
private val requirementsConditionConverter by lazy { SdkRequirementsConditionConverter() }
private val estimationFeeAddressFactory by lazy { EstimationFeeAddressFactory() }
private val initMutex = Mutex()
override suspend fun update(
userWalletId: UserWalletId,
network: Network,
extraTokens: Set<CryptoCurrency.Token>,
): UpdateWalletManagerResult {
val userWallet = getUserWallet(userWalletId)
val blockchain = network.toBlockchain()
val derivationPath = network.derivationPath.value
return getAndUpdateWalletManager(userWallet, blockchain, derivationPath, extraTokens)
}
override suspend fun remove(userWalletId: UserWalletId, networks: Set<Network>) {
if (networks.isEmpty()) return
val blockchainsToDerivationPaths = networks.map {
it.toBlockchain() to it.derivationPath.value
}
withContext(dispatchers.io) {
walletManagersStore.remove(userWalletId) { walletManager ->
val wallet = walletManager.wallet
val blockchainToDerivationPath = wallet.blockchain to wallet.publicKey.derivationPath?.rawPath
blockchainToDerivationPath in blockchainsToDerivationPaths
}
}
}
override suspend fun removeTokens(userWalletId: UserWalletId, tokens: Set<CryptoCurrency.Token>) {
if (tokens.isEmpty()) return
tokens
.groupBy(CryptoCurrency.Token::network)
.forEach { (network, networkTokens) ->
removeTokens(
userWalletId = userWalletId,
network = network,
networkTokens = sdkTokenConverter.convertList(networkTokens),
)
}
}
override suspend fun removeTokensByTokenInfo(userWalletId: UserWalletId, tokenInfos: Set<TokenInfo>) {
if (tokenInfos.isEmpty()) return
tokenInfos
.groupBy { it.network }
.forEach { (network, tokenInfoList) ->
removeTokens(
userWalletId = userWalletId,
network = network,
networkTokens = tokenInfoList.map {
Token(
name = it.name,
symbol = it.symbol,
contractAddress = it.contractAddress,
decimals = it.decimals,
id = it.id,
)
},
)
}
}
private suspend fun removeTokens(userWalletId: UserWalletId, network: Network, networkTokens: List<Token>) {
withContext(dispatchers.io) {
val walletManager = walletManagersStore.getSyncOrNull(
userWalletId = userWalletId,
blockchain = network.toBlockchain(),
derivationPath = network.derivationPath.value,
) ?: return@withContext
networkTokens.forEach { token ->
walletManager.removeToken(token)
}
walletManagersStore.store(userWalletId, walletManager)
}
}
override suspend fun updatePendingTransactions(
userWalletId: UserWalletId,
network: Network,
): UpdateWalletManagerResult {
val userWallet = getUserWallet(userWalletId)
val blockchain = network.toBlockchain()
val derivationPath = network.derivationPath.value
if (derivationPath != null &&
userWallet is UserWallet.Cold &&
!userWallet.scanResponse.hasDerivation(blockchain, derivationPath)
) {
Timber.w("Derivation missed for: $blockchain")
return UpdateWalletManagerResult.MissedDerivation
}
val walletManager = getOrCreateWalletManager(userWalletId, blockchain, derivationPath)
if (walletManager == null || blockchain == Blockchain.Unknown) {
Timber.w("Unable to get a wallet manager for blockchain: $blockchain")
return UpdateWalletManagerResult.Unreachable()
}
return getLastWalletManagerResult(walletManager)
}
override suspend fun getExploreUrl(
userWalletId: UserWalletId,
network: Network,
addressType: AddressType,
contractAddress: String?,
): String {
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
requireNotNull(walletManager) {
"Unable to get a wallet manager for blockchain: $blockchain"
}
val address = walletManager
.wallet
.addresses
.find { it.type == addressType }
?.value ?: walletManager.wallet.address
return blockchain.getExploreUrl(address, contractAddress)
}
override suspend fun getTxHistoryState(userWalletId: UserWalletId, currency: CryptoCurrency): TxHistoryState {
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = currency.network,
)
requireNotNull(walletManager) {
"Unable to get a wallet manager for blockchain: ${currency.network}"
}
return walletManager
.getTransactionHistoryState(
address = walletManager.wallet.address,
filterType = when (currency) {
is CryptoCurrency.Coin -> TransactionHistoryRequest.FilterType.Coin
is CryptoCurrency.Token -> {
val blockchainToken = Token(
name = currency.name,
symbol = currency.symbol,
contractAddress = currency.contractAddress,
decimals = currency.decimals,
id = currency.id.rawCurrencyId?.value,
)
TransactionHistoryRequest.FilterType.Contract(blockchainToken)
}
},
)
.let(txHistoryStateConverter::convert)
}
override suspend fun getTxHistoryItems(
userWalletId: UserWalletId,
currency: CryptoCurrency,
page: Page,
pageSize: Int,
): PaginationWrapper<TxInfo> {
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = currency.network,
)
requireNotNull(walletManager) {
"Unable to get a wallet manager for blockchain: ${currency.network}"
}
val itemsResult = walletManager.getTransactionsHistory(
request = TransactionHistoryRequest(
address = walletManager.wallet.address,
decimals = currency.decimals,
page = page,
pageSize = pageSize,
filterType = when (currency) {
is CryptoCurrency.Coin -> TransactionHistoryRequest.FilterType.Coin
is CryptoCurrency.Token -> {
val blockchainToken = Token(
name = currency.name,
symbol = currency.symbol,
contractAddress = currency.contractAddress,
decimals = currency.decimals,
id = currency.id.rawCurrencyId?.value,
)
TransactionHistoryRequest.FilterType.Contract(blockchainToken)
}
},
),
)
return when (itemsResult) {
is Result.Success -> PaginationWrapper(
currentPage = sdkPageConverter.convert(page),
nextPage = sdkPageConverter.convert(itemsResult.data.nextPage),
items = SdkTransactionHistoryItemConverter(smartContractMethods = readSmartContractMethods())
.convertList(itemsResult.data.items),
)
is Result.Failure -> error(itemsResult.error.message ?: itemsResult.error.customMessage)
}
}
private fun getUserWallet(userWalletId: UserWalletId) = userWalletsStore.getSyncStrict(userWalletId)
private suspend fun getAndUpdateWalletManager(
userWallet: UserWallet,
blockchain: Blockchain,
derivationPath: String?,
extraTokens: Set<CryptoCurrency.Token>,
): UpdateWalletManagerResult {
if (derivationPath != null &&
userWallet is UserWallet.Cold &&
!userWallet.scanResponse.hasDerivation(blockchain, derivationPath)
) {
Timber.w("Derivation missed for: $blockchain")
return UpdateWalletManagerResult.MissedDerivation
}
val walletManager = getOrCreateWalletManager(
userWalletId = userWallet.walletId,
blockchain = blockchain,
derivationPath = derivationPath,
)
if (walletManager == null || blockchain == Blockchain.Unknown) {
Timber.w("Unable to create or find a wallet manager for blockchain: $blockchain")
return UpdateWalletManagerResult.Unreachable()
}
updateWalletManagerTokensIfNeeded(walletManager, extraTokens)
return try {
if (userWallet is UserWallet.Cold && demoConfig.isDemoCardId(userWallet.scanResponse.card.cardId)) {
updateDemoWalletManager(walletManager)
} else {
updateWalletManager(walletManager)
}
} finally {
walletManagersStore.store(userWallet.walletId, walletManager)
}
}
private fun updateDemoWalletManager(walletManager: WalletManager): UpdateWalletManagerResult {
val amount = demoConfig.getBalance(walletManager.wallet.blockchain)
walletManager.wallet.setAmount(amount)
return resultFactory.getDemoResult(walletManager, amount)
}
private suspend fun updateWalletManager(walletManager: WalletManager): UpdateWalletManagerResult {
return try {
walletManager.update()
resultFactory.getResult(walletManager)
} catch (e: BlockchainSdkError.AccountNotFound) {
resultFactory.getNoAccountResult(
walletManager = walletManager,
customMessage = e.customMessage,
amountToCreateAccount = e.amountToCreateAccount,
)
} catch (e: Throwable) {
Timber.w(e, "Unable to update a wallet manager for: ${walletManager.wallet.blockchain}")
resultFactory.getUnreachableResult(walletManager)
}
}
private fun getLastWalletManagerResult(walletManager: WalletManager): UpdateWalletManagerResult {
return try {
resultFactory.getResult(walletManager)
} catch (e: BlockchainSdkError.AccountNotFound) {
resultFactory.getNoAccountResult(
walletManager = walletManager,
customMessage = e.customMessage,
amountToCreateAccount = e.amountToCreateAccount,
)
} catch (e: Throwable) {
Timber.w(e, "Unable to update a wallet manager for: ${walletManager.wallet.blockchain}")
resultFactory.getUnreachableResult(walletManager)
}
}
@Deprecated("Will be removed in future")
override suspend fun getOrCreateWalletManager(
userWalletId: UserWalletId,
blockchain: Blockchain,
derivationPath: String?,
): WalletManager? {
initMutex.withLock {
val userWallet = getUserWallet(userWalletId)
var walletManager = walletManagersStore.getSyncOrNull(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = derivationPath,
)
val path = derivationPath?.let { DerivationPath(rawPath = it) }
if (walletManager == null) {
when (userWallet) {
is UserWallet.Hot -> {
walletManager = walletManagerFactory.createWalletManagerForHot(
hotWallet = userWallet,
blockchain = blockchain,
derivationPath = path,
)
}
is UserWallet.Cold -> {
walletManager = walletManagerFactory.createWalletManager(
scanResponse = userWallet.scanResponse,
blockchain = blockchain,
derivationPath = path,
)
}
}
walletManager ?: return null
walletManagersStore.store(userWalletId, walletManager)
}
return walletManager
}
}
@Deprecated("Will be removed in future")
override suspend fun getOrCreateWalletManager(userWalletId: UserWalletId, network: Network): WalletManager? {
val blockchain = network.toBlockchain()
return getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
)
}
@Deprecated("Will be removed in future")
override suspend fun getStoredWalletManagers(userWalletId: UserWalletId): List<WalletManager> {
return walletManagersStore.getAllSync(userWalletId)
}
override suspend fun getDefaultAddress(userWalletId: UserWalletId, network: Network): String? {
return getAddresses(userWalletId, network)
.firstOrNull { it.type == AddressType.Default }
?.value
}
override suspend fun getAddresses(userWalletId: UserWalletId, network: Network): Set<Address> {
val manager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
return manager?.wallet?.addresses.orEmpty()
}
override suspend fun getRentInfo(userWalletId: UserWalletId, network: Network): RentData? {
val manager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
if (manager !is RentProvider) return null
return when (val result = manager.minimalBalanceForRentExemption()) {
is Result.Success -> {
RentData(manager.rentAmount(), result.data)
}
is Result.Failure -> null
}
}
@Deprecated("Will be removed in future")
override fun getAll(userWalletId: UserWalletId): Flow<List<WalletManager>> {
return walletManagersStore.getAll(userWalletId)
}
override suspend fun validateSignatureCount(
userWalletId: UserWalletId,
network: Network,
signedHashes: Int,
): Either<Throwable, Unit> {
return either {
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
val validator = ensureNotNull(walletManager as? SignatureCountValidator) {
raise(IllegalStateException("Wallet manager is not a SignatureCountValidator"))
}
when (val result = validator.validateSignatureCount(signedHashes)) {
is SimpleResult.Failure -> raise(result.error)
is SimpleResult.Success -> Unit.right()
}
}
}
@Deprecated("Will be removed in future")
override suspend fun getFee(
amount: Amount,
destination: String,
userWalletId: UserWalletId,
network: Network,
): Result<TransactionFee>? = withContext(dispatchers.io) {
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
(walletManager as? TransactionSender)?.getFee(
amount = amount,
destination = destination,
)
}
override suspend fun estimateFee(
amount: Amount,
userWalletId: UserWalletId,
network: Network,
): Result<TransactionFee>? = withContext(dispatchers.io) {
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
)
val destination = estimationFeeAddressFactory.makeAddress(blockchain)
val callData = if (amount.type is AmountType.Token) {
SmartContractCallDataProviderFactory.getTokenTransferCallData(
destinationAddress = destination,
amount = amount,
blockchain = blockchain,
)
} else {
null
}
(walletManager as? TransactionSender)?.estimateFee(
amount = amount,
destination = destination,
callData = callData,
)
}
@Deprecated("Will be removed in future")
override suspend fun validateTransaction(
amount: Amount,
fee: Amount?,
userWalletId: UserWalletId,
network: Network,
): EnumSet<TransactionError>? {
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
return walletManager?.validateTransaction(amount, fee)
}
@Deprecated("Will be removed in future")
override suspend fun createTransaction(
amount: Amount,
fee: Fee,
memo: String?,
destination: String,
userWalletId: UserWalletId,
network: Network,
): TransactionData? {
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
network = network,
)
return walletManager?.createTransaction(amount, fee, destination)
}
override suspend fun getRecentTransactions(userWalletId: UserWalletId, currency: CryptoCurrency): List<TxInfo> {
val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network)
if (walletManager == null) {
Timber.e("Unable to get a wallet manager for blockchain: ${currency.network.id}")
return emptyList()
}
val transactionDataConverter = TransactionDataToTxHistoryItemConverter(
walletAddresses = SdkAddressToAddressConverter.convertList(walletManager.wallet.addresses).toSet(),
feePaidCurrency = walletManager.wallet.blockchain.feePaidCurrency(),
)
return walletManager.wallet.recentTransactions
.filter { transaction ->
when (currency) {
is CryptoCurrency.Coin -> transaction.amount.type is AmountType.Coin
is CryptoCurrency.Token -> transaction.contractAddress == currency.contractAddress
}
}
.mapNotNull(transactionDataConverter::convert)
}
override suspend fun tokenBalance(
userWalletId: UserWalletId,
network: Network,
name: String,
symbol: String,
contractAddress: String,
decimals: Int,
id: String?,
): BigDecimal {
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
)
requireNotNull(walletManager) { "Unable to get a wallet manager for blockchain: $blockchain" }
return walletManager.wallet.fundsAvailable(
AmountType.Token(
token = Token(
name = name,
symbol = symbol,
contractAddress = contractAddress,
decimals = decimals,
id = id,
),
),
)
}
override suspend fun getAssetRequirements(
userWalletId: UserWalletId,
currency: CryptoCurrency,
): AssetRequirementsCondition? {
return withContext(dispatchers.io) {
val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network)
val currencyType = cryptoCurrencyTypeConverter.convert(currency)
if (walletManager !is AssetRequirementsManager) {
return@withContext null
}
val condition = walletManager.requirementsCondition(currencyType) ?: return@withContext null
requirementsConditionConverter.convert(condition)
}
}
override suspend fun fulfillRequirements(
userWalletId: UserWalletId,
currency: CryptoCurrency,
signer: TransactionSigner,
): SimpleResult {
return withContext(dispatchers.io) {
val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network)
val currencyType = cryptoCurrencyTypeConverter.convert(currency)
if (walletManager !is AssetRequirementsManager) {
return@withContext SimpleResult.Failure(
BlockchainSdkError.CustomError("WalletManager is not implemented AssetRequirementsManager"),
)
}
walletManager.fulfillRequirements(currencyType, signer)
}
}
override suspend fun discardRequirements(userWalletId: UserWalletId, currency: CryptoCurrency): SimpleResult {
return withContext(dispatchers.io) {
val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network)
val currencyType = cryptoCurrencyTypeConverter.convert(currency)
if (walletManager !is AssetRequirementsManager) {
return@withContext SimpleResult.Failure(
BlockchainSdkError.CustomError("WalletManager is not implemented AssetRequirementsManager"),
)
}
walletManager.discardRequirements(currencyType)
}
}
override suspend fun checkUtxoConsolidationAvailability(userWalletId: UserWalletId, network: Network): Boolean {
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
) ?: return false
return (walletManager as? UtxoBlockchainManager)?.allowConsolidation == true
}
override suspend fun getNFTCollections(userWalletId: UserWalletId, network: Network): List<NFTCollection> {
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
) ?: return emptyList()
val address = walletManager.wallet.address
return walletManager.getCollections(address)
}
override suspend fun getNFTAssets(
userWalletId: UserWalletId,
network: Network,
collectionIdentifier: NFTCollection.Identifier,
): List<NFTAsset> {
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
) ?: return emptyList()
val address = walletManager.wallet.address
return walletManager.getAssets(address, collectionIdentifier)
}
override suspend fun getNFTAsset(
userWalletId: UserWalletId,
network: Network,
collectionIdentifier: NFTCollection.Identifier,
assetIdentifier: NFTAsset.Identifier,
): NFTAsset? {
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
) ?: return null
return walletManager.getAsset(collectionIdentifier, assetIdentifier)
}
override suspend fun getNFTSalePrice(
userWalletId: UserWalletId,
network: Network,
collectionIdentifier: NFTCollection.Identifier,
assetIdentifier: NFTAsset.Identifier,
): NFTAsset.SalePrice? {
val blockchain = network.toBlockchain()
val walletManager = getOrCreateWalletManager(
userWalletId = userWalletId,
blockchain = blockchain,
derivationPath = network.derivationPath.value,
) ?: return null
return walletManager.getSalePrice(collectionIdentifier, assetIdentifier)
}
override suspend fun getNFTExploreUrl(network: Network, assetIdentifier: NFTAsset.Identifier): String? {
val blockchain = network.toBlockchain()
return blockchain.getNFTExploreUrl(assetIdentifier)
}
override suspend fun isAccountInitialized(userWalletId: UserWalletId, network: Network): Boolean {
val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = network)
val initializableAccountWalletManger = walletManager as? InitializableAccount ?: return true
return initializableAccountWalletManger.accountInitializationState == InitializableAccount.State.INITIALIZED
}
private fun updateWalletManagerTokensIfNeeded(walletManager: WalletManager, tokens: Set<CryptoCurrency.Token>) {
if (tokens.isEmpty()) return
val tokensToAdd = sdkTokenConverter
.convertList(tokens)
.filter { it !in walletManager.cardTokens }
walletManager.addTokens(tokensToAdd)
}
private suspend fun readSmartContractMethods(): Map<String, SmartContractMethod> {
return assetLoader.loadMap<SmartContractMethod>(fileName = "contract_methods")
}
}

View file

@ -1,283 +0,0 @@
package com.tangem.domain.walletmanager
import arrow.core.Either
import com.tangem.blockchain.blockchains.solana.RentProvider
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.address.Address
import com.tangem.blockchain.common.address.AddressType
import com.tangem.blockchain.common.pagination.Page
import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.blockchain.extensions.Result
import com.tangem.blockchain.extensions.SimpleResult
import com.tangem.blockchain.nft.models.NFTAsset
import com.tangem.blockchain.nft.models.NFTCollection
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.network.TxInfo
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.domain.txhistory.models.PaginationWrapper
import com.tangem.domain.txhistory.models.TxHistoryState
import com.tangem.domain.walletmanager.model.RentData
import com.tangem.domain.walletmanager.model.TokenInfo
import kotlinx.coroutines.flow.Flow
import java.math.BigDecimal
import java.util.EnumSet
// TODO: Move to its own module
/**
* A facade for managing wallets.
*/
@Suppress("TooManyFunctions")
interface WalletManagersFacade {
/**
* Updates the wallet manager associated with a user's wallet and network.
*
* @param userWalletId The ID of the user's wallet.
* @param network The network.
* @param extraTokens Additional tokens.
* @return The result of updating the wallet manager.
*/
suspend fun update(
userWalletId: UserWalletId,
network: Network,
extraTokens: Set<CryptoCurrency.Token>,
): UpdateWalletManagerResult
/**
* Removes the wallet managers associated with a user's wallet and networks.
*
* @param userWalletId The ID of the user's wallet.
* @param networks Set of networks
* */
suspend fun remove(userWalletId: UserWalletId, networks: Set<Network>)
suspend fun removeTokens(userWalletId: UserWalletId, tokens: Set<CryptoCurrency.Token>)
suspend fun removeTokensByTokenInfo(userWalletId: UserWalletId, tokenInfos: Set<TokenInfo>)
/**
* Returns [UpdateWalletManagerResult] with last pending transactions
*
* @param userWalletId The ID of the user's wallet.
* @param network The network.
* @return The result of updating the wallet manager.
*/
suspend fun updatePendingTransactions(userWalletId: UserWalletId, network: Network): UpdateWalletManagerResult
/**
* Returns network explorer URL of the wallet manager associated with a user's wallet and network.
*
* @param userWalletId The ID of the user's wallet.
* @param network The network.
* @param addressType Address type.
* @param contractAddress Contract address if currency is Token.
*
* @return The network explorer URL, maybe empty if the wallet manager was not found.
* */
suspend fun getExploreUrl(
userWalletId: UserWalletId,
network: Network,
addressType: AddressType,
contractAddress: String?,
): String
/**
* Returns transactions count
*
* @param userWalletId The ID of the user's wallet.
* @param currency currency.
*/
suspend fun getTxHistoryState(userWalletId: UserWalletId, currency: CryptoCurrency): TxHistoryState
/**
* Returns transaction history items wrapped to pagination
*
* @param userWalletId The ID of the user's wallet.
* @param currency currency.
* @param page Pagination page.
* @param pageSize Pagination size.
*/
suspend fun getTxHistoryItems(
userWalletId: UserWalletId,
currency: CryptoCurrency,
page: Page,
pageSize: Int,
): PaginationWrapper<TxInfo>
@Deprecated("Will be removed in future")
suspend fun getOrCreateWalletManager(
userWalletId: UserWalletId,
blockchain: Blockchain,
derivationPath: String?,
): WalletManager?
@Deprecated("Will be removed in future")
suspend fun getOrCreateWalletManager(userWalletId: UserWalletId, network: Network): WalletManager?
@Deprecated("Will be removed in future")
suspend fun getStoredWalletManagers(userWalletId: UserWalletId): List<WalletManager>
/**
* Returns default network address for selected wallet in given network
*
* @param userWalletId selected wallet id
* @param network network of currency
*/
suspend fun getDefaultAddress(userWalletId: UserWalletId, network: Network): String?
/** Returns list of all addresses for all currencies in selected wallet
*
* @param userWalletId selected wallet id
* @param network required to create wallet manager
*/
suspend fun getAddresses(userWalletId: UserWalletId, network: Network): Set<Address>
/**
* Returns info about rent if wallet manager implemented [RentProvider], otherwise null
*
* @param userWalletId selected wallet id
* @param network network of currency
*/
suspend fun getRentInfo(userWalletId: UserWalletId, network: Network): RentData?
@Deprecated("Will be removed in future")
fun getAll(userWalletId: UserWalletId): Flow<List<WalletManager>>
suspend fun validateSignatureCount(
userWalletId: UserWalletId,
network: Network,
signedHashes: Int,
): Either<Throwable, Unit>
/**
* Returns fee for transaction
*
* @param amount of transaction
* @param destination address
* @param userWalletId selected wallet id
* @param network network of currency
*/
@Deprecated("Will be removed in future")
suspend fun getFee(
amount: Amount,
destination: String,
userWalletId: UserWalletId,
network: Network,
): Result<TransactionFee>?
/**
* Returns estimated fee for transaction
*
* @param amount of transaction
* @param userWalletId selected wallet id
* @param network network of currency
*/
suspend fun estimateFee(amount: Amount, userWalletId: UserWalletId, network: Network): Result<TransactionFee>?
/**
* Validates transaction
*
* @param amount of transaction
* @param fee of transaction
* @param userWalletId selected wallet id
* @param network network of currency
*/
@Deprecated("Will be removed in future")
suspend fun validateTransaction(
amount: Amount,
fee: Amount?,
userWalletId: UserWalletId,
network: Network,
): EnumSet<TransactionError>?
/**
* Creates transaction [TransactionData]
*
* @param amount of transaction
* @param fee of transaction
* @param memo of transaction optional
* @param destination address
* @param userWalletId selected wallet id
* @param network network of currency
*/
@Suppress("LongParameterList")
@Deprecated("Will be removed in future")
suspend fun createTransaction(
amount: Amount,
fee: Fee,
memo: String?,
destination: String,
userWalletId: UserWalletId,
network: Network,
): TransactionData?
/** Get recent transactions of [userWalletId] for [currency] */
suspend fun getRecentTransactions(userWalletId: UserWalletId, currency: CryptoCurrency): List<TxInfo>
@Suppress("LongParameterList")
suspend fun tokenBalance(
userWalletId: UserWalletId,
network: Network,
name: String,
symbol: String,
contractAddress: String,
decimals: Int,
id: String? = null,
): BigDecimal
/**
* Get requirements for asset(currency)
* @return null if there's no requirement, otherwise [AssetRequirementsCondition].
*/
suspend fun getAssetRequirements(userWalletId: UserWalletId, currency: CryptoCurrency): AssetRequirementsCondition?
suspend fun fulfillRequirements(
userWalletId: UserWalletId,
currency: CryptoCurrency,
signer: TransactionSigner,
): SimpleResult
suspend fun discardRequirements(userWalletId: UserWalletId, currency: CryptoCurrency): SimpleResult
/**
* Indicates UTXO consolidation availability
*
* @param userWalletId selected user wallet
* @param network availability for network
*/
suspend fun checkUtxoConsolidationAvailability(userWalletId: UserWalletId, network: Network): Boolean
suspend fun getNFTCollections(userWalletId: UserWalletId, network: Network): List<NFTCollection>
suspend fun getNFTAssets(
userWalletId: UserWalletId,
network: Network,
collectionIdentifier: NFTCollection.Identifier,
): List<NFTAsset>
suspend fun getNFTAsset(
userWalletId: UserWalletId,
network: Network,
collectionIdentifier: NFTCollection.Identifier,
assetIdentifier: NFTAsset.Identifier,
): NFTAsset?
suspend fun getNFTSalePrice(
userWalletId: UserWalletId,
network: Network,
collectionIdentifier: NFTCollection.Identifier,
assetIdentifier: NFTAsset.Identifier,
): NFTAsset.SalePrice?
suspend fun getNFTExploreUrl(network: Network, assetIdentifier: NFTAsset.Identifier): String?
/**
* If wallet manager implements [InitializableAccount] then returns [InitializableAccount.isAccountInitialized]
* value. Otherwise always return true
*/
suspend fun isAccountInitialized(userWalletId: UserWalletId, network: Network): Boolean
}

View file

@ -1,11 +0,0 @@
package com.tangem.domain.walletmanager.model
import java.math.BigDecimal
/**
* Represents wallet blockchain rent
* @param rent Amount that will be charged in overtime if the blockchain does not have an amount greater than
* the [exemptionAmount]
* @param exemptionAmount Amount that should be on the blockchain balance not to pay rent
*/
data class RentData(val rent: BigDecimal, val exemptionAmount: BigDecimal)

View file

@ -1,11 +0,0 @@
package com.tangem.domain.walletmanager.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class SmartContractMethod(
@Json(name = "info") val info: String?,
@Json(name = "source") val source: String?,
@Json(name = "name") val name: String,
)

View file

@ -1,12 +0,0 @@
package com.tangem.domain.walletmanager.model
import com.tangem.domain.models.network.Network
data class TokenInfo(
val network: Network,
val name: String,
val symbol: String,
val contractAddress: String,
val decimals: Int,
val id: String? = null,
)

View file

@ -1,23 +0,0 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.blockchain.common.CryptoCurrencyType
import com.tangem.blockchain.common.Token
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.utils.converter.Converter
internal class CryptoCurrencyTypeConverter : Converter<CryptoCurrency, CryptoCurrencyType> {
override fun convert(value: CryptoCurrency): CryptoCurrencyType {
return when (value) {
is CryptoCurrency.Coin -> CryptoCurrencyType.Coin
is CryptoCurrency.Token -> CryptoCurrencyType.Token(
info = Token(
name = value.name,
symbol = value.symbol,
contractAddress = value.contractAddress,
decimals = value.decimals,
id = value.id.rawCurrencyId?.value,
),
)
}
}
}

View file

@ -1,24 +0,0 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.blockchain.common.address.AddressType
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult.Address
import com.tangem.utils.converter.Converter
import com.tangem.blockchain.common.address.Address as SdkAddress
/**
* Convert [SdkAddress] to [Address]
*
[REDACTED_AUTHOR]
*/
internal object SdkAddressToAddressConverter : Converter<SdkAddress, Address> {
override fun convert(value: SdkAddress): Address {
return Address(
value = value.value,
type = when (value.type) {
AddressType.Default -> Address.Type.Primary
AddressType.Legacy -> Address.Type.Secondary
},
)
}
}

View file

@ -1,23 +0,0 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.domain.txhistory.models.Page
import com.tangem.utils.converter.TwoWayConverter
import com.tangem.blockchain.common.pagination.Page as SdkPage
class SdkPageConverter : TwoWayConverter<SdkPage, Page> {
override fun convert(value: SdkPage): Page {
return when (value) {
is SdkPage.Initial -> Page.Initial
is SdkPage.LastPage -> Page.LastPage
is SdkPage.Next -> Page.Next(value = value.value)
}
}
override fun convertBack(value: Page): SdkPage {
return when (value) {
is Page.Initial -> SdkPage.Initial
is Page.LastPage -> SdkPage.LastPage
is Page.Next -> SdkPage.Next(value = value.value)
}
}
}

View file

@ -1,31 +0,0 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.domain.transaction.models.AssetRequirementsCondition
import com.tangem.utils.converter.Converter
import com.tangem.blockchain.common.trustlines.AssetRequirementsCondition as SdkRequirementsCondition
internal class SdkRequirementsConditionConverter : Converter<SdkRequirementsCondition, AssetRequirementsCondition> {
override fun convert(value: SdkRequirementsCondition): AssetRequirementsCondition {
return when (value) {
is SdkRequirementsCondition.PaidTransaction -> AssetRequirementsCondition.PaidTransaction
is SdkRequirementsCondition.RequiredTrustline -> AssetRequirementsCondition.RequiredTrustline(
requiredAmount = requireNotNull(value.amount.value),
currencySymbol = value.amount.currencySymbol,
decimals = value.amount.decimals,
)
is SdkRequirementsCondition.PaidTransactionWithFee -> AssetRequirementsCondition.PaidTransactionWithFee(
feeAmount = requireNotNull(value.feeAmount.value),
feeCurrencySymbol = value.feeAmount.currencySymbol,
decimals = value.feeAmount.decimals,
)
is SdkRequirementsCondition.IncompleteTransaction -> AssetRequirementsCondition.IncompleteTransaction(
amount = requireNotNull(value.amount.value),
currencySymbol = value.amount.currencySymbol,
currencyDecimals = value.amount.decimals,
feeAmount = requireNotNull(value.feeAmount.value),
feeCurrencySymbol = value.feeAmount.currencySymbol,
feeCurrencyDecimals = value.feeAmount.decimals,
)
}
}
}

View file

@ -1,18 +0,0 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.utils.converter.Converter
import com.tangem.blockchain.common.Token as SdkToken
internal class SdkTokenConverter : Converter<CryptoCurrency.Token, SdkToken> {
override fun convert(value: CryptoCurrency.Token): SdkToken {
return SdkToken(
id = value.id.rawCurrencyId?.value,
name = value.name,
symbol = value.symbol,
contractAddress = value.contractAddress,
decimals = value.decimals,
)
}
}

View file

@ -1,103 +0,0 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryItem
import com.tangem.domain.models.network.TxInfo
import com.tangem.domain.walletmanager.model.SmartContractMethod
import com.tangem.utils.converter.Converter
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryItem as SdkTransactionHistoryItem
internal class SdkTransactionHistoryItemConverter(
smartContractMethods: Map<String, SmartContractMethod>,
) : Converter<SdkTransactionHistoryItem, TxInfo> {
private val typeConverter by lazy { SdkTransactionTypeConverter(smartContractMethods) }
override fun convert(value: SdkTransactionHistoryItem): TxInfo = TxInfo(
txHash = value.txHash,
timestampInMillis = value.timestamp,
isOutgoing = value.isOutgoing,
destinationType = value.destinationType.toDomain(),
sourceType = value.sourceType.toDomain(),
interactionAddressType = value.extractInteractionAddressType(),
status = when (value.status) {
SdkTransactionHistoryItem.TransactionStatus.Confirmed -> TxInfo.TransactionStatus.Confirmed
SdkTransactionHistoryItem.TransactionStatus.Failed -> TxInfo.TransactionStatus.Failed
SdkTransactionHistoryItem.TransactionStatus.Unconfirmed -> TxInfo.TransactionStatus.Unconfirmed
},
type = typeConverter.convert(value.type),
amount = requireNotNull(value.amount.value) { "Transaction amount value must not be null" },
)
private fun SdkTransactionHistoryItem.SourceType.toDomain(): TxInfo.SourceType = when (this) {
is TransactionHistoryItem.SourceType.Single -> TxInfo.SourceType.Single(address)
is TransactionHistoryItem.SourceType.Multiple -> TxInfo.SourceType.Multiple(addresses)
}
private fun SdkTransactionHistoryItem.DestinationType.toDomain(): TxInfo.DestinationType = when (this) {
is SdkTransactionHistoryItem.DestinationType.Single -> TxInfo.DestinationType.Single(
addressType.toDomain(),
)
is SdkTransactionHistoryItem.DestinationType.Multiple -> TxInfo.DestinationType.Multiple(
addressTypes.map { it.toDomain() },
)
}
private fun SdkTransactionHistoryItem.AddressType.toDomain(): TxInfo.AddressType = when (this) {
is SdkTransactionHistoryItem.AddressType.Contract -> TxInfo.AddressType.Contract(address)
is SdkTransactionHistoryItem.AddressType.User -> TxInfo.AddressType.User(address)
is SdkTransactionHistoryItem.AddressType.Validator -> TxInfo.AddressType.Validator(address)
}
private fun SdkTransactionHistoryItem.extractInteractionAddressType(): TxInfo.InteractionAddressType? {
return when (val transactionType = type) {
SdkTransactionHistoryItem.TransactionType.Transfer -> if (isOutgoing) {
mapToInteractionAddressType(destinationType = destinationType)
} else {
mapToInteractionAddressType(sourceType = sourceType)
}
is SdkTransactionHistoryItem.TransactionType.ContractMethod,
is SdkTransactionHistoryItem.TransactionType.ContractMethodName,
-> mapToInteractionAddressType(destinationType = destinationType)
is SdkTransactionHistoryItem.TransactionType.TronStakingTransactionType.VoteWitnessContract -> {
TxInfo.InteractionAddressType.Validator(address = transactionType.validatorAddress)
}
else -> null
}
}
private fun mapToInteractionAddressType(
destinationType: SdkTransactionHistoryItem.DestinationType,
): TxInfo.InteractionAddressType {
return when (destinationType) {
is TransactionHistoryItem.DestinationType.Multiple -> TxInfo.InteractionAddressType.Multiple(
destinationType.addressTypes.map { it.address },
)
is TransactionHistoryItem.DestinationType.Single -> when (destinationType.addressType) {
is TransactionHistoryItem.AddressType.Contract -> TxInfo.InteractionAddressType.Contract(
destinationType.addressType.address,
)
is TransactionHistoryItem.AddressType.User -> TxInfo.InteractionAddressType.User(
destinationType.addressType.address,
)
is TransactionHistoryItem.AddressType.Validator -> TxInfo.InteractionAddressType.Validator(
destinationType.addressType.address,
)
}
}
}
private fun mapToInteractionAddressType(
sourceType: SdkTransactionHistoryItem.SourceType,
): TxInfo.InteractionAddressType {
return when (sourceType) {
is TransactionHistoryItem.SourceType.Multiple -> TxInfo.InteractionAddressType.Multiple(
sourceType.addresses,
)
is TransactionHistoryItem.SourceType.Single -> {
TxInfo.InteractionAddressType.User(sourceType.address)
}
}
}
}

View file

@ -1,16 +0,0 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.blockchain.transactionhistory.TransactionHistoryState
import com.tangem.domain.txhistory.models.TxHistoryState
import com.tangem.utils.converter.Converter
import com.tangem.blockchain.transactionhistory.TransactionHistoryState as SdkTransactionHistoryState
internal class SdkTransactionHistoryStateConverter : Converter<SdkTransactionHistoryState, TxHistoryState> {
override fun convert(value: TransactionHistoryState): TxHistoryState = when (value) {
is TransactionHistoryState.Success.Empty -> TxHistoryState.Success.Empty
is TransactionHistoryState.Success.HasTransactions -> TxHistoryState.Success.HasTransactions(value.txCount)
is TransactionHistoryState.Failed.FetchError -> TxHistoryState.Failed.FetchError(value.exception)
is TransactionHistoryState.NotImplemented -> TxHistoryState.NotImplemented
}
}

View file

@ -1,66 +0,0 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.blockchain.transactionhistory.models.TransactionHistoryItem.TransactionType
import com.tangem.domain.models.network.TxInfo
import com.tangem.domain.walletmanager.model.SmartContractMethod
import com.tangem.utils.converter.Converter
internal class SdkTransactionTypeConverter(
private val smartContractMethods: Map<String, SmartContractMethod>,
) : Converter<TransactionType, TxInfo.TransactionType> {
override fun convert(value: TransactionType): TxInfo.TransactionType {
return when (value) {
is TransactionType.ContractMethod -> {
getTransactionType(methodName = smartContractMethods[value.id]?.name)
}
is TransactionType.ContractMethodName -> {
getTransactionType(methodName = value.name)
}
is TransactionType.Transfer -> {
TxInfo.TransactionType.Transfer
}
is TransactionType.TronStakingTransactionType.FreezeBalanceV2Contract -> {
TxInfo.TransactionType.Staking.Stake
}
is TransactionType.TronStakingTransactionType.UnfreezeBalanceV2Contract -> {
TxInfo.TransactionType.Staking.Unstake
}
is TransactionType.TronStakingTransactionType.VoteWitnessContract -> {
TxInfo.TransactionType.Staking.Vote(value.validatorAddress)
}
is TransactionType.TronStakingTransactionType.WithdrawBalanceContract -> {
TxInfo.TransactionType.Staking.ClaimRewards
}
is TransactionType.TronStakingTransactionType.WithdrawExpireUnfreezeContract -> {
TxInfo.TransactionType.Staking.Withdraw
}
}
}
private fun getTransactionType(methodName: String?): TxInfo.TransactionType {
return when (methodName) {
"transfer" -> TxInfo.TransactionType.Transfer
"approve" -> TxInfo.TransactionType.Approve
"swap" -> TxInfo.TransactionType.Swap
"buyVoucher",
"buyVoucherPOL",
"delegate",
-> TxInfo.TransactionType.Staking.Stake
"sellVoucher_new",
"sellVoucher_newPOL",
"undelegate",
-> TxInfo.TransactionType.Staking.Unstake
"unstakeClaimTokens_new",
"unstakeClaimTokens_newPOL",
"claim",
-> TxInfo.TransactionType.Staking.Withdraw
"withdrawRewards",
"withdrawRewardsPOL",
-> TxInfo.TransactionType.Staking.ClaimRewards
"redelegate" -> TxInfo.TransactionType.Staking.Restake
null -> TxInfo.TransactionType.UnknownOperation
else -> TxInfo.TransactionType.Operation(name = methodName.replaceFirstChar { it.titlecase() })
}
}
}

View file

@ -1,77 +0,0 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.blockchain.common.*
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult.Address
import com.tangem.domain.models.network.TxInfo
import com.tangem.utils.converter.Converter
import timber.log.Timber
import java.math.BigDecimal
/**
* Convert [TransactionData] to [TxInfo]
*
* @property walletAddresses wallet addresses
*
[REDACTED_AUTHOR]
*/
internal class TransactionDataToTxHistoryItemConverter(
private val walletAddresses: Set<Address>,
private val feePaidCurrency: FeePaidCurrency,
) : Converter<TransactionData.Uncompiled, TxInfo?> {
override fun convert(value: TransactionData.Uncompiled): TxInfo? {
val hash = value.hash ?: return null
val millis = value.date?.timeInMillis ?: return null
val amount = getTransactionAmountValue(value.amount, value.fee?.amount) ?: return null
val isOutgoing = value.sourceAddress in walletAddresses.map(Address::value)
return TxInfo(
txHash = hash,
timestampInMillis = millis,
isOutgoing = isOutgoing,
destinationType = TxInfo.DestinationType.Single(
addressType = TxInfo.AddressType.User(value.destinationAddress),
),
sourceType = TxInfo.SourceType.Single(value.sourceAddress),
interactionAddressType = TxInfo.InteractionAddressType.User(
address = if (isOutgoing) value.destinationAddress else value.sourceAddress,
),
status = when (value.status) {
TransactionStatus.Confirmed -> TxInfo.TransactionStatus.Confirmed
TransactionStatus.Unconfirmed -> TxInfo.TransactionStatus.Unconfirmed
},
type = TxInfo.TransactionType.Transfer,
amount = amount,
)
}
private fun getTransactionAmountValue(amount: Amount, feeAmount: Amount?): BigDecimal? {
val feeValue = feeAmount?.value ?: BigDecimal.ZERO
val value = amount.value
if (value == null) {
Timber.w("Transaction amount must not be null: ${amount.currencySymbol}")
}
return when (feePaidCurrency) {
FeePaidCurrency.SameCurrency -> value?.plus(feeValue)
FeePaidCurrency.Coin -> {
if (amount.type is AmountType.Coin) value?.plus(feeValue) else value
}
is FeePaidCurrency.Token -> {
val token = (amount.type as? AmountType.Token)?.token ?: return value
if (isSameToken(token, feePaidCurrency.token)) {
value?.plus(feeValue)
} else {
value
}
}
is FeePaidCurrency.FeeResource -> value
}
}
private fun isSameToken(amountToken: Token, feeToken: Token): Boolean {
return amountToken.contractAddress.equals(feeToken.contractAddress, ignoreCase = true) &&
amountToken.symbol.equals(feeToken.symbol, ignoreCase = true)
}
}

View file

@ -1,185 +0,0 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.blockchain.common.*
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult.*
import com.tangem.blockchainsdk.utils.amountToCreateAccount
import com.tangem.domain.models.currency.CryptoCurrency
import timber.log.Timber
import java.math.BigDecimal
import com.tangem.blockchain.common.address.Address as SdkAddress
/** Factory for creating [UpdateWalletManagerResult] */
internal class UpdateWalletManagerResultFactory {
/** Get [Verified] result for [walletManager] */
fun getResult(walletManager: WalletManager): Verified {
val wallet = walletManager.wallet
val addresses = getAvailableAddresses(wallet.addresses)
val feePaidCurrency = wallet.blockchain.feePaidCurrency()
val txHistoryItemConverter = TransactionDataToTxHistoryItemConverter(addresses, feePaidCurrency)
return Verified(
selectedAddress = wallet.address,
addresses = addresses,
currenciesAmounts = getTokensAmounts(wallet.amounts.values.toSet()),
currentTransactions = getCurrentTransactions(txHistoryItemConverter, wallet.recentTransactions.toSet()),
)
}
/**
* Get demo [Verified] result
*
* @param walletManager wallet manager
* @param demoAmount amount that will be used for demo result
*/
fun getDemoResult(walletManager: WalletManager, demoAmount: Amount): Verified {
val wallet = walletManager.wallet
val addresses = getAvailableAddresses(wallet.addresses)
val feePaidCurrency = wallet.blockchain.feePaidCurrency()
val txHistoryItemConverter = TransactionDataToTxHistoryItemConverter(addresses, feePaidCurrency)
return Verified(
selectedAddress = wallet.address,
addresses = addresses,
currenciesAmounts = getDemoTokensAmounts(demoAmount, walletManager.cardTokens),
currentTransactions = getCurrentTransactions(txHistoryItemConverter, wallet.recentTransactions.toSet()),
)
}
/**
* Get [NoAccount] result.
* If unable to get required amount for creating account, [Unreachable] result will be returned.
*
* @param walletManager wallet manager
* @param customMessage custom error message
* @param amountToCreateAccount amount to create account
*/
fun getNoAccountResult(
walletManager: WalletManager,
customMessage: String,
amountToCreateAccount: BigDecimal?,
): UpdateWalletManagerResult {
val wallet = walletManager.wallet
val blockchain = wallet.blockchain
val firstWalletToken = wallet.getTokens().firstOrNull()
val amount = amountToCreateAccount ?: blockchain.amountToCreateAccount(walletManager, firstWalletToken)
return if (amount == null) {
Timber.w("Unable to get required amount to create account for: $blockchain")
Unreachable(
selectedAddress = wallet.address,
addresses = getAvailableAddresses(wallet.addresses),
)
} else {
NoAccount(
selectedAddress = wallet.address,
addresses = getAvailableAddresses(wallet.addresses),
amountToCreateAccount = amount,
errorMessage = customMessage,
)
}
}
/** Get [Unreachable] result for [walletManager] */
fun getUnreachableResult(walletManager: WalletManager): Unreachable {
val wallet = walletManager.wallet
return Unreachable(
selectedAddress = wallet.address,
addresses = getAvailableAddresses(wallet.addresses),
)
}
private fun getAvailableAddresses(addresses: Set<SdkAddress>): Set<Address> {
return SdkAddressToAddressConverter.convertList(addresses).toSet()
}
private fun getTokensAmounts(amounts: Set<Amount>): Set<CryptoCurrencyAmount> {
return amounts.mapNotNullTo(hashSetOf(), ::createCurrencyAmount)
}
private fun createCurrencyAmount(amount: Amount): CryptoCurrencyAmount? {
return when (val type = amount.type) {
is AmountType.Token -> {
val value = getCurrencyAmountValue(amount) ?: return null
CryptoCurrencyAmount.Token(
currencyRawId = type.token.id?.let(CryptoCurrency::RawID),
contractAddress = type.token.contractAddress,
value = value,
)
}
is AmountType.Coin -> {
val value = getCurrencyAmountValue(amount) ?: return null
CryptoCurrencyAmount.Coin(value = value)
}
is AmountType.FeeResource,
is AmountType.Reserve,
-> null
}
}
private fun getCurrencyAmountValue(amount: Amount): BigDecimal? {
val value = amount.value
if (value == null) {
Timber.w("Currency amount must not be null: ${amount.currencySymbol}")
}
return value
}
private fun getDemoTokensAmounts(demoAmount: Amount, tokens: Set<Token>): Set<CryptoCurrencyAmount> {
val amountValue = demoAmount.value ?: BigDecimal.ZERO
val demoAmounts = hashSetOf<CryptoCurrencyAmount>(CryptoCurrencyAmount.Coin(amountValue))
return tokens.mapTo(demoAmounts) { token ->
CryptoCurrencyAmount.Token(
currencyRawId = token.id?.let(CryptoCurrency::RawID),
contractAddress = token.contractAddress,
value = amountValue,
)
}
}
private fun getCurrentTransactions(
txHistoryItemConverter: TransactionDataToTxHistoryItemConverter,
recentTransactions: Set<TransactionData.Uncompiled>,
): Set<CryptoCurrencyTransaction> {
val unconfirmedTransactions = recentTransactions.filter { it.status == TransactionStatus.Unconfirmed }
return unconfirmedTransactions.mapNotNullTo(hashSetOf()) {
createCurrencyTransaction(
txHistoryItemConverter = txHistoryItemConverter,
data = it,
)
}
}
private fun createCurrencyTransaction(
txHistoryItemConverter: TransactionDataToTxHistoryItemConverter,
data: TransactionData.Uncompiled,
): CryptoCurrencyTransaction? {
return when (val type = data.amount.type) {
is AmountType.Coin -> {
val txHistoryItem = txHistoryItemConverter.convert(data) ?: return null
CryptoCurrencyTransaction.Coin(txInfo = txHistoryItem)
}
is AmountType.Token -> {
val txHistoryItem = txHistoryItemConverter.convert(data) ?: return null
CryptoCurrencyTransaction.Token(
tokenId = type.token.id,
contractAddress = type.token.contractAddress,
txInfo = txHistoryItem,
)
}
is AmountType.FeeResource,
is AmountType.Reserve,
-> null
}
}
}

View file

@ -1,87 +0,0 @@
package com.tangem.domain.walletmanager.utils
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.DerivationParams
import com.tangem.blockchain.common.WalletManager
import com.tangem.blockchainsdk.BlockchainSDKFactory
import com.tangem.crypto.hdWallet.DerivationPath
import com.tangem.domain.common.DerivationStyleProvider
import com.tangem.domain.common.extensions.makePublicKey
import com.tangem.domain.common.extensions.makeWalletManagerForApp
import com.tangem.domain.common.util.derivationStyleProvider
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.models.wallet.UserWallet
import timber.log.Timber
internal class WalletManagerFactory(
private val blockchainSDKFactory: BlockchainSDKFactory,
) {
suspend fun createWalletManager(
scanResponse: ScanResponse,
blockchain: Blockchain,
derivationPath: DerivationPath?,
): WalletManager? {
val derivationParams = getDerivationParams(derivationPath, scanResponse.derivationStyleProvider)
return try {
blockchainSDKFactory.getWalletManagerFactorySync()?.makeWalletManagerForApp(
scanResponse = scanResponse,
blockchain = blockchain,
derivationParams = derivationParams,
)
} catch (e: Throwable) {
Timber.w(e, "Failed to create wallet manager for $blockchain")
null
}
}
suspend fun createWalletManagerForHot(
hotWallet: UserWallet.Hot,
blockchain: Blockchain,
derivationPath: DerivationPath?,
): WalletManager? {
val curve = blockchain.getSupportedCurves().first()
val selectedWallet = hotWallet.wallets.orEmpty().firstOrNull { it.curve == curve }
?: return null
return try {
val factory = blockchainSDKFactory.getWalletManagerFactorySync() ?: return null
if (derivationPath == null) {
factory.createLegacyWalletManager(
blockchain = blockchain,
walletPublicKey = selectedWallet.publicKey,
curve = selectedWallet.curve,
)
} else {
factory.createWalletManager(
blockchain = blockchain,
publicKey = makePublicKey(
seedKey = selectedWallet.publicKey,
blockchain = blockchain,
derivationPath = derivationPath,
derivedWalletKeys = selectedWallet.derivedKeys,
isWallet2 = true,
) ?: return null,
curve = selectedWallet.curve,
)
}
} catch (e: Throwable) {
Timber.w(e, "Failed to create wallet manager for $blockchain")
null
}
}
private fun getDerivationParams(
derivationPath: DerivationPath?,
derivationStyleProvider: DerivationStyleProvider,
): DerivationParams? {
val derivationStyle = derivationStyleProvider.getDerivationStyle() ?: return null
return if (derivationPath == null) {
DerivationParams.Default(derivationStyle)
} else {
DerivationParams.Custom(derivationPath)
}
}
}

View file

@ -1,50 +0,0 @@
package com.tangem.domain.common
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class TwinsHelperTest {
private val pack1Twins = listOf(
"CB610000021",
"CB620000031",
)
private val pack2Twins = listOf(
"CB640000012",
"CB650000011",
)
@Test
fun `twins compatibility pack 1 success`() {
assertTrue(TwinsHelper.isTwinsCompatible(pack1Twins[0], pack1Twins[1]))
assertTrue(TwinsHelper.isTwinsCompatible(pack1Twins[1], pack1Twins[0]))
}
@Test
fun `twins compatibility pack 1 same cards`() {
assertFalse(TwinsHelper.isTwinsCompatible(pack1Twins[0], pack1Twins[0]))
assertFalse(TwinsHelper.isTwinsCompatible(pack1Twins[1], pack1Twins[1]))
}
@Test
fun `twins compatibility pack 2 success`() {
assertTrue(TwinsHelper.isTwinsCompatible(pack2Twins[0], pack2Twins[1]))
assertTrue(TwinsHelper.isTwinsCompatible(pack2Twins[1], pack2Twins[0]))
}
@Test
fun `twins compatibility pack 2 same cards`() {
assertFalse(TwinsHelper.isTwinsCompatible(pack2Twins[0], pack2Twins[0]))
assertFalse(TwinsHelper.isTwinsCompatible(pack2Twins[1], pack2Twins[1]))
}
@Test
fun `twins compatibility false for different packs`() {
assertFalse(TwinsHelper.isTwinsCompatible(pack2Twins[0], pack1Twins[1]))
assertFalse(TwinsHelper.isTwinsCompatible(pack1Twins[1], pack2Twins[0]))
assertFalse(TwinsHelper.isTwinsCompatible(pack1Twins[0], pack2Twins[1]))
assertFalse(TwinsHelper.isTwinsCompatible(pack2Twins[1], pack1Twins[0]))
}
}

View file

@ -1,176 +0,0 @@
package com.tangem.domain.common.configs
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.card.EllipticCurve
import junit.framework.TestCase.assertEquals
import org.junit.Test
class Wallet2CardConfigTest {
private val wallet2CardConfig = Wallet2CardConfig
private val expectedMapping = mapOf(
Blockchain.Unknown to null,
Blockchain.Arbitrum to EllipticCurve.Secp256k1,
Blockchain.ArbitrumTestnet to EllipticCurve.Secp256k1,
Blockchain.Avalanche to EllipticCurve.Secp256k1,
Blockchain.AvalancheTestnet to EllipticCurve.Secp256k1,
Blockchain.Binance to EllipticCurve.Secp256k1,
Blockchain.BinanceTestnet to EllipticCurve.Secp256k1,
Blockchain.BSC to EllipticCurve.Secp256k1,
Blockchain.BSCTestnet to EllipticCurve.Secp256k1,
Blockchain.Bitcoin to EllipticCurve.Secp256k1,
Blockchain.BitcoinTestnet to EllipticCurve.Secp256k1,
Blockchain.BitcoinCash to EllipticCurve.Secp256k1,
Blockchain.BitcoinCashTestnet to EllipticCurve.Secp256k1,
Blockchain.Cardano to EllipticCurve.Ed25519,
Blockchain.Cosmos to EllipticCurve.Secp256k1,
Blockchain.CosmosTestnet to EllipticCurve.Secp256k1,
Blockchain.Dogecoin to EllipticCurve.Secp256k1,
Blockchain.Ducatus to EllipticCurve.Secp256k1,
Blockchain.Ethereum to EllipticCurve.Secp256k1,
Blockchain.EthereumTestnet to EllipticCurve.Secp256k1,
Blockchain.EthereumClassic to EllipticCurve.Secp256k1,
Blockchain.EthereumClassicTestnet to EllipticCurve.Secp256k1,
Blockchain.Fantom to EllipticCurve.Secp256k1,
Blockchain.FantomTestnet to EllipticCurve.Secp256k1,
Blockchain.Litecoin to EllipticCurve.Secp256k1,
Blockchain.Near to EllipticCurve.Ed25519Slip0010,
Blockchain.NearTestnet to EllipticCurve.Ed25519Slip0010,
Blockchain.Polkadot to EllipticCurve.Ed25519Slip0010,
Blockchain.PolkadotTestnet to EllipticCurve.Ed25519Slip0010,
Blockchain.Kava to EllipticCurve.Secp256k1,
Blockchain.KavaTestnet to EllipticCurve.Secp256k1,
Blockchain.Kusama to EllipticCurve.Ed25519Slip0010,
Blockchain.Polygon to EllipticCurve.Secp256k1,
Blockchain.PolygonTestnet to EllipticCurve.Secp256k1,
Blockchain.RSK to EllipticCurve.Secp256k1,
Blockchain.Sei to EllipticCurve.Secp256k1,
Blockchain.SeiTestnet to EllipticCurve.Secp256k1,
Blockchain.Stellar to EllipticCurve.Ed25519Slip0010,
Blockchain.StellarTestnet to EllipticCurve.Ed25519Slip0010,
Blockchain.Solana to EllipticCurve.Ed25519Slip0010,
Blockchain.SolanaTestnet to EllipticCurve.Ed25519Slip0010,
Blockchain.Tezos to EllipticCurve.Ed25519Slip0010,
Blockchain.Tron to EllipticCurve.Secp256k1,
Blockchain.TronTestnet to EllipticCurve.Secp256k1,
Blockchain.XRP to EllipticCurve.Secp256k1,
Blockchain.Gnosis to EllipticCurve.Secp256k1,
Blockchain.Dash to EllipticCurve.Secp256k1,
Blockchain.Optimism to EllipticCurve.Secp256k1,
Blockchain.OptimismTestnet to EllipticCurve.Secp256k1,
Blockchain.Dischain to EllipticCurve.Secp256k1,
Blockchain.EthereumPow to EllipticCurve.Secp256k1,
Blockchain.EthereumPowTestnet to EllipticCurve.Secp256k1,
Blockchain.Kaspa to EllipticCurve.Secp256k1,
Blockchain.Telos to EllipticCurve.Secp256k1,
Blockchain.TelosTestnet to EllipticCurve.Secp256k1,
Blockchain.TON to EllipticCurve.Ed25519Slip0010,
Blockchain.TONTestnet to EllipticCurve.Ed25519Slip0010,
Blockchain.Ravencoin to EllipticCurve.Secp256k1,
Blockchain.RavencoinTestnet to EllipticCurve.Secp256k1,
Blockchain.TerraV1 to EllipticCurve.Secp256k1,
Blockchain.TerraV2 to EllipticCurve.Secp256k1,
Blockchain.Cronos to EllipticCurve.Secp256k1,
Blockchain.AlephZero to EllipticCurve.Ed25519Slip0010,
Blockchain.AlephZeroTestnet to EllipticCurve.Ed25519Slip0010,
Blockchain.OctaSpace to EllipticCurve.Secp256k1,
Blockchain.OctaSpaceTestnet to EllipticCurve.Secp256k1,
Blockchain.Chia to EllipticCurve.Bls12381G2Aug,
Blockchain.ChiaTestnet to EllipticCurve.Bls12381G2Aug,
Blockchain.Decimal to EllipticCurve.Secp256k1,
Blockchain.DecimalTestnet to EllipticCurve.Secp256k1,
Blockchain.XDC to EllipticCurve.Secp256k1,
Blockchain.XDCTestnet to EllipticCurve.Secp256k1,
Blockchain.VeChain to EllipticCurve.Secp256k1,
Blockchain.VeChainTestnet to EllipticCurve.Secp256k1,
Blockchain.Aptos to EllipticCurve.Ed25519Slip0010,
Blockchain.AptosTestnet to EllipticCurve.Ed25519Slip0010,
Blockchain.Playa3ull to EllipticCurve.Secp256k1,
Blockchain.Shibarium to EllipticCurve.Secp256k1,
Blockchain.ShibariumTestnet to EllipticCurve.Secp256k1,
Blockchain.Algorand to EllipticCurve.Ed25519Slip0010,
Blockchain.AlgorandTestnet to EllipticCurve.Ed25519Slip0010,
Blockchain.Hedera to EllipticCurve.Ed25519Slip0010,
Blockchain.HederaTestnet to EllipticCurve.Ed25519Slip0010,
Blockchain.Aurora to EllipticCurve.Secp256k1,
Blockchain.AuroraTestnet to EllipticCurve.Secp256k1,
Blockchain.Areon to EllipticCurve.Secp256k1,
Blockchain.AreonTestnet to EllipticCurve.Secp256k1,
Blockchain.PulseChain to EllipticCurve.Secp256k1,
Blockchain.PulseChainTestnet to EllipticCurve.Secp256k1,
Blockchain.ZkSyncEra to EllipticCurve.Secp256k1,
Blockchain.ZkSyncEraTestnet to EllipticCurve.Secp256k1,
Blockchain.Nexa to EllipticCurve.Secp256k1,
Blockchain.NexaTestnet to EllipticCurve.Secp256k1,
Blockchain.Moonbeam to EllipticCurve.Secp256k1,
Blockchain.MoonbeamTestnet to EllipticCurve.Secp256k1,
Blockchain.Manta to EllipticCurve.Secp256k1,
Blockchain.MantaTestnet to EllipticCurve.Secp256k1,
Blockchain.PolygonZkEVM to EllipticCurve.Secp256k1,
Blockchain.PolygonZkEVMTestnet to EllipticCurve.Secp256k1,
Blockchain.Radiant to EllipticCurve.Secp256k1,
Blockchain.Base to EllipticCurve.Secp256k1,
Blockchain.BaseTestnet to EllipticCurve.Secp256k1,
Blockchain.Moonriver to EllipticCurve.Secp256k1,
Blockchain.MoonriverTestnet to EllipticCurve.Secp256k1,
Blockchain.Mantle to EllipticCurve.Secp256k1,
Blockchain.MantleTestnet to EllipticCurve.Secp256k1,
Blockchain.Fact0rn to EllipticCurve.Secp256k1,
Blockchain.Flare to EllipticCurve.Secp256k1,
Blockchain.FlareTestnet to EllipticCurve.Secp256k1,
Blockchain.Taraxa to EllipticCurve.Secp256k1,
Blockchain.TaraxaTestnet to EllipticCurve.Secp256k1,
Blockchain.Koinos to EllipticCurve.Secp256k1,
Blockchain.KoinosTestnet to EllipticCurve.Secp256k1,
Blockchain.Joystream to EllipticCurve.Ed25519Slip0010,
Blockchain.Bittensor to EllipticCurve.Ed25519Slip0010,
Blockchain.Filecoin to EllipticCurve.Secp256k1,
Blockchain.Blast to EllipticCurve.Secp256k1,
Blockchain.BlastTestnet to EllipticCurve.Secp256k1,
Blockchain.Cyber to EllipticCurve.Secp256k1,
Blockchain.CyberTestnet to EllipticCurve.Secp256k1,
Blockchain.InternetComputer to EllipticCurve.Secp256k1,
Blockchain.Sui to EllipticCurve.Ed25519Slip0010,
Blockchain.SuiTestnet to EllipticCurve.Ed25519Slip0010,
Blockchain.EnergyWebChain to EllipticCurve.Secp256k1,
Blockchain.EnergyWebChainTestnet to EllipticCurve.Secp256k1,
Blockchain.EnergyWebX to EllipticCurve.Ed25519Slip0010,
Blockchain.EnergyWebXTestnet to EllipticCurve.Ed25519Slip0010,
Blockchain.Casper to EllipticCurve.Secp256k1,
Blockchain.CasperTestnet to EllipticCurve.Secp256k1,
Blockchain.Core to EllipticCurve.Secp256k1,
Blockchain.CoreTestnet to EllipticCurve.Secp256k1,
Blockchain.Xodex to EllipticCurve.Secp256k1,
Blockchain.Canxium to EllipticCurve.Secp256k1,
Blockchain.Chiliz to EllipticCurve.Secp256k1,
Blockchain.ChilizTestnet to EllipticCurve.Secp256k1,
Blockchain.Clore to EllipticCurve.Secp256k1,
Blockchain.VanarChain to EllipticCurve.Secp256k1,
Blockchain.VanarChainTestnet to EllipticCurve.Secp256k1,
Blockchain.OdysseyChain to EllipticCurve.Secp256k1,
Blockchain.OdysseyChainTestnet to EllipticCurve.Secp256k1,
Blockchain.Bitrock to EllipticCurve.Secp256k1,
Blockchain.BitrockTestnet to EllipticCurve.Secp256k1,
Blockchain.Sonic to EllipticCurve.Secp256k1,
Blockchain.SonicTestnet to EllipticCurve.Secp256k1,
Blockchain.ApeChain to EllipticCurve.Secp256k1,
Blockchain.ApeChainTestnet to EllipticCurve.Secp256k1,
Blockchain.KaspaTestnet to EllipticCurve.Secp256k1,
Blockchain.Alephium to EllipticCurve.Secp256k1,
Blockchain.AlephiumTestnet to EllipticCurve.Secp256k1,
Blockchain.Scroll to EllipticCurve.Secp256k1,
Blockchain.ScrollTestnet to EllipticCurve.Secp256k1,
Blockchain.ZkLinkNova to EllipticCurve.Secp256k1,
Blockchain.ZkLinkNovaTestnet to EllipticCurve.Secp256k1,
Blockchain.Pepecoin to EllipticCurve.Secp256k1,
Blockchain.PepecoinTestnet to EllipticCurve.Secp256k1,
)
@Test
fun blockchainCurveMappingTest() {
Blockchain.entries.forEach {
val resultCurve = wallet2CardConfig.primaryCurve(it)
assertEquals(expectedMapping[it], resultCurve)
}
}
}

View file

@ -1,786 +0,0 @@
package com.tangem.domain.walletmanager.utils
import com.google.common.truth.Truth
import com.tangem.blockchain.common.*
import com.tangem.blockchain.common.address.Address
import com.tangem.blockchain.common.address.AddressType
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult.*
import com.tangem.blockchainsdk.models.UpdateWalletManagerResult.Address.Type
import com.tangem.common.test.domain.walletmanager.MockUpdateWalletManagerResultFactory
import com.tangem.common.test.utils.ProvideTestModels
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.TxInfo
import io.mockk.clearMocks
import io.mockk.every
import io.mockk.mockk
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.TestInstance
import org.junit.jupiter.params.ParameterizedTest
import java.math.BigDecimal
import java.util.Calendar
/**
[REDACTED_AUTHOR]
*/
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class UpdateWalletManagerResultFactoryTest {
private val factory = UpdateWalletManagerResultFactory()
private val mockFactory = MockUpdateWalletManagerResultFactory()
private val walletManager = mockk<WalletManager>()
@BeforeEach
fun resetMocks() {
clearMocks(walletManager)
}
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetResult {
@ParameterizedTest
@ProvideTestModels
fun getResult(model: GetResultTestModel) {
// Arrange
every { walletManager.wallet } returns model.wallet
// Act
val actual = factory.getResult(walletManager = walletManager)
// Assert
Truth.assertThat(actual).isEqualTo(model.expected)
}
private fun provideTestModels(): List<GetResultTestModel> = listOf(
// region Wallet without amount and transactions
GetResultTestModel(
wallet = createWallet(
coinValue = null,
addresses = setOf(Address(value = "0x1", type = AddressType.Default)),
transactions = emptyList(),
),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(Address(value = "0x1", type = Type.Primary)),
currenciesAmounts = emptySet(),
currentTransactions = emptySet(),
),
),
// endregion
// region Wallet with coin amount and without transactions
GetResultTestModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
),
transactions = emptyList(),
),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(Address(value = "0x1", type = Type.Primary)),
currenciesAmounts = setOf(CryptoCurrencyAmount.Coin(value = BigDecimal.ONE)),
currentTransactions = emptySet(),
),
),
// endregion
// region Wallet with coin amount and coin transaction
GetResultTestModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
),
transactions = listOf(
createRecentTransaction(
amount = Amount(
blockchain = Blockchain.Ethereum,
value = BigDecimal.ONE,
),
status = TransactionStatus.Unconfirmed,
),
),
),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(Address(value = "0x1", type = Type.Primary)),
currenciesAmounts = setOf(CryptoCurrencyAmount.Coin(value = BigDecimal.ONE)),
currentTransactions = setOf(
CryptoCurrencyTransaction.Coin(
txInfo = createTxInfo(
status = TxInfo.TransactionStatus.Unconfirmed,
amount = BigDecimal.ONE,
),
),
),
),
),
// endregion
// region Wallet with amounts and transactions (coin and token)
GetResultTestModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
tokensAmount = mapOf(usdtToken to BigDecimal.ZERO),
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
Address(value = "0x11", type = AddressType.Legacy),
),
transactions = listOf(
createRecentTransaction(
amount = Amount(
blockchain = Blockchain.Ethereum,
value = BigDecimal.ONE,
),
status = TransactionStatus.Confirmed,
),
createRecentTransaction(
amount = Amount(
value = BigDecimal.TEN,
blockchain = Blockchain.Ethereum,
type = AmountType.Token(token = usdtToken),
currencySymbol = "USDT",
),
status = TransactionStatus.Unconfirmed,
),
),
),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(
Address(value = "0x1", type = Type.Primary),
Address(value = "0x11", type = Type.Secondary),
),
currenciesAmounts = setOf(
CryptoCurrencyAmount.Coin(value = BigDecimal.ONE),
CryptoCurrencyAmount.Token(
value = BigDecimal.ZERO,
currencyRawId = usdtToken.id?.let(CryptoCurrency::RawID),
contractAddress = usdtToken.contractAddress,
),
),
currentTransactions = setOf(
CryptoCurrencyTransaction.Token(
txInfo = createTxInfo(
status = TxInfo.TransactionStatus.Unconfirmed,
amount = BigDecimal.TEN,
),
tokenId = "0x3",
contractAddress = "0x4",
),
),
),
),
// endregion
)
}
data class GetResultTestModel(val wallet: Wallet, val expected: Verified)
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetDemoResult {
@ParameterizedTest
@ProvideTestModels
fun getDemoResult(model: GetDemoResultTestModel) {
// Arrange
every { walletManager.wallet } returns model.wallet
every { walletManager.cardTokens } returns model.cardTokens.toMutableSet()
// Act
val actual = factory.getDemoResult(walletManager = walletManager, demoAmount = model.demoAmount)
// Assert
Truth.assertThat(actual).isEqualTo(model.expected)
}
private fun provideTestModels(): List<GetDemoResultTestModel> {
return listOf(
// region Wallet without amount and transactions
GetDemoResultTestModel(
wallet = createWallet(
coinValue = null,
addresses = setOf(Address(value = "0x1", type = AddressType.Default)),
transactions = emptyList(),
),
demoAmount = Amount(value = null, blockchain = Blockchain.Ethereum),
cardTokens = emptySet(),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(
Address(value = "0x1", type = Type.Primary),
),
currenciesAmounts = setOf(
CryptoCurrencyAmount.Coin(value = BigDecimal.ZERO), // default for demo
),
currentTransactions = emptySet(),
),
),
GetDemoResultTestModel(
wallet = createWallet(
coinValue = null,
addresses = setOf(Address(value = "0x1", type = AddressType.Default)),
transactions = emptyList(),
),
demoAmount = Amount(value = BigDecimal.ONE, blockchain = Blockchain.Ethereum),
cardTokens = emptySet(),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(
Address(value = "0x1", type = Type.Primary),
),
currenciesAmounts = setOf(
CryptoCurrencyAmount.Coin(value = BigDecimal.ONE), // used demo amount
),
currentTransactions = emptySet(),
),
),
// endregion
// region Wallet with coin amount and without transactions
GetDemoResultTestModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
),
transactions = emptyList(),
),
demoAmount = Amount(value = null, blockchain = Blockchain.Ethereum),
cardTokens = emptySet(),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(
Address(
value = "0x1",
type = Type.Primary,
),
),
currenciesAmounts = setOf(CryptoCurrencyAmount.Coin(value = BigDecimal.ZERO)),
currentTransactions = emptySet(),
),
),
GetDemoResultTestModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
),
transactions = emptyList(),
),
demoAmount = Amount(value = BigDecimal.TEN, blockchain = Blockchain.Ethereum),
cardTokens = emptySet(),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(
Address(
value = "0x1",
type = Type.Primary,
),
),
currenciesAmounts = setOf(CryptoCurrencyAmount.Coin(value = BigDecimal.TEN)),
currentTransactions = emptySet(),
),
),
// endregion
// region Wallet with coin amount and coin transaction
GetDemoResultTestModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
),
transactions = listOf(
createRecentTransaction(
amount = Amount(
blockchain = Blockchain.Ethereum,
value = BigDecimal.ONE,
),
status = TransactionStatus.Unconfirmed,
),
),
),
demoAmount = Amount(value = BigDecimal.TEN, blockchain = Blockchain.Ethereum),
cardTokens = emptySet(),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(
Address(value = "0x1", type = Type.Primary),
),
currenciesAmounts = setOf(CryptoCurrencyAmount.Coin(value = BigDecimal.TEN)),
currentTransactions = setOf(
CryptoCurrencyTransaction.Coin(
txInfo = createTxInfo(
status = TxInfo.TransactionStatus.Unconfirmed,
amount = BigDecimal.ONE,
),
),
),
),
),
// endregion
// region Wallet with amounts and transactions (coin and token)
GetDemoResultTestModel(
wallet = createWallet(
coinValue = BigDecimal.TEN,
tokensAmount = mapOf(usdtToken to BigDecimal.TEN),
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
Address(value = "0x11", type = AddressType.Legacy),
),
transactions = listOf(
createRecentTransaction(
amount = Amount(
blockchain = Blockchain.Ethereum,
value = BigDecimal.ONE,
),
status = TransactionStatus.Confirmed,
),
createRecentTransaction(
amount = Amount(
value = BigDecimal.TEN,
blockchain = Blockchain.Ethereum,
type = AmountType.Token(token = usdtToken),
currencySymbol = "USDT",
),
status = TransactionStatus.Unconfirmed,
),
),
),
demoAmount = Amount(value = null, blockchain = Blockchain.Ethereum),
cardTokens = setOf(usdtToken),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(
Address(value = "0x1", type = Type.Primary),
Address(value = "0x11", type = Type.Secondary),
),
currenciesAmounts = setOf(
CryptoCurrencyAmount.Coin(value = BigDecimal.ZERO),
CryptoCurrencyAmount.Token(
value = BigDecimal.ZERO,
currencyRawId = usdtToken.id?.let(CryptoCurrency::RawID),
contractAddress = usdtToken.contractAddress,
),
),
currentTransactions = setOf(
CryptoCurrencyTransaction.Token(
txInfo = createTxInfo(
status = TxInfo.TransactionStatus.Unconfirmed,
amount = BigDecimal.TEN,
),
tokenId = "0x3",
contractAddress = "0x4",
),
),
),
),
GetDemoResultTestModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
tokensAmount = mapOf(usdtToken to BigDecimal.ZERO),
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
Address(value = "0x11", type = AddressType.Legacy),
),
transactions = listOf(
createRecentTransaction(
amount = Amount(
blockchain = Blockchain.Ethereum,
value = BigDecimal.ONE,
),
status = TransactionStatus.Unconfirmed,
),
createRecentTransaction(
amount = Amount(
value = BigDecimal.TEN,
blockchain = Blockchain.Ethereum,
type = AmountType.Token(token = usdtToken),
currencySymbol = "USDT",
),
status = TransactionStatus.Unconfirmed,
),
),
),
demoAmount = Amount(value = BigDecimal.TEN, blockchain = Blockchain.Ethereum),
cardTokens = setOf(usdtToken),
expected = Verified(
selectedAddress = "0x1",
addresses = setOf(
Address(value = "0x1", type = Type.Primary),
Address(value = "0x11", type = Type.Secondary),
),
currenciesAmounts = setOf(
CryptoCurrencyAmount.Coin(value = BigDecimal.TEN),
CryptoCurrencyAmount.Token(
value = BigDecimal.TEN,
currencyRawId = usdtToken.id?.let(CryptoCurrency::RawID),
contractAddress = usdtToken.contractAddress,
),
),
currentTransactions = setOf(
CryptoCurrencyTransaction.Token(
txInfo = createTxInfo(
status = TxInfo.TransactionStatus.Unconfirmed,
amount = BigDecimal.TEN,
),
tokenId = "0x3",
contractAddress = "0x4",
),
CryptoCurrencyTransaction.Coin(
txInfo = createTxInfo(
status = TxInfo.TransactionStatus.Unconfirmed,
amount = BigDecimal.ONE,
),
),
),
),
),
// endregion
)
}
}
data class GetDemoResultTestModel(
val wallet: Wallet,
val demoAmount: Amount,
val cardTokens: Set<Token>,
val expected: Verified,
)
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetNoAccountResult {
@ParameterizedTest
@ProvideTestModels
fun getNoAccountResult(model: GetNoAccountResultModel) {
// Arrange
every { walletManager.wallet } returns model.wallet
// Act
val actual = factory.getNoAccountResult(
walletManager = walletManager,
customMessage = model.customMessage,
amountToCreateAccount = model.amountToCreateAccount,
)
// Assert
Truth.assertThat(actual).isEqualTo(model.expected)
}
private fun provideTestModels() = listOf(
// region amountToCreateAccount is null
GetNoAccountResultModel(
wallet = createWallet(
coinValue = null,
addresses = setOf(Address(value = "0x1", type = AddressType.Default)),
transactions = emptyList(),
),
customMessage = "",
amountToCreateAccount = null,
expected = Unreachable(
selectedAddress = "0x1",
addresses = setOf(Address(value = "0x1", type = Type.Primary)),
),
),
// endregion
// region Wallet without amount and transactions
GetNoAccountResultModel(
wallet = createWallet(
coinValue = null,
addresses = setOf(Address(value = "0x1", type = AddressType.Default)),
transactions = emptyList(),
),
customMessage = "",
amountToCreateAccount = BigDecimal.ONE,
expected = mockFactory.createNoAccount(),
),
// endregion
// region Wallet with coin amount and without transactions
GetNoAccountResultModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
),
transactions = emptyList(),
),
customMessage = "custom message",
amountToCreateAccount = BigDecimal.ONE,
expected = NoAccount(
selectedAddress = "0x1",
addresses = setOf(Address(value = "0x1", type = Type.Primary)),
amountToCreateAccount = BigDecimal.ONE,
errorMessage = "custom message",
),
),
// endregion
// region Wallet with coin amount and coin transaction
GetNoAccountResultModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
),
transactions = listOf(
createRecentTransaction(
amount = Amount(
blockchain = Blockchain.Ethereum,
value = BigDecimal.ONE,
),
status = TransactionStatus.Unconfirmed,
),
),
),
customMessage = "",
amountToCreateAccount = BigDecimal.ZERO,
expected = NoAccount(
selectedAddress = "0x1",
addresses = setOf(Address(value = "0x1", type = Type.Primary)),
amountToCreateAccount = BigDecimal.ZERO,
errorMessage = "",
),
),
// endregion
// region Wallet with amounts and transactions (coin and token)
GetNoAccountResultModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
tokensAmount = mapOf(usdtToken to BigDecimal.ZERO),
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
Address(value = "0x11", type = AddressType.Legacy),
),
transactions = listOf(
createRecentTransaction(
amount = Amount(
blockchain = Blockchain.Ethereum,
value = BigDecimal.ONE,
),
status = TransactionStatus.Unconfirmed,
),
createRecentTransaction(
amount = Amount(
value = BigDecimal.TEN,
blockchain = Blockchain.Ethereum,
type = AmountType.Token(token = usdtToken),
currencySymbol = "USDT",
),
status = TransactionStatus.Unconfirmed,
),
),
),
customMessage = "",
amountToCreateAccount = BigDecimal.ZERO,
expected = NoAccount(
selectedAddress = "0x1",
addresses = setOf(
Address(value = "0x1", type = Type.Primary),
Address(value = "0x11", type = Type.Secondary),
),
amountToCreateAccount = BigDecimal.ZERO,
errorMessage = "",
),
),
// endregion
)
}
data class GetNoAccountResultModel(
val wallet: Wallet,
val customMessage: String,
val amountToCreateAccount: BigDecimal?,
val expected: UpdateWalletManagerResult,
)
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetUnreachableResult {
@ParameterizedTest
@ProvideTestModels
fun getUnreachableResult(model: GetUnreachableResultModel) {
// Arrange
every { walletManager.wallet } returns model.wallet
// Act
val actual = factory.getUnreachableResult(walletManager = walletManager)
// Assert
Truth.assertThat(actual).isEqualTo(model.expected)
}
private fun provideTestModels() = listOf(
// region Wallet without amount and transactions
GetUnreachableResultModel(
wallet = createWallet(
coinValue = null,
addresses = setOf(Address(value = "0x1", type = AddressType.Default)),
transactions = emptyList(),
),
expected = Unreachable(
selectedAddress = "0x1",
addresses = setOf(Address(value = "0x1", type = Type.Primary)),
),
),
// endregion
// region Wallet with coin amount and without transactions
GetUnreachableResultModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
),
transactions = emptyList(),
),
expected = Unreachable(
selectedAddress = "0x1",
addresses = setOf(Address(value = "0x1", type = Type.Primary)),
),
),
// endregion
// region Wallet with coin amount and coin transaction
GetUnreachableResultModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
),
transactions = listOf(
createRecentTransaction(
amount = Amount(
blockchain = Blockchain.Ethereum,
value = BigDecimal.ONE,
),
status = TransactionStatus.Unconfirmed,
),
),
),
expected = Unreachable(
selectedAddress = "0x1",
addresses = setOf(Address(value = "0x1", type = Type.Primary)),
),
),
// endregion
// region Wallet with amounts and transactions (coin and token)
GetUnreachableResultModel(
wallet = createWallet(
coinValue = BigDecimal.ONE,
tokensAmount = mapOf(usdtToken to BigDecimal.ZERO),
addresses = setOf(
Address(value = "0x1", type = AddressType.Default),
Address(value = "0x11", type = AddressType.Legacy),
),
transactions = listOf(
createRecentTransaction(
amount = Amount(
blockchain = Blockchain.Ethereum,
value = BigDecimal.ONE,
),
status = TransactionStatus.Unconfirmed,
),
createRecentTransaction(
amount = Amount(
value = BigDecimal.TEN,
blockchain = Blockchain.Ethereum,
type = AmountType.Token(token = usdtToken),
currencySymbol = "USDT",
),
status = TransactionStatus.Unconfirmed,
),
),
),
expected = Unreachable(
selectedAddress = "0x1",
addresses = setOf(
Address(value = "0x1", type = Type.Primary),
Address(value = "0x11", type = Type.Secondary),
),
),
),
// endregion
)
}
data class GetUnreachableResultModel(
val wallet: Wallet,
val expected: UpdateWalletManagerResult,
)
private fun createWallet(
coinValue: BigDecimal?,
tokensAmount: Map<Token, BigDecimal> = emptyMap(),
addresses: Set<Address>,
transactions: List<TransactionData.Uncompiled>,
): Wallet {
return Wallet(
blockchain = Blockchain.Ethereum,
addresses = addresses,
publicKey = mockk(),
tokens = setOf(),
).apply {
coinValue?.let(::setCoinValue)
tokensAmount.forEach { (token, amount) -> addTokenValue(value = amount, token = token) }
recentTransactions += transactions
}
}
private fun createRecentTransaction(amount: Amount, status: TransactionStatus): TransactionData.Uncompiled {
return TransactionData.Uncompiled(
amount = amount,
fee = null,
sourceAddress = "0x1",
destinationAddress = "0x2",
status = status,
hash = "hash",
date = Calendar.getInstance().apply {
timeInMillis = 1748251839317
},
extras = null,
contractAddress = null,
)
}
private fun createTxInfo(status: TxInfo.TransactionStatus, amount: BigDecimal): TxInfo {
return TxInfo(
txHash = "hash",
timestampInMillis = 1748251839317,
isOutgoing = true,
destinationType = TxInfo.DestinationType.Single(
addressType = TxInfo.AddressType.User(address = "0x2"),
),
sourceType = TxInfo.SourceType.Single(address = "0x1"),
interactionAddressType = TxInfo.InteractionAddressType.User(address = "0x2"),
status = status,
type = TxInfo.TransactionType.Transfer,
amount = amount,
)
}
private companion object {
val usdtToken = Token(
id = "0x3",
contractAddress = "0x4",
symbol = "USDT",
decimals = 6,
name = "Tether",
)
}
}