Updated on 2026-08-14
This commit is contained in:
parent
d0c4d92a8c
commit
5fea60f2a2
83 changed files with 2801 additions and 775 deletions
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.domain
|
|||
import CreateProductWalletTask
|
||||
import CreateProductWalletTaskResponse
|
||||
import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
import com.tangem.Message
|
||||
import com.tangem.TangemSdk
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
|
|
@ -177,6 +178,10 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
|
|||
}
|
||||
}
|
||||
|
||||
fun getString(@StringRes stringResId: Int, vararg formatArgs: Any?): String {
|
||||
return context.getString(stringResId, formatArgs)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val config = Config(
|
||||
linkedTerminal = true,
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.blockchain.common.BlockchainSdkConfig
|
|||
import com.tangem.tap.common.shop.shopify.ShopifyShop
|
||||
import com.tangem.tap.common.zendesk.ZendeskConfig
|
||||
import com.tangem.tap.domain.configurable.Loader
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
|
|
@ -22,6 +23,7 @@ data class Config(
|
|||
val isCreatingTwinCardsAllowed: Boolean = false,
|
||||
val shopify: ShopifyShop? = null,
|
||||
val zendesk: ZendeskConfig? = null,
|
||||
val saltPayConfig: SaltPayConfig? = null,
|
||||
)
|
||||
|
||||
class ConfigManager {
|
||||
|
|
@ -90,6 +92,7 @@ class ConfigManager {
|
|||
appsFlyerDevKey = values.appsFlyerDevKey,
|
||||
shopify = values.shopifyShop,
|
||||
zendesk = values.zendesk,
|
||||
saltPayConfig = values.saltPay,
|
||||
)
|
||||
defaultConfig = defaultConfig.copy(
|
||||
coinMarketCapKey = values.coinMarketCapKey,
|
||||
|
|
@ -106,6 +109,7 @@ class ConfigManager {
|
|||
appsFlyerDevKey = values.appsFlyerDevKey,
|
||||
shopify = values.shopifyShop,
|
||||
zendesk = values.zendesk,
|
||||
saltPayConfig = values.saltPay,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,32 +2,34 @@ package com.tangem.tap.domain.configurable.config
|
|||
|
||||
import com.tangem.tap.common.shop.shopify.ShopifyShop
|
||||
import com.tangem.tap.common.zendesk.ZendeskConfig
|
||||
import com.tangem.tap.features.onboarding.products.wallet.saltPay.SaltPayConfig
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
||||
class FeatureModel(
|
||||
val isWalletPayIdEnabled: Boolean,
|
||||
val isTopUpEnabled: Boolean,
|
||||
val isSendingToPayIdEnabled: Boolean,
|
||||
val isCreatingTwinCardsAllowed: Boolean,
|
||||
val isWalletPayIdEnabled: Boolean,
|
||||
val isTopUpEnabled: Boolean,
|
||||
val isSendingToPayIdEnabled: Boolean,
|
||||
val isCreatingTwinCardsAllowed: Boolean,
|
||||
)
|
||||
|
||||
class ConfigValueModel(
|
||||
val coinMarketCapKey: String,
|
||||
val mercuryoWidgetId: String,
|
||||
val mercuryoSecret: String,
|
||||
val moonPayApiKey: String,
|
||||
val moonPayApiSecretKey: String,
|
||||
val blockchairApiKey: String?,
|
||||
val blockchairAuthorizationToken: String?,
|
||||
val blockcypherTokens: Set<String>?,
|
||||
val infuraProjectId: String?,
|
||||
val appsFlyerDevKey: String,
|
||||
val shopifyShop: ShopifyShop?,
|
||||
val zendesk: ZendeskConfig?,
|
||||
val tronGridApiKey: String,
|
||||
val coinMarketCapKey: String,
|
||||
val mercuryoWidgetId: String,
|
||||
val mercuryoSecret: String,
|
||||
val moonPayApiKey: String,
|
||||
val moonPayApiSecretKey: String,
|
||||
val blockchairApiKey: String?,
|
||||
val blockchairAuthorizationToken: String?,
|
||||
val blockcypherTokens: Set<String>?,
|
||||
val infuraProjectId: String?,
|
||||
val appsFlyerDevKey: String,
|
||||
val shopifyShop: ShopifyShop?,
|
||||
val zendesk: ZendeskConfig?,
|
||||
val saltPay: SaltPayConfig,
|
||||
val tronGridApiKey: String,
|
||||
)
|
||||
|
||||
class ConfigModel(val features: FeatureModel?, val configValues: ConfigValueModel?) {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ val Card.isWalletDataSupported: Boolean
|
|||
|
||||
val Card.isMultiwalletAllowed: Boolean
|
||||
get() {
|
||||
return !isTangemTwin() && !isStart2Coin && !isTangemNote() && !isSaltPay
|
||||
return !isTangemTwin() && !isStart2Coin && !isTangemNote && !isSaltPay
|
||||
&& (firmwareVersion >= FirmwareVersion.MultiWalletAvailable ||
|
||||
getSingleWallet()?.curve == EllipticCurve.Secp256k1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,20 @@
|
|||
package com.tangem.tap.domain.extensions
|
||||
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumWalletManager
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.DerivationParams
|
||||
import com.tangem.blockchain.common.DerivationStyle
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.blockchain.common.WalletManager
|
||||
import com.tangem.blockchain.common.WalletManagerFactory
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.CardWallet
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.extensions.guard
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.extensions.toMapKey
|
||||
import com.tangem.common.hdWallet.DerivationPath
|
||||
import com.tangem.domain.common.SaltPayWorkaround
|
||||
import com.tangem.domain.common.ScanResponse
|
||||
import com.tangem.domain.common.TapWorkarounds.isTestCard
|
||||
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
|
||||
|
|
@ -20,7 +24,7 @@ import com.tangem.tap.features.wallet.models.Currency
|
|||
fun WalletManagerFactory.makeWalletManagerForApp(
|
||||
scanResponse: ScanResponse,
|
||||
blockchain: Blockchain,
|
||||
derivationParams: DerivationParams?
|
||||
derivationParams: DerivationParams?,
|
||||
): WalletManager? {
|
||||
val card = scanResponse.card
|
||||
if (card.isTestCard && blockchain.getTestnetVersion() == null) return null
|
||||
|
|
@ -62,26 +66,26 @@ fun WalletManagerFactory.makeWalletManagerForApp(
|
|||
makeWalletManager(
|
||||
blockchain = environmentBlockchain,
|
||||
walletPublicKey = wallet.publicKey,
|
||||
curve = wallet.curve
|
||||
curve = wallet.curve,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun WalletManagerFactory.makeWalletManagerForApp(
|
||||
scanResponse: ScanResponse, blockchainNetwork: BlockchainNetwork
|
||||
scanResponse: ScanResponse, blockchainNetwork: BlockchainNetwork,
|
||||
): WalletManager? {
|
||||
return makeWalletManagerForApp(
|
||||
scanResponse,
|
||||
blockchain = blockchainNetwork.blockchain,
|
||||
derivationParams = getDerivationParams(blockchainNetwork.derivationPath, scanResponse.card)
|
||||
derivationParams = getDerivationParams(blockchainNetwork.derivationPath, scanResponse.card),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getDerivationParams(derivationPath: String?, card: Card): DerivationParams? {
|
||||
return derivationPath?.let {
|
||||
DerivationParams.Custom(
|
||||
DerivationPath(it)
|
||||
DerivationPath(it),
|
||||
)
|
||||
} ?: if (!card.settings.isHDWalletAllowed) {
|
||||
null
|
||||
|
|
@ -93,17 +97,19 @@ private fun getDerivationParams(derivationPath: String?, card: Card): Derivation
|
|||
}
|
||||
|
||||
fun WalletManagerFactory.makeWalletManagerForApp(
|
||||
scanResponse: ScanResponse, currency: Currency
|
||||
scanResponse: ScanResponse,
|
||||
currency: Currency,
|
||||
): WalletManager? {
|
||||
return makeWalletManagerForApp(
|
||||
scanResponse,
|
||||
blockchain = currency.blockchain,
|
||||
derivationParams = getDerivationParams(currency.derivationPath, scanResponse.card)
|
||||
derivationParams = getDerivationParams(currency.derivationPath, scanResponse.card),
|
||||
)
|
||||
}
|
||||
|
||||
fun WalletManagerFactory.makeWalletManagersForApp(
|
||||
scanResponse: ScanResponse, blockchains: List<Currency>,
|
||||
scanResponse: ScanResponse,
|
||||
blockchains: List<Currency>,
|
||||
): List<WalletManager> {
|
||||
return blockchains
|
||||
.filter { it.isBlockchain() }
|
||||
|
|
@ -122,7 +128,7 @@ fun WalletManagerFactory.makePrimaryWalletManager(
|
|||
return makeWalletManagerForApp(
|
||||
scanResponse = scanResponse,
|
||||
blockchain = blockchain,
|
||||
derivationParams = derivationParams
|
||||
derivationParams = derivationParams,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -132,4 +138,23 @@ private fun selectWallet(wallets: List<CardWallet>): CardWallet? {
|
|||
1 -> wallets[0]
|
||||
else -> wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: wallets[0]
|
||||
}
|
||||
}
|
||||
|
||||
fun WalletManagerFactory.makeSaltPayWalletManager(
|
||||
scanResponse: ScanResponse,
|
||||
): EthereumWalletManager {
|
||||
val blockchain = scanResponse.getBlockchain()
|
||||
if (blockchain != Blockchain.SaltPay && blockchain != Blockchain.SaltPayTestnet)
|
||||
throw IllegalArgumentException()
|
||||
|
||||
val token = SaltPayWorkaround.tokenFrom(blockchain)
|
||||
val cardWallet = scanResponse.card.wallets.firstOrNull().guard {
|
||||
throw NullPointerException("SaltPay card must have one wallet at least")
|
||||
}
|
||||
|
||||
return makeWalletManager(
|
||||
blockchain = blockchain,
|
||||
publicKey = Wallet.PublicKey(cardWallet.publicKey, null, null),
|
||||
tokens = listOf(token),
|
||||
) as EthereumWalletManager
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
package com.tangem.tap.domain.statePrinter
|
||||
|
||||
import com.squareup.moshi.FromJson
|
||||
import com.squareup.moshi.ToJson
|
||||
import com.tangem.common.json.MoshiJsonConverter
|
||||
import com.tangem.domain.redux.state.StringStateConverter
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.onboarding.products.wallet.redux.BackupStep
|
||||
import com.tangem.tap.store
|
||||
import timber.log.Timber
|
||||
import java.math.BigInteger
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
class OnboardingWalletStateConverter : StringStateConverter<AppState> {
|
||||
private val converter = MoshiJsonConverter(
|
||||
MoshiJsonConverter.getTangemSdkAdapters() + internalAdapters(),
|
||||
MoshiJsonConverter.getTangemSdkTypedAdapters(),
|
||||
)
|
||||
|
||||
private fun internalAdapters(): List<Any> {
|
||||
return listOf(
|
||||
BackupStepAdapter(),
|
||||
BigIntegerAdapter(),
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
override fun convert(stateHolder: AppState): String {
|
||||
val model = stateHolder.onboardingWalletState
|
||||
val json = converter.prettyPrint(model)
|
||||
|
||||
return json
|
||||
}
|
||||
}
|
||||
|
||||
fun printOnboardingWalletState() {
|
||||
val stringState = OnboardingWalletStateConverter().convert(store.state)
|
||||
Timber.d(stringState)
|
||||
}
|
||||
|
||||
class BackupStepAdapter {
|
||||
@ToJson
|
||||
fun toJson(src: BackupStep): String {
|
||||
return when (src) {
|
||||
BackupStep.AddBackupCards -> "AddBackupCards"
|
||||
BackupStep.EnterAccessCode -> "EnterAccessCode"
|
||||
BackupStep.Finished -> "Finished"
|
||||
BackupStep.InitBackup -> "InitBackup"
|
||||
BackupStep.ReenterAccessCode -> "ReenterAccessCode"
|
||||
BackupStep.ScanOriginCard -> "ScanOriginCard"
|
||||
BackupStep.SetAccessCode -> "SetAccessCode"
|
||||
is BackupStep.WriteBackupCard -> "WriteBackupCard[${src.cardNumber}]"
|
||||
BackupStep.WritePrimaryCard -> "WritePrimaryCard"
|
||||
}
|
||||
}
|
||||
|
||||
@FromJson
|
||||
fun fromJson(json: String): BackupStep {
|
||||
return when (json) {
|
||||
"AddBackupCards" -> BackupStep.AddBackupCards
|
||||
"EnterAccessCode" -> BackupStep.EnterAccessCode
|
||||
"Finished" -> BackupStep.Finished
|
||||
"InitBackup" -> BackupStep.InitBackup
|
||||
"ReenterAccessCode" -> BackupStep.ReenterAccessCode
|
||||
"ScanOriginCard" -> BackupStep.ScanOriginCard
|
||||
"SetAccessCode" -> BackupStep.SetAccessCode
|
||||
"WriteBackupCard[1]" -> BackupStep.WriteBackupCard(1)
|
||||
"WriteBackupCard[2]" -> BackupStep.WriteBackupCard(2)
|
||||
"WriteBackupCard[3]" -> BackupStep.WriteBackupCard(3)
|
||||
"WritePrimaryCard" -> BackupStep.WritePrimaryCard
|
||||
else -> throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class BigIntegerAdapter {
|
||||
@ToJson
|
||||
fun toJson(src: BigInteger): String {
|
||||
return src.toString()
|
||||
}
|
||||
|
||||
@FromJson
|
||||
fun fromJson(json: String): BigInteger {
|
||||
return BigInteger(json)
|
||||
}
|
||||
}
|
||||
|
|
@ -18,9 +18,9 @@ import com.tangem.domain.common.ScanResponse
|
|||
import com.tangem.domain.common.TapWorkarounds.isExcluded
|
||||
import com.tangem.domain.common.TapWorkarounds.isNotSupportedInThatRelease
|
||||
import com.tangem.domain.common.TapWorkarounds.isSaltPay
|
||||
import com.tangem.domain.common.TapWorkarounds.isTangemTwins
|
||||
import com.tangem.domain.common.TapWorkarounds.useOldStyleDerivation
|
||||
import com.tangem.domain.common.TwinsHelper
|
||||
import com.tangem.domain.common.isTangemTwins
|
||||
import com.tangem.operations.PreflightReadMode
|
||||
import com.tangem.operations.PreflightReadTask
|
||||
import com.tangem.operations.ScanTask
|
||||
|
|
@ -31,10 +31,9 @@ import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
|
|||
import com.tangem.tap.domain.TapSdkError
|
||||
import com.tangem.tap.domain.extensions.getPrimaryCurve
|
||||
import com.tangem.tap.domain.extensions.getSingleWallet
|
||||
import com.tangem.tap.domain.tokens.UserTokensRepository
|
||||
import com.tangem.tap.domain.extensions.hasNoWallets
|
||||
import com.tangem.tap.domain.extensions.isHdWalletAllowedByApp
|
||||
import com.tangem.tap.domain.extensions.isMultiwalletAllowed
|
||||
import com.tangem.tap.domain.tokens.UserTokensRepository
|
||||
import com.tangem.tap.domain.tokens.models.BlockchainNetwork
|
||||
import com.tangem.tap.preferencesStorage
|
||||
|
||||
|
|
@ -60,7 +59,7 @@ class ScanProductTask(
|
|||
}
|
||||
|
||||
val commandProcessor = when {
|
||||
card.isTangemTwins() -> ScanTwinProcessor()
|
||||
card.isTangemTwins -> ScanTwinProcessor()
|
||||
else -> ScanWalletProcessor(userTokensRepository, additionalBlockchainsToDerive)
|
||||
}
|
||||
commandProcessor.proceed(card, session) { processorResult ->
|
||||
|
|
@ -84,8 +83,8 @@ class ScanProductTask(
|
|||
}
|
||||
|
||||
private fun getErrorIfExcludedCard(card: Card): TangemError? {
|
||||
if (card.isExcluded()) return TapSdkError.CardForDifferentApp
|
||||
if (card.isNotSupportedInThatRelease()) return TapSdkError.CardNotSupportedByRelease
|
||||
if (card.isExcluded) return TapSdkError.CardForDifferentApp
|
||||
if (card.isNotSupportedInThatRelease) return TapSdkError.CardNotSupportedByRelease
|
||||
if (card.isSaltPay && card.hasNoWallets()) return TapSdkError.ScanPrimaryCard
|
||||
return null
|
||||
}
|
||||
|
|
@ -111,6 +110,7 @@ private class ScanWalletProcessor(
|
|||
callback: (result: CompletionResult<ScanResponse>) -> Unit,
|
||||
) {
|
||||
if (card.wallets.isEmpty() || !card.isMultiwalletAllowed) {
|
||||
// match for the saltPay card to
|
||||
startLinkingForBackupIfNeeded(card, session, callback)
|
||||
return
|
||||
}
|
||||
|
|
@ -166,13 +166,17 @@ private class ScanWalletProcessor(
|
|||
session: CardSession,
|
||||
callback: (result: CompletionResult<ScanResponse>) -> Unit,
|
||||
) {
|
||||
val productType = when(card.isSaltPay){
|
||||
true -> ProductType.SaltPay
|
||||
else -> ProductType.Wallet
|
||||
}
|
||||
val derivations = collectDerivations(card)
|
||||
if (derivations.isEmpty() || !card.settings.isHDWalletAllowed) {
|
||||
callback(
|
||||
CompletionResult.Success(
|
||||
ScanResponse(
|
||||
card = card,
|
||||
productType = ProductType.Wallet,
|
||||
productType = productType,
|
||||
walletData = session.environment.walletData,
|
||||
primaryCard = primaryCard,
|
||||
),
|
||||
|
|
@ -186,7 +190,7 @@ private class ScanWalletProcessor(
|
|||
is CompletionResult.Success -> {
|
||||
val response = ScanResponse(
|
||||
card = card,
|
||||
productType = ProductType.Wallet,
|
||||
productType = productType,
|
||||
walletData = session.environment.walletData,
|
||||
derivedKeys = result.data.entries,
|
||||
primaryCard = primaryCard,
|
||||
|
|
|
|||
|
|
@ -93,17 +93,10 @@ class UserTokensRepository(
|
|||
|
||||
private fun Card.getUserId(): String {
|
||||
val walletPublicKey = this.wallets.firstOrNull()?.publicKey ?: return ""
|
||||
return calculateUserId(walletPublicKey)
|
||||
}
|
||||
|
||||
private fun calculateUserId(walletPublicKey: ByteArray): String {
|
||||
val message = MESSAGE.toByteArray()
|
||||
val keyHash = walletPublicKey.calculateSha256()
|
||||
return message.calculateHmacSha256(keyHash).toHexString()
|
||||
return UserWalletId(walletPublicKey).stringValue
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MESSAGE = "UserWalletID"
|
||||
fun init(context: Context, tangemTechService: TangemTechService): UserTokensRepository {
|
||||
val fileReader = AndroidFileReader(context)
|
||||
val oldUserTokensRepository = OldUserTokensRepository(
|
||||
|
|
@ -114,4 +107,16 @@ class UserTokensRepository(
|
|||
return UserTokensRepository(storageService, networkService)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class UserWalletId(
|
||||
val walletPublicKey: ByteArray,
|
||||
) {
|
||||
val stringValue: String = calculateUserId(walletPublicKey)
|
||||
|
||||
private fun calculateUserId(walletPublicKey: ByteArray): String {
|
||||
val message = "UserWalletID".toByteArray()
|
||||
val keyHash = walletPublicKey.calculateSha256()
|
||||
return message.calculateHmacSha256(keyHash).toHexString()
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue