Updated on 2026-08-14
This commit is contained in:
parent
71e4d3a6ed
commit
0594e1b680
23 changed files with 298 additions and 208 deletions
|
|
@ -3,6 +3,9 @@ package com.tangem.tap.domain.sdk.impl
|
|||
import android.content.res.Resources
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.Log
|
||||
import com.tangem.Message
|
||||
import com.tangem.TangemSdk
|
||||
|
|
@ -24,6 +27,7 @@ import com.tangem.domain.card.repository.CardSdkConfigRepository
|
|||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.WithdrawalSignatureResult
|
||||
import com.tangem.domain.visa.model.*
|
||||
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
|
|
@ -516,23 +520,44 @@ internal class DefaultTangemSdkManager(
|
|||
|
||||
override suspend fun tangemPayProduceInitialCredentials(
|
||||
cardId: String,
|
||||
): CompletionResult<TangemPayInitialCredentials> {
|
||||
): Either<Throwable, TangemPayInitialCredentials> {
|
||||
return coroutineScope {
|
||||
runTaskAsyncReturnOnMain(
|
||||
val result = runTaskAsyncReturnOnMain(
|
||||
runnable = tangemPayChallengeTaskFactory.create(coroutineScope = this),
|
||||
cardId = cardId,
|
||||
initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)),
|
||||
)
|
||||
|
||||
return@coroutineScope when (result) {
|
||||
is CompletionResult.Failure<*> -> result.error.left()
|
||||
is CompletionResult.Success<TangemPayInitialCredentials> -> result.data.right()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getWithdrawalSignature(cardId: String, hash: String): CompletionResult<String> {
|
||||
override suspend fun getWithdrawalSignature(
|
||||
cardId: String,
|
||||
hash: String,
|
||||
): Either<Throwable, WithdrawalSignatureResult> {
|
||||
return coroutineScope {
|
||||
runTaskAsyncReturnOnMain(
|
||||
val result = runTaskAsyncReturnOnMain(
|
||||
runnable = TangemPaySignWithdrawalHashTask(cardId = cardId, hash = hash.hexToBytes()),
|
||||
cardId = cardId,
|
||||
initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)),
|
||||
)
|
||||
|
||||
return@coroutineScope when (result) {
|
||||
is CompletionResult.Failure<*> -> {
|
||||
if (result.error is TangemSdkError.UserCancelled) {
|
||||
WithdrawalSignatureResult.Cancelled.right()
|
||||
} else {
|
||||
result.error.left()
|
||||
}
|
||||
}
|
||||
is CompletionResult.Success<String> -> {
|
||||
WithdrawalSignatureResult.Success(result.data).right()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.domain.sdk.impl
|
|||
import android.content.res.Resources
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import arrow.core.Either
|
||||
import com.tangem.Message
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.KeyPair
|
||||
|
|
@ -18,7 +19,11 @@ import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
|||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.visa.model.*
|
||||
import com.tangem.domain.pay.WithdrawalSignatureResult
|
||||
import com.tangem.domain.visa.model.TangemPayInitialCredentials
|
||||
import com.tangem.domain.visa.model.VisaActivationInput
|
||||
import com.tangem.domain.visa.model.VisaDataForApprove
|
||||
import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet
|
||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||
import com.tangem.operations.preflightread.PreflightReadFilter
|
||||
import com.tangem.operations.wallet.CreateWalletResponse
|
||||
|
|
@ -215,11 +220,14 @@ class MockTangemSdkManager(
|
|||
|
||||
override suspend fun tangemPayProduceInitialCredentials(
|
||||
cardId: String,
|
||||
): CompletionResult<TangemPayInitialCredentials> {
|
||||
): Either<Throwable, TangemPayInitialCredentials> {
|
||||
error("Not implemented")
|
||||
}
|
||||
|
||||
override suspend fun getWithdrawalSignature(cardId: String, hash: String): CompletionResult<String> {
|
||||
override suspend fun getWithdrawalSignature(
|
||||
cardId: String,
|
||||
hash: String,
|
||||
): Either<Throwable, WithdrawalSignatureResult> {
|
||||
error("Not implemented")
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.tangem.tap.domain.tasks.visa
|
|||
import arrow.core.getOrElse
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.CardWallet
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.CompletionCallback
|
||||
|
|
@ -42,12 +41,14 @@ class TangemPayGenerateAddressAndSignChallengeTask @AssistedInject constructor(
|
|||
|
||||
private suspend fun runSuspend(session: CardSession): CompletionResult<TangemPayInitialCredentials> {
|
||||
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
|
||||
val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.curve }
|
||||
?: return CompletionResult.Failure(VisaActivationError.MissingWallet.tangemError)
|
||||
|
||||
val address = when (val derivationResult = runDerivationTask(session, wallet)) {
|
||||
is CompletionResult.Failure<*> -> return CompletionResult.Failure(derivationResult.error)
|
||||
is CompletionResult.Success<ExtendedPublicKey> -> generateAddressFromExtendedKey(derivationResult.data)
|
||||
is CompletionResult.Success<ExtendedPublicKey> -> VisaUtilities.generateAddressFromExtendedKey(
|
||||
extendedPublicKey = derivationResult.data,
|
||||
)
|
||||
}
|
||||
|
||||
val userWalletId = UserWalletIdBuilder.walletPublicKey(wallet.publicKey)
|
||||
|
|
@ -119,14 +120,6 @@ class TangemPayGenerateAddressAndSignChallengeTask @AssistedInject constructor(
|
|||
return deferred.await()
|
||||
}
|
||||
|
||||
private fun generateAddressFromExtendedKey(extendedPublicKey: ExtendedPublicKey): String {
|
||||
val derivationData = VisaUtilities.visaBlockchain.makeAddressesFromExtendedPublicKey(
|
||||
extendedPublicKey = extendedPublicKey,
|
||||
cachedIndex = null,
|
||||
)
|
||||
return derivationData.address
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(coroutineScope: CoroutineScope): TangemPayGenerateAddressAndSignChallengeTask
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
package com.tangem.tap.domain.tasks.visa
|
||||
|
||||
import com.tangem.blockchain.common.UnmarshalHelper
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
import com.tangem.common.card.EllipticCurve
|
||||
import com.tangem.common.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.CompletionCallback
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.toDecompressedPublicKey
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.core.error.ext.tangemError
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
|
|
@ -40,7 +36,7 @@ class TangemPaySignWithdrawalHashTask(
|
|||
private fun proceedSign(card: Card, session: CardSession, callback: CompletionCallback<String>) {
|
||||
val derivationPath = VisaUtilities.customDerivationPath
|
||||
|
||||
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run {
|
||||
val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.curve } ?: run {
|
||||
callback(CompletionResult.Failure(VisaActivationError.MissingWallet.tangemError))
|
||||
return
|
||||
}
|
||||
|
|
@ -71,7 +67,7 @@ class TangemPaySignWithdrawalHashTask(
|
|||
private fun signData(
|
||||
targetWalletPublicKey: ByteArray,
|
||||
derivationPath: DerivationPath?,
|
||||
extendedPublicKey: ExtendedPublicKey?,
|
||||
extendedPublicKey: ExtendedPublicKey,
|
||||
session: CardSession,
|
||||
callback: CompletionCallback<String>,
|
||||
) {
|
||||
|
|
@ -84,12 +80,11 @@ class TangemPaySignWithdrawalHashTask(
|
|||
signTask.run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val rsvSignature = UnmarshalHelper.unmarshalSignatureExtended(
|
||||
val rsvSignature = VisaUtilities.unmarshallSignature(
|
||||
signature = result.data.signature,
|
||||
hash = hash,
|
||||
publicKey = extendedPublicKey?.publicKey?.toDecompressedPublicKey()
|
||||
?: targetWalletPublicKey.toDecompressedPublicKey(),
|
||||
).asRSVLegacyEVM().toHexString().lowercase()
|
||||
extendedPublicKey = extendedPublicKey,
|
||||
)
|
||||
|
||||
callback(CompletionResult.Success(rsvSignature))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,28 +1,19 @@
|
|||
package com.tangem.tap.domain.tasks.visa
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.blockchain.blockchains.ethereum.EthereumUtils.toKeccak
|
||||
import com.tangem.blockchain.common.UnmarshalHelper
|
||||
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.core.CardSession
|
||||
import com.tangem.common.core.CardSessionRunnable
|
||||
import com.tangem.common.core.CompletionCallback
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.common.extensions.toDecompressedPublicKey
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.core.error.ext.tangemError
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.domain.card.common.visa.VisaUtilities
|
||||
import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility
|
||||
import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility.findKeyWithoutDerivation
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.visa.error.VisaActivationError
|
||||
import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet
|
||||
import com.tangem.operations.ScanTask
|
||||
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
|
||||
import com.tangem.operations.sign.SignHashCommand
|
||||
|
||||
|
|
@ -46,11 +37,7 @@ class VisaCustomerWalletApproveTask(
|
|||
return
|
||||
}
|
||||
|
||||
if (card.settings.isHDWalletAllowed) {
|
||||
proceedApprove(card, session, callback)
|
||||
} else {
|
||||
proceedApproveWithLegacyCard(card, session, callback)
|
||||
}
|
||||
proceedApprove(card, session, callback)
|
||||
}
|
||||
|
||||
private fun proceedApprove(
|
||||
|
|
@ -60,7 +47,7 @@ class VisaCustomerWalletApproveTask(
|
|||
) {
|
||||
val derivationPath = VisaUtilities.customDerivationPath
|
||||
|
||||
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run {
|
||||
val wallet = card.wallets.firstOrNull { it.curve == VisaUtilities.curve } ?: run {
|
||||
callback(CompletionResult.Failure(VisaActivationError.MissingWallet.tangemError))
|
||||
return
|
||||
}
|
||||
|
|
@ -114,43 +101,15 @@ class VisaCustomerWalletApproveTask(
|
|||
)
|
||||
}
|
||||
|
||||
private fun proceedApproveWithLegacyCard(
|
||||
card: Card,
|
||||
session: CardSession,
|
||||
callback: CompletionCallback<VisaSignedDataByCustomerWallet>,
|
||||
) {
|
||||
val publicKey = findKeyWithoutDerivation(
|
||||
targetAddress = visaDataForApprove.targetAddress,
|
||||
card = CardDTO(card),
|
||||
).getOrElse { error ->
|
||||
callback(CompletionResult.Failure(error.tangemError))
|
||||
return
|
||||
}
|
||||
|
||||
signApproveData(
|
||||
targetWalletPublicKey = publicKey,
|
||||
derivationPath = null,
|
||||
extendedPublicKey = null,
|
||||
session = session,
|
||||
callback = callback,
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: [REDACTED_TASK_KEY] - Get this public function from Blockchain SDK
|
||||
private fun hashPersonalMessage(message: ByteArray): ByteArray {
|
||||
val prefix = "\u0019Ethereum Signed Message:\n${message.size}".toByteArray()
|
||||
return (prefix + message).toKeccak()
|
||||
}
|
||||
|
||||
private fun signApproveData(
|
||||
targetWalletPublicKey: ByteArray,
|
||||
derivationPath: DerivationPath?,
|
||||
extendedPublicKey: ExtendedPublicKey?,
|
||||
extendedPublicKey: ExtendedPublicKey,
|
||||
session: CardSession,
|
||||
callback: CompletionCallback<VisaSignedDataByCustomerWallet>,
|
||||
) {
|
||||
val content = "Tangem Pay wants to sign in with your account. Nonce: ${visaDataForApprove.hashToSign}"
|
||||
val hash = hashPersonalMessage(content.toByteArray(Charsets.UTF_8))
|
||||
val content = VisaUtilities.signWithNonceMessage(visaDataForApprove.hashToSign)
|
||||
val hash = VisaUtilities.hashPersonalMessage(content.toByteArray(Charsets.UTF_8))
|
||||
|
||||
val signTask = SignHashCommand(
|
||||
hash = hash,
|
||||
|
|
@ -161,36 +120,13 @@ class VisaCustomerWalletApproveTask(
|
|||
signTask.run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val rsvSignature = UnmarshalHelper.unmarshalSignatureExtended(
|
||||
val rsvSignature = VisaUtilities.unmarshallSignature(
|
||||
signature = result.data.signature,
|
||||
hash = hash,
|
||||
publicKey = extendedPublicKey?.publicKey?.toDecompressedPublicKey()
|
||||
?: targetWalletPublicKey.toDecompressedPublicKey(),
|
||||
).asRSVLegacyEVM().toHexString().lowercase()
|
||||
|
||||
scanCard(
|
||||
session = session,
|
||||
callback = callback,
|
||||
signedData = visaDataForApprove.sign(rsvSignature, visaDataForApprove.targetAddress),
|
||||
extendedPublicKey = extendedPublicKey,
|
||||
)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun scanCard(
|
||||
signedData: VisaSignedDataByCustomerWallet,
|
||||
session: CardSession,
|
||||
callback: CompletionCallback<VisaSignedDataByCustomerWallet>,
|
||||
) {
|
||||
val scanTask = ScanTask()
|
||||
scanTask.run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
callback(CompletionResult.Success(signedData))
|
||||
visaDataForApprove.sign(rsvSignature, visaDataForApprove.targetAddress)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
callback(CompletionResult.Failure(result.error))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue