Updated on 2026-08-14

This commit is contained in:
Tangem 2021-12-07 19:17:57 +00:00
commit c44d6e444d
41 changed files with 586 additions and 311 deletions

View file

@ -77,9 +77,9 @@ dependencies {
implementation 'com.google.android.play:core-ktx:1.8.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'com.tangem:blockchain:develop-48'
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-100'
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-100'
implementation 'com.tangem:blockchain:develop-50'
implementation 'com.tangem.tangem-sdk-kotlin:core:develop-104'
implementation 'com.tangem.tangem-sdk-kotlin:android:develop-104'
// WebView
implementation "androidx.browser:browser:1.3.0"

View file

@ -0,0 +1,17 @@
package com.tangem.tap.common.extensions
/**
[REDACTED_AUTHOR]
*/
data class ByteArrayKey(val bytes: ByteArray) {
override fun equals(other: Any?): Boolean {
return this === other || other is ByteArrayKey && this.bytes.contentEquals(other.bytes)
}
override fun hashCode(): Int = bytes.contentHashCode()
}
fun ByteArray.toMapKey(): ByteArrayKey = ByteArrayKey(this)
fun ByteArrayKey.bytes(): ByteArray = this.bytes

View file

@ -36,6 +36,7 @@ sealed class GlobalAction : Action {
}
data class ScanCard(
val shouldDeriveWC: Boolean,
val onSuccess: ((ScanResponse) -> Unit)? = null,
val onFailure: ((TangemError) -> Unit)? = null,
val messageResId: Int? = null,

View file

@ -3,6 +3,7 @@ package com.tangem.tap.common.redux.global
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.services.Result
import com.tangem.tap.*
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.dispatchDialogShow
import com.tangem.tap.common.extensions.dispatchOnMain
@ -13,10 +14,6 @@ import com.tangem.tap.domain.configurable.warningMessage.WarningMessagesManager
import com.tangem.tap.features.send.redux.SendAction
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.network.moonpay.MoonpayService
import com.tangem.tap.preferencesStorage
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
import timber.log.Timber
@ -91,7 +88,12 @@ private val globalMiddlewareHandler: Middleware<AppState> = { dispatch, appState
}
is GlobalAction.ScanCard -> {
scope.launch {
val result = tangemSdkManager.scanProduct(FirebaseAnalyticsHandler, action.messageResId)
val result = tangemSdkManager.scanProduct(
FirebaseAnalyticsHandler,
currenciesRepository,
action.shouldDeriveWC,
action.messageResId
)
store.dispatch(GlobalAction.ScanFailsCounter.ChooseBehavior(result))
withMainContext {
when (result) {

View file

@ -1,6 +1,6 @@
package com.tangem.tap.domain
import CreateProductWalletAndRescanTask
import CreateProductWalletTask
import android.content.Context
import com.tangem.Message
import com.tangem.TangemSdk
@ -12,7 +12,10 @@ import com.tangem.common.card.FirmwareVersion
import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.Config
import com.tangem.common.core.TangemSdkError
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.operations.CommandResponse
import com.tangem.operations.derivation.DeriveWalletPublicKeysTask
import com.tangem.operations.derivation.ExtendedPublicKeyList
import com.tangem.operations.pins.CheckUserCodesCommand
import com.tangem.operations.pins.CheckUserCodesResponse
import com.tangem.operations.pins.SetUserCodeCommand
@ -23,6 +26,7 @@ import com.tangem.tap.domain.tasks.CreateWalletAndRescanTask
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
import com.tangem.tap.domain.tasks.product.ScanProductTask
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.tokens.CurrenciesRepository
import com.tangem.wallet.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -33,18 +37,20 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
suspend fun scanProduct(
analyticsHandler: AnalyticsHandler,
currenciesRepository: CurrenciesRepository,
shouldDeriveWC: Boolean,
messageRes: Int? = null,
): CompletionResult<ScanResponse> {
analyticsHandler.triggerEvent(AnalyticsEvent.READY_TO_SCAN, null)
val message = Message(context.getString(messageRes ?: R.string.initial_message_scan_header))
return runTaskAsyncReturnOnMain(ScanProductTask(), null, message)
.also { sendScanFailuresToAnalytics(analyticsHandler, it) }
return runTaskAsyncReturnOnMain(ScanProductTask(null, currenciesRepository, shouldDeriveWC), null, message)
.also { sendScanFailuresToAnalytics(analyticsHandler, it) }
}
suspend fun createProductWallet(scanResponse: ScanResponse): CompletionResult<Card> {
return runTaskAsync(
CreateProductWalletAndRescanTask(scanResponse.productType),
CreateProductWalletTask(scanResponse.productType),
scanResponse.card.cardId,
Message(context.getString(R.string.initial_message_create_wallet_body))
)
@ -66,6 +72,14 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
initialMessage = Message(context.getString(R.string.initial_message_create_wallet_body)))
}
suspend fun derivePublicKeys(
cardId: String,
walletPublicKey: ByteArray,
derivationPaths: List<DerivationPath>
): CompletionResult<ExtendedPublicKeyList> {
return runTaskAsyncReturnOnMain(DeriveWalletPublicKeysTask(walletPublicKey, derivationPaths), cardId)
}
suspend fun resetToFactorySettings(card: Card): CompletionResult<Card> {
return runTaskAsyncReturnOnMain(
ResetToFactorySettingsTask(),
@ -104,13 +118,13 @@ class TangemSdkManager(private val tangemSdk: TangemSdk, private val context: Co
suspend fun <T : CommandResponse> runTaskAsync(
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null,
): CompletionResult<T> =
withContext(Dispatchers.Main) {
suspendCoroutine { continuation ->
tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result ->
continuation.resume(result)
}
withContext(Dispatchers.Main) {
suspendCoroutine { continuation ->
tangemSdk.startSessionWithRunnable(runnable, cardId, initialMessage) { result ->
continuation.resume(result)
}
}
}
private suspend fun <T : CommandResponse> runTaskAsyncReturnOnMain(
runnable: CardSessionRunnable<T>, cardId: String? = null, initialMessage: Message? = null,

View file

@ -3,6 +3,7 @@ package com.tangem.tap.domain
import com.tangem.Message
import com.tangem.TangemSdk
import com.tangem.blockchain.common.TransactionSigner
import com.tangem.blockchain.common.Wallet
import com.tangem.common.CompletionResult
import com.tangem.tap.domain.tasks.SignHashTask
import com.tangem.tap.domain.tasks.SignHashesTask
@ -15,13 +16,9 @@ class TangemSigner(
private val signerCallback: (TangemSignerResponse) -> Unit,
) : TransactionSigner {
override suspend fun sign(
hash: ByteArray,
cardId: String,
walletPublicKey: ByteArray,
): CompletionResult<ByteArray> =
suspendCoroutine { continuation ->
val command = SignHashTask(hash, walletPublicKey)
override suspend fun sign(hash: ByteArray, cardId: String, publicKey: Wallet.PublicKey): CompletionResult<ByteArray> {
return suspendCoroutine { continuation ->
val command = SignHashTask(hash, publicKey)
tangemSdk.startSessionWithRunnable(
runnable = command,
cardId = cardId,
@ -42,15 +39,11 @@ class TangemSigner(
}
}
}
}
override suspend fun sign(
hashes: List<ByteArray>,
cardId: String,
walletPublicKey: ByteArray,
): CompletionResult<List<ByteArray>> =
suspendCoroutine { continuation ->
val task = SignHashesTask(hashes, walletPublicKey)
override suspend fun sign(hashes: List<ByteArray>, cardId: String, publicKey: Wallet.PublicKey): CompletionResult<List<ByteArray>> {
return suspendCoroutine { continuation ->
val task = SignHashesTask(hashes, publicKey)
tangemSdk.startSessionWithRunnable(
runnable = task,
cardId = cardId,
@ -71,6 +64,7 @@ class TangemSigner(
}
}
}
}
}
data class TangemSignerResponse(

View file

@ -1,7 +1,6 @@
package com.tangem.tap.domain
import com.tangem.blockchain.common.*
import com.tangem.common.card.Card
import com.tangem.common.services.Result
import com.tangem.tap.common.analytics.AnalyticsEvent
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
@ -131,7 +130,7 @@ class TapWalletManager {
store.dispatch(WalletAction.MultiWallet.SetPrimaryToken(primaryToken))
}
if (data.card.isMultiwalletAllowed) {
loadMultiWalletData(data.card, blockchain, primaryWalletManager)
loadMultiWalletData(data, blockchain, primaryWalletManager)
} else {
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(primaryWalletManager))
store.dispatch(WalletAction.MultiWallet.AddBlockchains(listOf(blockchain)))
@ -139,7 +138,7 @@ class TapWalletManager {
} else {
if (data.card.isMultiwalletAllowed) {
loadMultiWalletData(data.card, blockchain, null)
loadMultiWalletData(data, blockchain, null)
}
}
val moonPayStatus = store.state.globalState.moonpayStatus
@ -149,10 +148,10 @@ class TapWalletManager {
}
private fun loadMultiWalletData(
card: Card, primaryBlockchain: Blockchain?, primaryWalletManager: WalletManager?
scanResponse: ScanResponse, primaryBlockchain: Blockchain?, primaryWalletManager: WalletManager?
) {
val primaryTokens = primaryWalletManager?.cardTokens?.toList() ?: emptyList()
val savedCurrencies = currenciesRepository.loadCardCurrencies(card.cardId)
val savedCurrencies = currenciesRepository.loadCardCurrencies(scanResponse.card.cardId)
if (savedCurrencies == null) {
if (primaryBlockchain != null && primaryWalletManager != null) {
@ -169,11 +168,11 @@ class TapWalletManager {
CardCurrencies(blockchains = blockchains, tokens = emptyList())
))
val walletManagers =
walletManagerFactory.makeWalletManagersForApp(card, blockchains.toList())
walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains.toList())
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManagers))
store.dispatch(WalletAction.MultiWallet.AddBlockchains(blockchains.toList()))
}
store.dispatch(WalletAction.MultiWallet.FindBlockchainsInUse(card, walletManagerFactory))
store.dispatch(WalletAction.MultiWallet.FindBlockchainsInUse)
store.dispatch(WalletAction.MultiWallet.FindTokensInUse)
} else {
val blockchains = savedCurrencies.blockchains.toList()
@ -182,10 +181,10 @@ class TapWalletManager {
primaryWalletManager != null && primaryBlockchain != null
) {
val blockchainsWithoutPrimary = blockchains.filterNot { it == primaryBlockchain }
walletManagerFactory.makeWalletManagersForApp(card, blockchainsWithoutPrimary)
walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchainsWithoutPrimary)
.plus(primaryWalletManager)
} else {
walletManagerFactory.makeWalletManagersForApp(card, blockchains)
walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains)
}
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManagers))

View file

@ -3,74 +3,70 @@ package com.tangem.tap.domain.extensions
import com.tangem.blockchain.common.Blockchain
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.hexToBytes
import com.tangem.tap.common.extensions.toMapKey
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.tasks.product.ScanResponse
fun WalletManagerFactory.makeWalletManagerForApp(
card: Card,
blockchain: Blockchain,
scanResponse: ScanResponse, blockchain: Blockchain
): WalletManager? {
val card = scanResponse.card
if (card.isTestCard && blockchain.getTestnetVersion() == null) return null
val supportedCurves = blockchain.getSupportedCurves() ?: return null
val wallets = card.wallets.filter { wallet -> supportedCurves.contains(wallet.curve) }
val wallet = selectWallet(wallets)
val publicKey = wallet?.publicKey ?: return null
val curveToUse = wallet.curve ?: return null
return if (card.isTestCard) {
blockchain.getTestnetVersion()?.let {
makeWalletManager(card.cardId, publicKey, it, curveToUse)
val wallet = selectWallet(wallets) ?: return null
val environmentBlockchain = if (card.isTestCard) blockchain.getTestnetVersion()!! else blockchain
val seedKey = wallet.extendedPublicKey
return when {
scanResponse.isTangemTwins() && scanResponse.secondTwinPublicKey != null -> {
makeTwinWalletManager(
card.cardId,
wallet.publicKey, scanResponse.secondTwinPublicKey.hexToBytes(),
environmentBlockchain, wallet.curve
)
}
seedKey != null -> {
val derivedKeys = scanResponse.derivedKeys[wallet.publicKey.toMapKey()]
val derivedKey = derivedKeys?.firstOrNull { it.derivationPath == blockchain.derivationPath() }
?: return null
makeWalletManager(card.cardId, environmentBlockchain, seedKey, derivedKey)
}
else -> {
makeWalletManager(card.cardId, environmentBlockchain, wallet.publicKey, wallet.curve)
}
} else {
makeWalletManager(card.cardId, publicKey, blockchain, curveToUse)
}
}
fun WalletManagerFactory.makeWalletManagersForApp(
scanResponse: ScanResponse, blockchains: List<Blockchain>,
): List<WalletManager> {
val isTestCard = scanResponse.card.isTestCard
val filteredBlockchains = blockchains.mapNotNull { if (isTestCard) it.getTestnetVersion() else it }
return filteredBlockchains.mapNotNull { makeWalletManagerForApp(scanResponse, it) }
}
fun WalletManagerFactory.makePrimaryWalletManager(
scanResponse: ScanResponse,
): WalletManager? {
val blockchain = if (scanResponse.card.isTestCard) {
scanResponse.getBlockchain().getTestnetVersion() ?: return null
} else {
scanResponse.getBlockchain()
}
return makeWalletManagerForApp(scanResponse, blockchain)
}
private fun selectWallet(wallets: List<CardWallet>): CardWallet? {
return when (wallets.size) {
0 -> null
1 -> wallets[0]
else -> wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: wallets[0]
}
}
fun WalletManagerFactory.makeWalletManagersForApp(
card: Card, blockchains: List<Blockchain>,
): List<WalletManager> {
return if (card.isTestCard) {
blockchains.mapNotNull { blockchain ->
blockchain.getTestnetVersion()?.let { makeWalletManagerForApp(card, it) }
}
} else {
blockchains.mapNotNull { blockchain -> makeWalletManagerForApp(card, blockchain) }
}
}
fun WalletManagerFactory.makePrimaryWalletManager(
data: ScanResponse,
): WalletManager? {
val card = data.card
val blockchain = if (card.isTestCard) {
data.getBlockchain().getTestnetVersion()
} else {
data.getBlockchain()
}
val supportedCurves = blockchain?.getSupportedCurves() ?: return null
val wallets = card.wallets.filter { wallet -> supportedCurves.contains(wallet.curve) }
val wallet = selectWallet(wallets)
val publicKey = wallet?.publicKey ?: return null
val curveToUse = wallet.curve
return if (data.isTangemTwins() && data.secondTwinPublicKey != null) {
makeMultisigWalletManager(
cardId = card.cardId,
walletPublicKey = publicKey, pairPublicKey = data.secondTwinPublicKey.hexToBytes(),
blockchain = blockchain, curve = curveToUse
)
} else {
makeWalletManager(card.cardId, publicKey, blockchain, curveToUse)
}
}

View file

@ -9,7 +9,7 @@ import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
import com.tangem.operations.PreflightReadMode
import com.tangem.operations.PreflightReadTask
import com.tangem.operations.wallet.CreateWalletCommand
import com.tangem.operations.wallet.CreateWalletTask
@Deprecated("Use CreateProductWalletAndRescanTask instead")
class CreateWalletAndRescanTask : CardSessionRunnable<Card> {
@ -22,7 +22,7 @@ class CreateWalletAndRescanTask : CardSessionRunnable<Card> {
val firmwareVersion = card.firmwareVersion
val task = if (firmwareVersion < FirmwareVersion.MultiWalletAvailable) {
CreateWalletCommand(card.supportedCurves.first())
CreateWalletTask(card.supportedCurves.first())
} else {
CreateWalletsTask()
}

View file

@ -7,9 +7,9 @@ import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.operations.PreflightReadMode
import com.tangem.operations.PreflightReadTask
import com.tangem.operations.wallet.CreateWalletCommand
import com.tangem.operations.wallet.CreateWalletTask
@Deprecated("Use CreateProductWalletsResponse instead")
@Deprecated("Use CreateProductWalletTask instead")
class CreateWalletsTask(curves: List<EllipticCurve>? = null) : CardSessionRunnable<Card> {
private val curves = curves ?: listOf(
@ -30,7 +30,7 @@ class CreateWalletsTask(curves: List<EllipticCurve>? = null) : CardSessionRunnab
callback: (result: CompletionResult<Card>) -> Unit
) {
CreateWalletCommand(curve).run(session) { result ->
CreateWalletTask(curve).run(session) { result ->
when (result) {
is CompletionResult.Success -> {
if (index == curves.lastIndex) {

View file

@ -1,5 +1,6 @@
package com.tangem.tap.domain.tasks
import com.tangem.blockchain.common.Wallet
import com.tangem.common.CompletionResult
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
@ -15,17 +16,17 @@ class TangemSignHashResponse(
class SignHashTask(
private val hash: ByteArray,
private val walletPublicKey: ByteArray,
private val publicKey: Wallet.PublicKey,
) : CardSessionRunnable<TangemSignHashResponse> {
override fun run(session: CardSession, callback: CompletionCallback<TangemSignHashResponse>) {
SignHashCommand(hash, walletPublicKey).run(session) { response ->
SignHashCommand(hash, publicKey.seedKey, publicKey.derivationPath).run(session) { response ->
when (response) {
is CompletionResult.Success -> {
callback(CompletionResult.Success(TangemSignHashResponse(
response.data.signature,
response.data.totalSignedHashes,
session.environment.card?.wallet(walletPublicKey)?.remainingSignatures
session.environment.card?.wallet(publicKey.seedKey)?.remainingSignatures
)))
}
is CompletionResult.Failure ->

View file

@ -1,5 +1,6 @@
package com.tangem.tap.domain.tasks
import com.tangem.blockchain.common.Wallet
import com.tangem.common.CompletionResult
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
@ -15,16 +16,16 @@ class TangemSignHashesResponse(
class SignHashesTask(
private val hashes: Collection<ByteArray>,
private val walletPublicKey: ByteArray,
private val publicKey: Wallet.PublicKey,
) : CardSessionRunnable<TangemSignHashesResponse> {
override fun run(session: CardSession, callback: CompletionCallback<TangemSignHashesResponse>) {
SignHashesCommand(hashes.toTypedArray(), walletPublicKey).run(session) { response ->
SignHashesCommand(hashes.toTypedArray(), publicKey.seedKey, publicKey.derivationPath).run(session) { response ->
when (response) {
is CompletionResult.Success -> {
callback(CompletionResult.Success(TangemSignHashesResponse(
response.data.signatures,
response.data.totalSignedHashes,
session.environment.card?.wallet(walletPublicKey)?.remainingSignatures
session.environment.card?.wallet(publicKey.seedKey)?.remainingSignatures
)))
}
is CompletionResult.Failure ->

View file

@ -1,25 +1,29 @@
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.CompletionResult
import com.tangem.common.card.Card
import com.tangem.common.card.CardWallet
import com.tangem.common.card.EllipticCurve
import com.tangem.common.card.FirmwareVersion
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
import com.tangem.operations.PreflightReadMode
import com.tangem.operations.PreflightReadTask
import com.tangem.operations.wallet.CreateWalletCommand
import com.tangem.operations.derivation.DeriveWalletPublicKeysTask
import com.tangem.operations.derivation.ExtendedPublicKeyList
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.operations.wallet.CreateWalletTask
import com.tangem.tap.common.extensions.toMapKey
import com.tangem.tap.domain.ProductType
import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain
import com.tangem.tap.domain.tasks.product.CreateProductWalletsTask
import com.tangem.tap.domain.tasks.product.CreateWalletsTask
import com.tangem.tap.domain.tasks.product.ProductCommandProcessor
import com.tangem.tap.domain.tasks.product.getCurvesForNonCreatedWallets
import com.tangem.tap.store
/**
[REDACTED_AUTHOR]
*/
class CreateProductWalletAndRescanTask(
class CreateProductWalletTask(
private val type: ProductType,
) : CardSessionRunnable<Card> {
@ -28,33 +32,19 @@ class CreateProductWalletAndRescanTask(
callback(CompletionResult.Failure(TangemSdkError.CardError()))
return
}
CreateProductWalletTask(card, type).run(session) { result ->
when (result) {
is CompletionResult.Success -> PreflightReadTask(PreflightReadMode.FullCardRead).run(
session,
callback)
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}
}
}
private class CreateProductWalletTask(
private val card: Card,
private val type: ProductType,
) : CardSessionRunnable<CreateWalletResponse> {
override fun run(
session: CardSession,
callback: (result: CompletionResult<CreateWalletResponse>) -> Unit,
) {
val commandProcessor = when (type) {
ProductType.Note -> CreateWalletTangemNote()
ProductType.Twins -> throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet")
ProductType.Wallet -> CreateWalletTangemWallet()
else -> CreateWalletOtherCards()
}
commandProcessor.proceed(card, session, callback)
commandProcessor.proceed(card, session) {
when (it) {
is CompletionResult.Success -> callback(CompletionResult.Success(session.environment.card!!))
is CompletionResult.Failure -> callback(CompletionResult.Failure(it.error))
}
}
}
}
@ -84,13 +74,13 @@ private class CreateWalletTangemNote : ProductCommandProcessor<CreateWalletRespo
} else {
intersectCurves[0]
}
CreateWalletCommand(curve).run(session, callback)
CreateWalletTask(curve).run(session, callback)
}
}
}
private class CreateWalletTangemWallet : ProductCommandProcessor<CreateWalletResponse> {
override fun proceed(
card: Card,
session: CardSession,
@ -98,16 +88,37 @@ private class CreateWalletTangemWallet : ProductCommandProcessor<CreateWalletRes
) {
val supportedCurves = setOf(EllipticCurve.Secp256k1, EllipticCurve.Ed25519)
val curves = card.getCurvesForNonCreatedWallets().intersect(supportedCurves).toList()
CreateProductWalletsTask(curves).run(session) { result ->
CreateWalletsTask(curves).run(session) { result ->
when (result) {
is CompletionResult.Success ->
callback(CompletionResult.Success(result.data.createWalletResponses[0]))
is CompletionResult.Success -> {
val response = result.data.createWalletResponses[0]
val derivationPaths = listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
.mapNotNull { it.derivationPath() }
DeriveWalletPublicKeysTask(response.wallet.publicKey, derivationPaths).run(session) {
when (it) {
is CompletionResult.Success -> {
updateDerivedKeys(response.wallet, it.data)
callback(CompletionResult.Success(response))
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(it.error))
}
}
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}
}
}
//TODO: updating derived wallet public keys - make it better later
private fun updateDerivedKeys(wallet: CardWallet, derivedKeys: ExtendedPublicKeyList) {
val onboardingManager = store.state.globalState.onboardingManager ?: return
onboardingManager.scanResponse = onboardingManager.scanResponse.copy(
derivedKeys = mapOf(wallet.publicKey.toMapKey() to derivedKeys)
)
}
private class CreateWalletOtherCards : ProductCommandProcessor<CreateWalletResponse> {
override fun proceed(
@ -117,9 +128,9 @@ private class CreateWalletOtherCards : ProductCommandProcessor<CreateWalletRespo
) {
val firmwareVersion = card.firmwareVersion
val task = if (firmwareVersion < FirmwareVersion.MultiWalletAvailable) {
CreateProductWalletsTask(listOf(card.supportedCurves.first()))
CreateWalletsTask(listOf(card.supportedCurves.first()))
} else {
CreateProductWalletsTask(card.getCurvesForNonCreatedWallets())
CreateWalletsTask(card.getCurvesForNonCreatedWallets())
}
task.run(session) { result ->

View file

@ -5,23 +5,23 @@ import com.tangem.common.card.EllipticCurve
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.operations.CommandResponse
import com.tangem.operations.wallet.CreateWalletCommand
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.operations.wallet.CreateWalletTask
/**
[REDACTED_AUTHOR]
*/
class CreateProductWalletsResponse(
class CreateWalletsResponse(
val createWalletResponses: List<CreateWalletResponse>
) : CommandResponse
class CreateProductWalletsTask(
class CreateWalletsTask(
private val curves: List<EllipticCurve>,
) : CardSessionRunnable<CreateProductWalletsResponse> {
) : CardSessionRunnable<CreateWalletsResponse> {
private val createdWalletsResponses = mutableListOf<CreateWalletResponse>()
override fun run(session: CardSession, callback: (result: CompletionResult<CreateProductWalletsResponse>) -> Unit) {
override fun run(session: CardSession, callback: (result: CompletionResult<CreateWalletsResponse>) -> Unit) {
val curve = curves[createdWalletsResponses.size]
createWallet(curve, session, callback)
}
@ -29,15 +29,14 @@ class CreateProductWalletsTask(
private fun createWallet(
curve: EllipticCurve,
session: CardSession,
callback: (result: CompletionResult<CreateProductWalletsResponse>) -> Unit
callback: (result: CompletionResult<CreateWalletsResponse>) -> Unit
) {
CreateWalletCommand(curve).run(session) { result ->
CreateWalletTask(curve).run(session) { result ->
when (result) {
is CompletionResult.Success -> {
createdWalletsResponses.add(result.data)
if (createdWalletsResponses.size == curves.size) {
callback(CompletionResult.Success(CreateProductWalletsResponse(createdWalletsResponses)))
callback(CompletionResult.Success(CreateWalletsResponse(createdWalletsResponses)))
return@run
}
createWallet(curves[createdWalletsResponses.size], session, callback)

View file

@ -14,11 +14,16 @@ import com.tangem.common.core.TangemError
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.toHexString
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.common.hdWallet.ExtendedPublicKey
import com.tangem.operations.CommandResponse
import com.tangem.operations.PreflightReadMode
import com.tangem.operations.PreflightReadTask
import com.tangem.operations.ScanTask
import com.tangem.operations.derivation.DeriveWalletPublicKeysTask
import com.tangem.operations.issuerAndUserData.ReadIssuerDataCommand
import com.tangem.tap.common.extensions.ByteArrayKey
import com.tangem.tap.common.extensions.toMapKey
import com.tangem.tap.domain.ProductType
import com.tangem.tap.domain.TapSdkError
import com.tangem.tap.domain.TapWorkarounds
@ -26,6 +31,7 @@ import com.tangem.tap.domain.TapWorkarounds.getTangemNoteBlockchain
import com.tangem.tap.domain.TapWorkarounds.isExcluded
import com.tangem.tap.domain.TapWorkarounds.isNotSupportedInThatRelease
import com.tangem.tap.domain.extensions.getSingleWallet
import com.tangem.tap.domain.tokens.CurrenciesRepository
import com.tangem.tap.domain.twins.TwinsHelper
data class ScanResponse(
@ -33,6 +39,7 @@ data class ScanResponse(
val productType: ProductType,
val walletData: WalletData?,
val secondTwinPublicKey: String? = null,
val derivedKeys: Map<KeyWalletPublicKey, List<ExtendedPublicKey>> = mapOf()
) : CommandResponse {
fun getBlockchain(): Blockchain {
@ -60,7 +67,13 @@ data class ScanResponse(
fun twinsIsTwinned(): Boolean = card.isTangemTwins() && walletData != null && secondTwinPublicKey != null
}
class ScanProductTask(val card: Card? = null) : CardSessionRunnable<ScanResponse> {
typealias KeyWalletPublicKey = ByteArrayKey
class ScanProductTask(
val card: Card? = null,
private val currenciesRepository: CurrenciesRepository?,
private val shouldDeriveWC: Boolean
) : CardSessionRunnable<ScanResponse> {
override fun run(
session: CardSession,
@ -80,7 +93,7 @@ class ScanProductTask(val card: Card? = null) : CardSessionRunnable<ScanResponse
val commandProcessor = when {
TapWorkarounds.isTangemNote(card) -> ScanNoteProcessor()
card.isTangemTwins() -> ScanTwinProcessor()
TapWorkarounds.isTangemWallet(card) -> ScanWalletProcessor()
TapWorkarounds.isTangemWallet(card) -> ScanWalletProcessor(currenciesRepository, shouldDeriveWC)
else -> ScanOtherCardsProcessor()
}
commandProcessor.proceed(card, session) { processorResult ->
@ -116,13 +129,60 @@ private class ScanNoteProcessor : ProductCommandProcessor<ScanResponse> {
}
}
private class ScanWalletProcessor : ProductCommandProcessor<ScanResponse> {
private class ScanWalletProcessor(
private val currenciesRepository: CurrenciesRepository?,
private val shouldDeriveWC: Boolean
) : ProductCommandProcessor<ScanResponse> {
override fun proceed(
card: Card,
session: CardSession,
callback: (result: CompletionResult<ScanResponse>) -> Unit
) {
callback(CompletionResult.Success(ScanResponse(card, ProductType.Wallet, session.environment.walletData)))
val derivationPaths = collectDerivationPaths(card)?.distinct()
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
if (derivationPaths.isNullOrEmpty() || wallet == null || wallet.chainCode == null) {
callback(CompletionResult.Success(ScanResponse(card, ProductType.Wallet, session.environment.walletData)))
return
}
DeriveWalletPublicKeysTask(wallet.publicKey, derivationPaths).run(session) { result ->
when (result) {
is CompletionResult.Success -> {
val derivedKeys = mapOf(wallet.publicKey.toMapKey() to result.data)
val response = ScanResponse(
card,
ProductType.Wallet,
session.environment.walletData,
derivedKeys = derivedKeys
)
callback(CompletionResult.Success(response))
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(result.error))
}
}
}
private fun collectDerivationPaths(card: Card): List<DerivationPath>? {
val currenciesRepository = currenciesRepository ?: return null
val cardCurrencies = currenciesRepository.loadCardCurrencies(card.cardId)
val blockchainsToDerive = if (cardCurrencies == null) {
mutableListOf(Blockchain.Bitcoin, Blockchain.Ethereum)
} else {
val tokenBlockchains = cardCurrencies.tokens.map { it.blockchain }
(cardCurrencies.blockchains + tokenBlockchains).toMutableList()
}
if (shouldDeriveWC) {
blockchainsToDerive.addAll(listOf(Blockchain.Ethereum, Blockchain.Binance, Blockchain.EthereumTestnet))
}
return blockchainsToDerive.toSet()
.filter { it.getSupportedCurves()?.contains(EllipticCurve.Secp256k1) == true }
.mapNotNull { it.derivationPath() }
}
}
@ -174,7 +234,7 @@ private class ScanOtherCardsProcessor : ProductCommandProcessor<ScanResponse> {
return
}
CreateProductWalletsTask(curvesToCreate).run(session) { result ->
CreateWalletsTask(curvesToCreate).run(session) { result ->
when (result) {
is CompletionResult.Success -> {
PreflightReadTask(PreflightReadMode.FullCardRead, card.cardId).run(session) { readResult ->

View file

@ -35,7 +35,7 @@ class TopUpManager {
store.dispatch(
GlobalAction.UpdateWalletSignedHashes(
walletSignedHashes = signResponse.totalSignedHashes,
walletPublicKey = walletManager.wallet.publicKey,
walletPublicKey = walletManager.wallet.publicKey.seedKey,
remainingSignatures = signResponse.remainingSignatures
)
)

View file

@ -4,8 +4,8 @@ import com.tangem.common.CompletionResult
import com.tangem.common.card.EllipticCurve
import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.operations.wallet.CreateWalletCommand
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.operations.wallet.CreateWalletTask
import com.tangem.operations.wallet.PurgeWalletCommand
import com.tangem.tap.domain.extensions.getSingleWallet
@ -20,15 +20,13 @@ class CreateFirstTwinWalletTask : CardSessionRunnable<CreateWalletResponse> {
when (response) {
is CompletionResult.Success -> {
session.environment.card = session.environment.card?.setWallets(emptyList())
CreateWalletCommand(EllipticCurve.Secp256k1)
.run(session) { callback(it) }
CreateWalletTask(EllipticCurve.Secp256k1).run(session) { callback(it) }
}
is CompletionResult.Failure -> callback(CompletionResult.Failure(response.error))
}
}
} else {
CreateWalletCommand(EllipticCurve.Secp256k1)
.run(session) { callback(it) }
CreateWalletTask(EllipticCurve.Secp256k1).run(session) { callback(it) }
}
}
}

View file

@ -8,8 +8,8 @@ import com.tangem.common.core.CardSession
import com.tangem.common.core.CardSessionRunnable
import com.tangem.common.core.TangemError
import com.tangem.common.extensions.hexToBytes
import com.tangem.operations.wallet.CreateWalletCommand
import com.tangem.operations.wallet.CreateWalletResponse
import com.tangem.operations.wallet.CreateWalletTask
import com.tangem.operations.wallet.PurgeWalletCommand
import com.tangem.tap.domain.extensions.getSingleWallet
import com.tangem.wallet.R
@ -47,7 +47,7 @@ class CreateSecondTwinWalletTask(
private fun finishTask(session: CardSession, callback: (result: CompletionResult<CreateWalletResponse>) -> Unit) {
session.setMessage(creatingWalletMessage)
CreateWalletCommand(EllipticCurve.Secp256k1).run(session) { result ->
CreateWalletTask(EllipticCurve.Secp256k1).run(session) { result ->
when (result) {
is CompletionResult.Success -> {
session.environment.card = session.environment.card?.updateWallet(result.data.wallet)

View file

@ -23,7 +23,7 @@ class FinalizeTwinTask(
PreflightReadTask(PreflightReadMode.FullCardRead).run(session) { readResult ->
when (readResult) {
is CompletionResult.Success ->
ScanProductTask(readResult.data).run(session, callback)
ScanProductTask(readResult.data, null, false).run(session, callback)
is CompletionResult.Failure ->
callback(CompletionResult.Failure(readResult.error))
}

View file

@ -2,7 +2,6 @@ package com.tangem.tap.domain.walletconnect
import com.tangem.blockchain.common.Blockchain
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.hexToBytes
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.global.GlobalAction
@ -97,10 +96,9 @@ class WalletConnectManager {
val activeData = sessions[session] ?: return
removeSimilarSessions(activeData)
val accounts = listOf(Blockchain.Ethereum.makeAddresses(activeData.wallet.walletPublicKey).first().value)
val approved = activeData.client.approveSession(
accounts = listOf(Blockchain.Ethereum.makeAddresses(
activeData.wallet.walletPublicKey.hexToBytes()).first().value
),
accounts = accounts,
chainId = activeData.wallet.chainId
)
if (approved) {

View file

@ -5,14 +5,12 @@ import com.tangem.blockchain.blockchains.ethereum.EthereumGasLoader
import com.tangem.blockchain.blockchains.ethereum.EthereumTransactionExtras
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.Companion.toKeccak
import com.tangem.blockchain.common.Amount
import com.tangem.blockchain.common.AmountType
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionSender
import com.tangem.blockchain.common.*
import com.tangem.blockchain.extensions.*
import com.tangem.common.CompletionResult
import com.tangem.common.core.TangemSdkError
import com.tangem.common.extensions.hexToBytes
import com.tangem.crypto.CryptoUtils
import com.tangem.operations.sign.SignHashCommand
import com.tangem.tap.common.analytics.FirebaseAnalyticsHandler
import com.tangem.tap.common.extensions.toFormattedString
@ -36,9 +34,14 @@ class WalletConnectSdkHelper {
type: WcTransactionType,
): WcTransactionData? {
val factory = store.state.globalState.tapWalletManager.walletManagerFactory
val publicKey = Wallet.PublicKey(
session.wallet.walletPublicKey,
session.wallet.derivedPublicKey,
session.wallet.derivationPath,
)
val walletManager = factory.makeEthereumWalletManager(
session.wallet.cardId,
session.wallet.walletPublicKey.hexToBytes(),
publicKey,
emptyList(),
isTestNet = session.wallet.isTestNet
) ?: return null
@ -151,7 +154,7 @@ class WalletConnectSdkHelper {
val command = SignHashCommand(
hash = dataToSign.hash,
walletPublicKey = data.walletManager.wallet.publicKey
walletPublicKey = data.walletManager.wallet.publicKey.seedKey
)
val result = tangemSdkManager.runTaskAsync(command, initialMessage = Message())
return when (result) {
@ -208,13 +211,13 @@ class WalletConnectSdkHelper {
}
suspend fun signPersonalMessage(hashToSign: ByteArray, wallet: WalletForSession): String? {
val publicKey = wallet.walletPublicKey.hexToBytes()
val publicKey = wallet.walletPublicKey
val command = SignHashCommand(hashToSign, publicKey)
return when (val result = tangemSdkManager.runTaskAsync(command, wallet.cardId)) {
is CompletionResult.Success -> {
val hash = result.data.signature
return EthereumUtils.prepareSignedMessageData(
hash, hashToSign, publicKey
hash, hashToSign, CryptoUtils.decompressPublicKey(wallet.walletPublicKey)
)
}
is CompletionResult.Failure -> {

View file

@ -1,9 +1,7 @@
package com.tangem.tap.features.details.redux.walletconnect
import com.tangem.blockchain.common.*
import com.tangem.common.card.Card
import com.tangem.common.extensions.guard
import com.tangem.common.extensions.toHexString
import com.tangem.tap.*
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.extensions.getFromClipboard
@ -14,6 +12,7 @@ import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.extensions.makeWalletManagerForApp
import com.tangem.tap.domain.isMultiwalletAllowed
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.domain.walletconnect.WalletConnectManager
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.wallet.R
@ -128,24 +127,32 @@ class WalletConnectMiddleware {
}
private fun handleScanCard(wcUri: String) {
store.dispatch(GlobalAction.ScanCard({ scanResponse ->
store.dispatch(GlobalAction.ScanCard(true, { scanResponse ->
val card = scanResponse.card
if (!card.isMultiwalletAllowed) {
store.dispatchOnMain(WalletConnectAction.UnsupportedCard)
return@ScanCard
}
val walletManager = getWalletManager(card).guard {
val walletManager = getWalletManager(scanResponse).guard {
store.dispatchOnMain(WalletConnectAction.UnsupportedCard)
return@ScanCard
}
val key = walletManager.wallet.publicKey
val wallet = walletManager.wallet
val derivedKey = if (wallet.publicKey.blockchainKey.contentEquals(wallet.publicKey.seedKey)) {
null
} else {
walletManager.wallet.publicKey.blockchainKey
}
store.dispatchOnMain(WalletConnectAction.OpenSession(
wcUri = wcUri,
wallet = WalletForSession(
card.cardId, key.toHexString(),
isTestNet = card.isTestCard
card.cardId,
wallet.publicKey.seedKey,
derivedKey,
wallet.publicKey.derivationPath,
card.isTestCard
),
))
}, {
@ -153,26 +160,32 @@ class WalletConnectMiddleware {
}, R.string.wallet_connect_scan_card_message))
}
private fun getWalletManager(card: Card): WalletManager? {
private fun getWalletManager(scanResponse: ScanResponse): WalletManager? {
val card = scanResponse.card
val factory = store.state.globalState.tapWalletManager.walletManagerFactory
val wcBlockchain = if (scanResponse.card.isTestCard) {
Blockchain.EthereumTestnet
} else {
Blockchain.Ethereum
}
return if (store.state.globalState.scanResponse?.card?.cardId == card.cardId) {
store.state.walletState.getWalletManager(Blockchain.Ethereum)
?: factory.makeWalletManagerForApp(card, Blockchain.Ethereum)
store.state.walletState.getWalletManager(wcBlockchain)
?: factory.makeWalletManagerForApp(scanResponse, wcBlockchain)
?.also { walletManager ->
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager))
store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain))
}
} else {
if (currenciesRepository.loadCardCurrencies(card.cardId)?.blockchains?.contains(
Blockchain.Ethereum) == true
wcBlockchain) == true
) {
factory.makeWalletManagerForApp(card, Blockchain.Ethereum)
factory.makeWalletManagerForApp(scanResponse, wcBlockchain)
} else {
factory.makeWalletManagerForApp(card,
Blockchain.Ethereum)
factory.makeWalletManagerForApp(scanResponse, wcBlockchain)
?.also {
currenciesRepository.saveAddedBlockchain(card.cardId, Blockchain.Ethereum)
currenciesRepository.saveAddedBlockchain(card.cardId, wcBlockchain)
}
}
}

View file

@ -1,7 +1,9 @@
package com.tangem.tap.features.details.redux.walletconnect
import com.squareup.moshi.JsonClass
import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.WalletManager
import com.tangem.common.hdWallet.DerivationPath
import com.tangem.tap.common.redux.StateDialog
import com.tangem.tap.features.details.ui.walletconnect.dialogs.PersonalSignDialogData
import com.tangem.tap.features.details.ui.walletconnect.dialogs.TransactionRequestDialogData
@ -21,9 +23,12 @@ data class WalletConnectSession(
val peerMeta: WCPeerMeta,
)
@JsonClass(generateAdapter = true)
data class WalletForSession(
val cardId: String,
val walletPublicKey: String,
val walletPublicKey: ByteArray,
val derivedPublicKey: ByteArray?,
val derivationPath: DerivationPath?,
val isTestNet: Boolean = false,
) {
val chainId

View file

@ -28,7 +28,7 @@ class HomeMiddleware {
val handler = homeMiddleware
const val CARD_SHOP_URI =
"https://shop.tangem.com/?afmc=1i&utm_campaign=1i&utm_source=leaddyno&utm_medium=affiliate"
"https://shop.tangem.com/?afmc=1i&utm_campaign=1i&utm_source=leaddyno&utm_medium=affiliate"
}
}
@ -61,7 +61,7 @@ private fun handleReadCard() {
store.dispatch(NavigationAction.NavigateTo(AppScreen.Disclaimer))
} else {
changeButtonState(ButtonState.PROGRESS)
store.dispatch(GlobalAction.ScanCard({ scanResponse ->
store.dispatch(GlobalAction.ScanCard(false, { scanResponse ->
store.state.globalState.tapWalletManager.updateConfigManager(scanResponse)
store.dispatch(TwinCardsAction.IfTwinsPrepareState(scanResponse))

View file

@ -71,7 +71,8 @@ private fun handleWalletAction(action: Action) {
withMainContext {
when (result) {
is CompletionResult.Success -> {
val updatedResponse = scanResponse.copy(card = result.data)
//here we must use updated scanResponse after createWallet & derivation
val updatedResponse = globalState.onboardingManager.scanResponse.copy(card = result.data)
onboardingManager.scanResponse = updatedResponse
onboardingManager.activationStarted(updatedResponse.card.cardId)
store.dispatch(OnboardingWalletAction.ProceedBackup)

View file

@ -39,7 +39,10 @@ class BackupReducer {
): BackupState {
return when (action) {
BackupAction.IntroduceBackup -> BackupState(backupStep = BackupStep.InitBackup)
BackupAction.IntroduceBackup -> BackupState(
backupStep = BackupStep.InitBackup,
canSkipBackup = state.canSkipBackup
)
BackupAction.StartBackup -> state.copy(backupStep = BackupStep.ScanOriginCard)

View file

@ -15,6 +15,7 @@ import com.tangem.tap.common.extensions.*
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.domain.TangemSigner
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.TapWorkarounds.isStart2Coin
@ -152,21 +153,32 @@ private fun sendTransaction(
if (card.isStart2Coin) {
tangemSdk.config.linkedTerminal = false
}
val signer = TangemSigner(
tangemSdk = tangemSdk, initialMessage = action.messageForSigner
) { signResponse ->
val signer = TangemSigner(tangemSdk, action.messageForSigner) { signResponse ->
store.dispatch(
GlobalAction.UpdateWalletSignedHashes(
walletSignedHashes = signResponse.totalSignedHashes,
walletPublicKey = walletManager.wallet.publicKey,
walletPublicKey = walletManager.wallet.publicKey.seedKey,
remainingSignatures = signResponse.remainingSignatures
)
)
}
val result = (walletManager as TransactionSender).send(txData, signer)
val sendResult = try {
(walletManager as TransactionSender).send(txData, signer)
} catch (ex: Exception) {
FirebaseCrashlytics.getInstance().recordException(ex)
delay(DELAY_SDK_DIALOG_CLOSE)
withMainContext {
dispatch(SendAction.ChangeSendButtonState(ButtonState.ENABLED))
store.dispatchErrorNotification(TapError.CustomError(ex.localizedMessage ?: "Unknown error"))
}
return@launch
}
withContext(Dispatchers.Main) {
tangemSdk.config.linkedTerminal = isLinkedTerminal
when (result) {
when (sendResult) {
is SimpleResult.Success -> {
FirebaseAnalyticsHandler.triggerEvent(
event = AnalyticsEvent.TRANSACTION_IS_SENT,
@ -191,20 +203,18 @@ private fun sendTransaction(
}
}
is SimpleResult.Failure -> {
when (result.error) {
when (sendResult.error) {
is CreateAccountUnderfunded -> {
val error = result.error as CreateAccountUnderfunded
val error = sendResult.error as CreateAccountUnderfunded
val reserve = error.minReserve.value?.stripZeroPlainString() ?: "0"
val symbol = error.minReserve.currencySymbol
dispatch(SendAction.SendError(TapError.CreateAccountUnderfunded(listOf(reserve, symbol))))
}
is SendException -> {
result.error?.let {
FirebaseCrashlytics.getInstance().recordException(it)
}
sendResult.error?.let { FirebaseCrashlytics.getInstance().recordException(it) }
}
is Throwable -> {
val throwable = result.error as Throwable
val throwable = sendResult.error as Throwable
val message = throwable.message
val infoHolder = store.state.globalState.feedbackManager?.infoHolder
when {
@ -228,13 +238,13 @@ private fun sendTransaction(
dispatch(SendAction.SendError(TapError.XmlError.AssetAccountNotCreated))
}
else -> {
(result.error as? TangemSdkError)?.let { error ->
(sendResult.error as? TangemSdkError)?.let { error ->
FirebaseAnalyticsHandler.logCardSdkError(
error,
FirebaseAnalyticsHandler.ActionToLog.SendTransaction,
mapOf(
FirebaseAnalyticsHandler.AnalyticsParam.BLOCKCHAIN
to walletManager.wallet.blockchain.currency),
to walletManager.wallet.blockchain.currency),
card = card,
)
}
@ -303,7 +313,7 @@ fun createValidateTransactionError(errorList: EnumSet<TransactionError>, walletM
private fun setIfSendingToPayIdEnabled(appState: AppState?, dispatch: (Action) -> Unit) {
val isSendingToPayIdEnabled =
appState?.globalState?.configManager?.config?.isSendingToPayIdEnabled ?: false
appState?.globalState?.configManager?.config?.isSendingToPayIdEnabled ?: false
dispatch(AddressPayIdActionUi.ChangePayIdState(isSendingToPayIdEnabled))
}

View file

@ -6,8 +6,12 @@ import com.tangem.blockchain.common.Token
import com.tangem.wallet.R
sealed class CurrencyListItem {
var isAdded: Boolean = false
var isLock: Boolean = false
data class TokenListItem(val token: Token) : CurrencyListItem()
data class BlockchainListItem(val blockchain: Blockchain) : CurrencyListItem()
data class TitleListItem(
@StringRes val titleResId: Int,
var isContentShown: Boolean = true,

View file

@ -7,6 +7,8 @@ import org.rekotlin.Action
sealed class TokensAction : Action {
object ResetState : TokensAction()
object LoadCurrencies : TokensAction() {
data class Success(val currencies: List<CurrencyListItem>) : TokensAction()
}
@ -19,4 +21,9 @@ sealed class TokensAction : Action {
data class ToggleShowTokensForBlockchain(val isShown: Boolean, val blockchain: Blockchain) : TokensAction()
sealed class TokensList : TokensAction() {
data class AddBlockchain(val blockchain: Blockchain) : TokensList()
data class AddToken(val token: Token) : TokensList()
object SaveChanges : TokensList()
}
}

View file

@ -1,9 +1,25 @@
package com.tangem.tap.features.tokens.redux
import com.tangem.blockchain.common.Blockchain
import com.tangem.blockchain.common.Token
import com.tangem.common.CompletionResult
import com.tangem.common.card.EllipticCurve
import com.tangem.tap.common.extensions.dispatchErrorNotification
import com.tangem.tap.common.extensions.toMapKey
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.currenciesRepository
import com.tangem.tap.domain.DELAY_SDK_DIALOG_CLOSE
import com.tangem.tap.domain.TapError
import com.tangem.tap.domain.TapWorkarounds.isTestCard
import com.tangem.tap.domain.tasks.product.ScanResponse
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.scope
import com.tangem.tap.store
import com.tangem.tap.tangemSdkManager
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.rekotlin.Middleware
class TokensMiddleware {
@ -12,22 +28,111 @@ class TokensMiddleware {
{ next ->
{ action ->
when (action) {
is TokensAction.LoadCurrencies -> {
val card = state()?.globalState?.scanResponse?.card
val isTestcard = card?.isTestCard ?: false
val tokens = currenciesRepository.getPopularTokens(isTestcard)
val blockchains = currenciesRepository.getBlockchains(
cardFirmware = card?.firmwareVersion,
isTestNet = isTestcard
)
val currencies = CurrencyListItem.createListOfCurrencies(
blockchains, tokens
)
store.dispatch(TokensAction.LoadCurrencies.Success(currencies))
}
is TokensAction.LoadCurrencies -> handleLoadCurrencies(action)
is TokensAction.TokensList.SaveChanges -> handleSaveChanges(action)
}
next(action)
}
}
}
private fun handleLoadCurrencies(action: TokensAction.LoadCurrencies) {
val scanResponse = store.state.globalState.scanResponse ?: return
val isTestcard = scanResponse.card.isTestCard
val tokens = currenciesRepository.getPopularTokens(isTestcard)
val blockchains = currenciesRepository.getBlockchains(
cardFirmware = scanResponse.card.firmwareVersion,
isTestNet = isTestcard
)
val currencies = CurrencyListItem.createListOfCurrencies(blockchains, tokens).toMutableList()
if (scanResponse.isTangemWallet()) {
currencies.forEach {
when (it) {
is CurrencyListItem.TitleListItem -> {
}
is CurrencyListItem.BlockchainListItem -> {
val firstCurve = it.blockchain.getSupportedCurves()?.firstOrNull()
if (firstCurve == EllipticCurve.Ed25519) {
it.isLock = true
}
}
is CurrencyListItem.TokenListItem -> {
val firstCurve = it.token.blockchain.getSupportedCurves()?.firstOrNull()
if (firstCurve == EllipticCurve.Ed25519) {
it.isLock = true
}
}
}
}
}
store.dispatch(TokensAction.LoadCurrencies.Success(currencies))
}
private fun handleSaveChanges(action: TokensAction.TokensList.SaveChanges) {
val scanResponse = store.state.globalState.scanResponse ?: return
val candidatesToAdd = store.state.tokensState.candidateToAdd
if (candidatesToAdd.isEmpty()) return
val blockchains = candidatesToAdd.filterIsInstance<CurrencyListItem.BlockchainListItem>()
.map { it.blockchain }
val tokens = candidatesToAdd.filterIsInstance<CurrencyListItem.TokenListItem>()
.map { it.token }
if (blockchains.isEmpty() && tokens.isEmpty()) return
if (scanResponse.isTangemWallet()) {
deriveMissingBlockchains(scanResponse, blockchains, tokens)
} else {
submitAdd(blockchains, tokens)
store.dispatch(NavigationAction.PopBackTo())
}
}
private fun deriveMissingBlockchains(
scanResponse: ScanResponse,
blockchains: List<Blockchain>,
tokens: List<Token>
) {
val wallet = scanResponse.card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: return
val tokenBlockchains = tokens.map { it.blockchain }
val derivationPathsCandidates = (blockchains + tokenBlockchains).distinct()
.mapNotNull { it.derivationPath() }
val mapKeyOfWalletPublicKey = wallet.publicKey.toMapKey()
val alreadyDerivedKeys = scanResponse.derivedKeys[mapKeyOfWalletPublicKey]?.toMutableList() ?: mutableListOf()
val alreadyDerivedPaths = alreadyDerivedKeys.map { it.derivationPath }
val toDerive = derivationPathsCandidates.filterNot { alreadyDerivedPaths.contains(it) }
scope.launch {
val result = tangemSdkManager.derivePublicKeys(scanResponse.card.cardId, wallet.publicKey, toDerive)
when (result) {
is CompletionResult.Success -> {
val newDerivedKeys = result.data
alreadyDerivedKeys.addAll(newDerivedKeys)
val updatedScanResponse = scanResponse.copy(
derivedKeys = mapOf(mapKeyOfWalletPublicKey to alreadyDerivedKeys.toList())
)
store.dispatch(GlobalAction.SaveScanNoteResponse(updatedScanResponse))
submitAdd(blockchains, tokens)
delay(DELAY_SDK_DIALOG_CLOSE)
store.dispatch(NavigationAction.PopBackTo())
}
is CompletionResult.Failure -> {
store.dispatchErrorNotification(TapError.CustomError("Error adding tokens"))
}
}
}
}
private fun submitAdd(blockchains: List<Blockchain>, tokens: List<Token>) {
(blockchains.map {
WalletAction.MultiWallet.AddBlockchain(it)
} + tokens.map {
WalletAction.MultiWallet.AddToken(it)
}).forEach { store.dispatch(it) }
}
}

View file

@ -17,6 +17,7 @@ private fun internalReduce(action: Action, state: AppState): TokensState {
val tokensState = state.tokensState
return when (action) {
is TokensAction.ResetState -> TokensState()
is TokensAction.LoadCurrencies.Success -> {
tokensState.copy(currencies = action.currencies, shownCurrencies = action.currencies)
}
@ -33,15 +34,25 @@ private fun internalReduce(action: Action, state: AppState): TokensState {
if (action.isShown) {
val shownCurrencies = tokensState.shownCurrencies
.removeTokensForBlockchain(action.blockchain)
shownCurrencies.toggleHeaderContentShownValue(action.blockchain)
shownCurrencies.toggleHeaderContentShownValue(action.blockchain)
tokensState.copy(shownCurrencies = shownCurrencies)
} else {
val shownCurrencies = tokensState.shownCurrencies
.addTokensForBlockchain(action.blockchain, tokensState.currencies)
shownCurrencies.toggleHeaderContentShownValue(action.blockchain)
shownCurrencies.toggleHeaderContentShownValue(action.blockchain)
tokensState.copy(shownCurrencies = shownCurrencies)
}
}
is TokensAction.TokensList.AddBlockchain -> {
val candidates = tokensState.candidateToAdd.toMutableSet()
candidates.add(CurrencyListItem.BlockchainListItem(action.blockchain))
tokensState.copy(candidateToAdd = candidates.toList())
}
is TokensAction.TokensList.AddToken -> {
val candidates = tokensState.candidateToAdd.toMutableSet()
candidates.add(CurrencyListItem.TokenListItem(action.token))
tokensState.copy(candidateToAdd = candidates.toList())
}
else -> tokensState
}
}

View file

@ -10,6 +10,7 @@ data class TokensState(
val addedCurrencies: CardCurrencies? = null,
val currencies: List<CurrencyListItem> = emptyList(),
val shownCurrencies: List<CurrencyListItem> = emptyList(),
val candidateToAdd: List<CurrencyListItem> = emptyList(),
) : StateType
data class TokenWithAmount(

View file

@ -12,6 +12,7 @@ import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.transition.TransitionInflater
import com.tangem.tap.common.redux.navigation.NavigationAction
import com.tangem.tap.features.tokens.redux.TokensAction
import com.tangem.tap.features.tokens.redux.TokensState
import com.tangem.tap.features.tokens.ui.adapters.CurrenciesAdapter
import com.tangem.tap.mainScope
@ -57,30 +58,31 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(activity as? AppCompatActivity)?.setSupportActionBar(toolbar)
toolbar.setNavigationOnClickListener { activity?.onBackPressed() }
btn_tokens_save_changes.setOnClickListener { store.dispatch(TokensAction.TokensList.SaveChanges)}
setupPopularTokensRecyclerView()
}
private fun setupPopularTokensRecyclerView() {
viewAdapter = CurrenciesAdapter()
rv_popular_tokens.layoutManager = LinearLayoutManager(context)
rv_popular_tokens.adapter = viewAdapter
}
override fun newState(state: TokensState) {
if (activity == null) return
btn_tokens_save_changes.isEnabled = state.candidateToAdd.isNotEmpty()
viewAdapter.addedCurrencies = state.addedCurrencies
viewAdapter.submitUnfilteredList(state.shownCurrencies)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.menu_search -> {
true
}
R.id.menu_search -> true
else -> super.onOptionsItemSelected(item)
}
}
@ -101,6 +103,11 @@ class AddTokensFragment : Fragment(R.layout.fragment_add_tokens),
.launchIn(mainScope)
return super.onCreateOptionsMenu(menu, inflater);
}
override fun onDestroy() {
store.dispatch(TokensAction.ResetState)
super.onDestroy()
}
}
fun SearchView.inputtedTextAsFlow(): Flow<String> = callbackFlow {

View file

@ -14,7 +14,6 @@ import com.tangem.tap.common.extensions.show
import com.tangem.tap.domain.tokens.CardCurrencies
import com.tangem.tap.features.tokens.redux.CurrencyListItem
import com.tangem.tap.features.tokens.redux.TokensAction
import com.tangem.tap.features.wallet.redux.WalletAction
import com.tangem.tap.store
import com.tangem.wallet.R
import kotlinx.android.synthetic.main.item_currency_subtitle.view.*
@ -86,15 +85,15 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
is CurrencyListItem.BlockchainListItem -> {
element.blockchain.currency.toLowerCase(Locale.US)
.contains(queryNormalized) ||
element.blockchain.fullName.toLowerCase(Locale.US)
.contains(queryNormalized)
element.blockchain.fullName.toLowerCase(Locale.US)
.contains(queryNormalized)
}
is CurrencyListItem.TokenListItem -> {
element.token.name.toLowerCase(Locale.US)
.contains(queryNormalized) ||
element.token.symbol.toLowerCase(Locale.US)
.contains(queryNormalized)
element.token.symbol.toLowerCase(Locale.US)
.contains(queryNormalized)
}
is CurrencyListItem.TitleListItem -> true
}
@ -105,17 +104,16 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
submitList(list)
}
class CurrenciesViewHolder(val view: View) :
RecyclerView.ViewHolder(view) {
class CurrenciesViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun bind(currency: CurrencyListItem, addedCurrencies: CardCurrencies?) {
when (currency) {
is CurrencyListItem.BlockchainListItem -> {
val blockchain = currency.blockchain
view.tv_currency_name.text = blockchain.fullName
view.tv_currency_symbol.text = blockchain.currency
val isAdded = addedCurrencies?.blockchains?.contains(blockchain) == true
view.btn_add_token.show(!isAdded)
view.btn_token_added.show(isAdded)
currency.isAdded = addedCurrencies?.blockchains?.contains(blockchain) == true
modifyAddTokenButton(currency)
Picasso.get().loadCurrenciesIcon(
imageView = view.iv_currency,
@ -124,9 +122,9 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
)
view.btn_add_token.setOnClickListener {
store.dispatch(WalletAction.MultiWallet.AddBlockchain(blockchain))
view.btn_add_token.hide()
view.btn_token_added.show()
store.dispatch(TokensAction.TokensList.AddBlockchain(blockchain))
currency.isAdded = !currency.isAdded
modifyAddTokenButton(currency)
}
}
is CurrencyListItem.TokenListItem -> {
@ -134,13 +132,11 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
view.tv_currency_name.text = token.name
view.tv_currency_symbol.text = token.symbol
val isAdded = addedCurrencies?.tokens
currency.isAdded = addedCurrencies?.tokens
?.any {
it.symbol == token.symbol && it.contractAddress == token.contractAddress
} == true
view.btn_add_token.show(!isAdded)
view.btn_token_added.show(isAdded)
modifyAddTokenButton(currency)
Picasso.get().loadCurrenciesIcon(
imageView = view.iv_currency,
@ -148,13 +144,23 @@ class CurrenciesAdapter : ListAdapter<CurrencyListItem, RecyclerView.ViewHolder>
token = token, blockchain = token.blockchain
)
view.btn_add_token.setOnClickListener {
store.dispatch(WalletAction.MultiWallet.AddToken(token))
view.btn_add_token.hide()
view.btn_token_added.show()
store.dispatch(TokensAction.TokensList.AddToken(token))
currency.isAdded = !currency.isAdded
modifyAddTokenButton(currency)
}
}
}
}
private fun modifyAddTokenButton(currency: CurrencyListItem) {
if (currency.isLock) {
view.btn_add_token.isEnabled = false
} else {
val text = if (currency.isAdded) R.string.add_token_added else R.string.common_add
view.btn_add_token.setText(text)
view.btn_add_token.isEnabled = !currency.isAdded
}
}
}
class TitleViewHolder(val view: View) :

View file

@ -50,8 +50,7 @@ sealed class WalletAction : Action {
data class AddToken(val token: Token) : MultiWallet()
data class SaveCurrencies(val cardCurrencies: CardCurrencies) : MultiWallet()
object FindTokensInUse : MultiWallet()
data class FindBlockchainsInUse(val card: Card, val factory: WalletManagerFactory) :
MultiWallet()
object FindBlockchainsInUse : MultiWallet()
data class TokenLoaded(val amount: Amount, val token: Token) : MultiWallet()
data class SelectWallet(val walletData: WalletData?) : MultiWallet()

View file

@ -24,10 +24,12 @@ class MultiWalletMiddleware {
fun handle(
action: WalletAction.MultiWallet, walletState: WalletState?, globalState: GlobalState?,
) {
val globalState = globalState ?: return
val tapWalletManager = globalState.tapWalletManager
when (action) {
is WalletAction.MultiWallet.AddWalletManagers -> {
store.state.globalState.feedbackManager?.infoHolder
?.setWalletsInfo(action.walletManagers)
globalState.feedbackManager?.infoHolder?.setWalletsInfo(action.walletManagers)
}
is WalletAction.MultiWallet.SelectWallet -> {
if (action.walletData != null) {
@ -35,7 +37,7 @@ class MultiWalletMiddleware {
}
}
is WalletAction.MultiWallet.AddToken -> {
globalState?.scanResponse?.card?.cardId?.let {
globalState.scanResponse?.card?.cardId?.let {
currenciesRepository.saveAddedToken(it, action.token)
}
addToken(action.token, walletState, globalState)
@ -44,35 +46,33 @@ class MultiWalletMiddleware {
addTokens(action.tokens, walletState, globalState)
}
is WalletAction.MultiWallet.AddBlockchain -> {
globalState?.scanResponse?.card?.let { card ->
currenciesRepository.saveAddedBlockchain(card.cardId, action.blockchain)
globalState.scanResponse?.let {
currenciesRepository.saveAddedBlockchain(it.card.cardId, action.blockchain)
if (walletState?.blockchains?.contains(action.blockchain) != true) {
globalState.tapWalletManager.walletManagerFactory
.makeWalletManagerForApp(card, action.blockchain)?.let {
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(it))
tapWalletManager.walletManagerFactory
.makeWalletManagerForApp(it, action.blockchain)?.let { walletManager ->
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager))
}
}
}
store.dispatch(WalletAction.LoadFiatRate(currency = Currency.Blockchain(action.blockchain)))
store.dispatch(WalletAction.LoadWallet(
moonpayStatus = globalState?.moonpayStatus,
moonpayStatus = globalState.moonpayStatus,
blockchain = action.blockchain
)
)
)
}
is WalletAction.MultiWallet.SaveCurrencies -> {
val cardId = globalState?.scanResponse?.card?.cardId
cardId?.let { currenciesRepository.saveCardCurrencies(it, action.cardCurrencies) }
globalState.scanResponse?.card?.cardId?.let {
currenciesRepository.saveCardCurrencies(it, action.cardCurrencies)
}
}
is WalletAction.MultiWallet.RemoveWallet -> {
val cardId = globalState?.scanResponse?.card?.cardId
val cardId = globalState.scanResponse?.card?.cardId
when (val currency = action.walletData.currency) {
is Currency.Blockchain -> {
cardId?.let {
currenciesRepository.removeBlockchain(
it,
currency.blockchain
)
currenciesRepository.removeBlockchain(it, currency.blockchain)
}
}
is Currency.Token -> {
@ -83,11 +83,13 @@ class MultiWalletMiddleware {
}
}
is WalletAction.MultiWallet.FindBlockchainsInUse -> {
val cardFirmware = globalState?.scanResponse?.card?.firmwareVersion
val scanResponse = globalState.scanResponse ?: return
val cardFirmware = scanResponse.card.firmwareVersion
val blockchains = currenciesRepository.getBlockchains(cardFirmware)
.filterNot { walletState?.blockchains?.contains(it) == true }
val walletManagers =
action.factory.makeWalletManagersForApp(action.card, blockchains)
tapWalletManager.walletManagerFactory.makeWalletManagersForApp(scanResponse, blockchains)
scope.launch {
walletManagers.map { walletManager ->
@ -120,12 +122,13 @@ class MultiWalletMiddleware {
}
}
is WalletAction.MultiWallet.FindTokensInUse -> {
val card = globalState?.scanResponse?.card ?: return
val scanResponse = globalState.scanResponse ?: return
val walletFactory = tapWalletManager.walletManagerFactory
val card = scanResponse.card
val walletManager = walletState?.getWalletManager(Blockchain.Ethereum)
?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp(
card = card,
blockchain = Blockchain.Ethereum
)
?: walletFactory.makeWalletManagerForApp(scanResponse, Blockchain.Ethereum)
val tokenFinder = walletManager as TokenFinder
scope.launch {
val result = tokenFinder.findTokens()
@ -159,19 +162,18 @@ class MultiWalletMiddleware {
}
private fun addToken(token: Token, walletState: WalletState?, globalState: GlobalState?) {
val card = globalState?.scanResponse?.card ?: return
val scanResponse = globalState?.scanResponse ?: return
val walletManager = walletState?.getWalletManager(token)
?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp(
card = card,
blockchain = token.blockchain
scanResponse,
token.blockchain
)?.also { walletManager ->
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager))
store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain))
}
store.dispatch(
WalletAction.LoadFiatRate(currency = Currency.Token(token))
)
store.dispatch(WalletAction.LoadFiatRate(currency = Currency.Token(token)))
scope.launch {
when (val result = walletManager?.addToken(token)) {
@ -189,12 +191,13 @@ class MultiWalletMiddleware {
walletState: WalletState?,
globalState: GlobalState?,
) {
val card = globalState?.scanResponse?.card ?: return
val scanResponse = globalState?.scanResponse ?: return
val tokensWithManagers = tokens.map { token ->
val walletManager = walletState?.getWalletManager(token)
?: globalState.tapWalletManager.walletManagerFactory.makeWalletManagerForApp(
card = card,
blockchain = token.blockchain
scanResponse,
token.blockchain
)?.also { walletManager ->
store.dispatch(WalletAction.MultiWallet.AddWalletManagers(walletManager))
store.dispatch(WalletAction.MultiWallet.AddBlockchain(walletManager.wallet.blockchain))

View file

@ -4,6 +4,7 @@ import com.squareup.moshi.FromJson
import com.squareup.moshi.Moshi
import com.squareup.moshi.ToJson
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.tangem.common.json.TangemSdkAdapter
import com.tangem.wallet.BuildConfig
import okhttp3.Interceptor
import okhttp3.OkHttpClient
@ -41,7 +42,10 @@ fun createMoshiConverterFactory(): Converter.Factory = MoshiConverterFactory.cre
fun createMoshi(): Moshi = Moshi.Builder()
.add(BigDecimalAdapter)
.add(KotlinJsonAdapterFactory()).build()
.add(KotlinJsonAdapterFactory())
.add(TangemSdkAdapter.DerivationPathAdapter())
.add(TangemSdkAdapter.DerivationNodeAdapter())
.build()
private fun createHttpLoggingInterceptor(): HttpLoggingInterceptor {
val logging = HttpLoggingInterceptor()

View file

@ -13,8 +13,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/backgroundLightGray"
android:fitsSystemWindows="true"
>
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
@ -30,14 +29,34 @@
android:id="@+id/cl_details_confirm"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_popular_tokens"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="0dp"
android:clipToPadding="false"
android:paddingBottom="80dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_tokens_save_changes"
style="@style/TapBlackButton"
android:layout_width="204dp"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:enabled="false"
android:gravity="center"
android:text="Save changes"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -168,7 +168,7 @@
app:layout_constraintBottom_toTopOf="@id/layout_buttons_common"
app:flow_verticalBias="0"
app:layout_constraintTop_toTopOf="@+id/guideline2"
android:layout_marginBottom="12dp"/>
android:layout_marginBottom="8dp"/>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_layout_backup_info"
@ -209,7 +209,7 @@
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintGuide_percent="0.60"
app:layout_constraintGuide_percent="0.55"
app:layout_constraintTop_toTopOf="parent" />

View file

@ -74,33 +74,17 @@
app:layout_constraintVertical_bias="0"
tools:text="NODLE" />
<com.google.android.material.chip.Chip
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_add_token"
style="@style/Widget.MaterialComponents.Chip.Action"
android:layout_width="102dp"
android:layout_height="wrap_content"
android:layout_height="43dp"
android:layout_marginEnd="24dp"
android:src="@drawable/ic_inactive"
android:backgroundTint="@color/selector_btn_green"
android:elevation="0dp"
android:text="@string/common_add"
android:textAlignment="center"
android:textColor="@android:color/white"
app:chipBackgroundColor="@color/accent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.chip.Chip
android:id="@+id/btn_token_added"
style="@style/Widget.MaterialComponents.Chip.Action"
android:layout_width="102dp"
android:layout_height="wrap_content"
android:layout_marginEnd="24dp"
android:enabled="false"
android:src="@drawable/ic_inactive"
android:text="@string/add_token_added"
android:textAlignment="center"
android:textColor="@android:color/white"
app:chipBackgroundColor="@color/tapButtonDisabled"
android:textAllCaps="false"
android:textColor="@color/btn_tap_text_color"
app:cornerRadius="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
@ -110,7 +94,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="start"
app:constraint_referenced_ids="btn_add_token, btn_token_added" />
app:constraint_referenced_ids="btn_add_token" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -10,7 +10,7 @@
<color name="tapButtonColor">@color/accent</color>
<color name="tapButtonDisabled">#661ACE80</color>
<color name="tapButtonColorBlack">@color/darkGray6</color>
<color name="tapButtonBlackDisabled">#661C1C1E</color>
<color name="tapButtonBlackDisabled">#6A6A6A</color>
<color name="success">#5AC461</color>
<color name="error">#C4291C</color>