Updated on 2026-08-14

This commit is contained in:
Tangem 2024-04-03 12:05:24 +03:00
parent f9ace58267
commit 5dec166e7c
7 changed files with 74 additions and 39 deletions

View file

@ -7,6 +7,7 @@ import arrow.fx.coroutines.parZip
import com.tangem.blockchain.common.address.Address
import com.tangem.blockchain.common.address.AddressType
import com.tangem.common.card.EllipticCurve
import com.tangem.common.extensions.hexToBytes
import com.tangem.common.extensions.toHexString
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.visa.utils.VisaConfig
@ -58,7 +59,6 @@ internal class DefaultVisaRepository(
override suspend fun getVisaCurrency(userWalletId: UserWalletId, isRefresh: Boolean): VisaCurrency {
val address = makeAddress(userWalletId)
// val address = "0x40d8194b7168723ece51fa34d16825c60ba03dfa" // for testing
fetchVisaCurrencyIfExpired(address, isRefresh)
@ -69,7 +69,7 @@ internal class DefaultVisaRepository(
private suspend fun fetchVisaCurrencyIfExpired(address: String, isRefresh: Boolean) {
cacheRegistry.invokeOnExpire(
key = getBalancesAndLimitsKey(address),
key = getVisaCurrencyKey(address),
skipCache = isRefresh,
block = { fetchVisaCurrency(address) },
)
@ -78,12 +78,12 @@ internal class DefaultVisaRepository(
private suspend fun fetchVisaCurrency(address: String) {
parZip(
dispatchers.io,
{ visaContractInfoProvider.getBalancesAndLimits(address) },
{ visaContractInfoProvider.getContractInfo(address) },
{ getFiatRate() },
{ balancesAndLimits, fiatRate ->
{ contractInfo, fiatRate ->
fetchedCurrencies.update { value ->
value.apply {
put(address, currencyFactory.create(balancesAndLimits, fiatRate))
put(address, currencyFactory.create(contractInfo, fiatRate))
}
}
},
@ -96,8 +96,7 @@ internal class DefaultVisaRepository(
isRefresh: Boolean,
): Flow<PagingData<VisaTxHistoryItem>> {
val userWallet = findVisaUserWallet(userWalletId)
val cardPubKey = getCardPubKey(userWallet).toHexString()
// val cardPubKey = "02C2BBA0DA1E066EA968C1EB129499F6DEBC5FD82D70D61DCAF691CDB69AF5D8B9" // for testing
val cardPubKey = getCardPubKey(userWallet)
val pager = Pager(
config = PagingConfig(
pageSize = pageSize,
@ -124,8 +123,7 @@ internal class DefaultVisaRepository(
override suspend fun getTxDetails(userWalletId: UserWalletId, txId: String): VisaTxDetails {
return withContext(dispatchers.io) {
val userWallet = findVisaUserWallet(userWalletId)
val cardPubKey = getCardPubKey(userWallet).toHexString()
// val cardPubKey = "02C2BBA0DA1E066EA968C1EB129499F6DEBC5FD82D70D61DCAF691CDB69AF5D8B9" // for testing
val cardPubKey = getCardPubKey(userWallet)
val transaction = fetchedHistoryItems.value[cardPubKey]?.firstOrNull {
it.transactionId.toString() == txId
}
@ -139,6 +137,8 @@ internal class DefaultVisaRepository(
}
private suspend fun makeAddress(userWalletId: UserWalletId): String {
if (IS_DEMO_MODE_ENABLED) return DEMO_ADDRESS
val userWallet = findVisaUserWallet(userWalletId)
val walletAddresses = makeWalletAddresses(userWallet)
val walletAddress = walletAddresses.firstOrNull { it.type == AddressType.Default }
@ -161,16 +161,18 @@ internal class DefaultVisaRepository(
private fun makeWalletAddresses(userWallet: UserWallet): Set<Address> {
val walletBlockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain()
return walletBlockchain.makeAddresses(getCardPubKey(userWallet))
return walletBlockchain.makeAddresses(getCardPubKey(userWallet).hexToBytes())
}
private fun getCardPubKey(userWallet: UserWallet): ByteArray {
private fun getCardPubKey(userWallet: UserWallet): String {
if (IS_DEMO_MODE_ENABLED) return DEMO_PUBLIC_KEY
val cardWallet = userWallet.scanResponse.card.wallets.firstOrNull {
it.curve == EllipticCurve.Secp256k1
}
requireNotNull(cardWallet) { "Secp256k1 card wallet not found" }
return cardWallet.publicKey
return cardWallet.publicKey.toHexString()
}
private suspend fun findVisaUserWallet(userWalletId: UserWalletId): UserWallet {
@ -184,7 +186,15 @@ internal class DefaultVisaRepository(
return userWallet
}
private fun getBalancesAndLimitsKey(address: String): String {
return "visa_balances_and_limits_$address"
private fun getVisaCurrencyKey(address: String): String {
return "visa_currency_$address"
}
private companion object {
// Must be `false` in production
const val IS_DEMO_MODE_ENABLED = false
const val DEMO_ADDRESS = "0x40d8194b7168723ece51fa34d16825c60ba03dfa"
const val DEMO_PUBLIC_KEY = "02C2BBA0DA1E066EA968C1EB129499F6DEBC5FD82D70D61DCAF691CDB69AF5D8B9"
}
}

View file

@ -6,9 +6,7 @@ internal object VisaConfig {
const val NETWORK_NAME = "Polygon PoS"
const val TOKEN_SYMBOL = "USDT"
const val TOKEN_ID = "tether"
const val TOKEN_DECIMALS = 8
val fiatCurrency = AppCurrency(
code = "EUR",

View file

@ -1,7 +1,7 @@
package com.tangem.data.visa.utils
import com.tangem.domain.visa.model.VisaCurrency
import com.tangem.lib.visa.model.VisaBalancesAndLimits
import com.tangem.lib.visa.model.VisaContractInfo
import org.joda.time.DateTime
import org.joda.time.DateTimeZone
import org.joda.time.Instant
@ -10,22 +10,22 @@ import java.math.BigInteger
internal class VisaCurrencyFactory {
fun create(balancesAndLimits: VisaBalancesAndLimits, fiatRate: BigDecimal?): VisaCurrency {
fun create(contractInfo: VisaContractInfo, fiatRate: BigDecimal?): VisaCurrency {
val now = Instant.now()
val currentLimit = if (balancesAndLimits.limitsChangeDate > now) {
balancesAndLimits.oldLimits
val currentLimit = if (contractInfo.limitsChangeDate > now) {
contractInfo.oldLimits
} else {
balancesAndLimits.newLimits
contractInfo.newLimits
}
val remainingOtpLimit = getRemainingOtp(currentLimit, now)
return VisaCurrency(
symbol = VisaConfig.TOKEN_SYMBOL,
symbol = contractInfo.token.symbol,
networkName = VisaConfig.NETWORK_NAME,
decimals = VisaConfig.TOKEN_DECIMALS,
decimals = contractInfo.token.decimals,
fiatRate = fiatRate,
fiatCurrency = VisaConfig.fiatCurrency,
balances = with(balancesAndLimits) {
balances = with(contractInfo) {
VisaCurrency.Balances(
total = balances.total,
verified = balances.verified,
@ -44,7 +44,7 @@ internal class VisaCurrencyFactory {
)
}
private fun getRemainingOtp(currentLimit: VisaBalancesAndLimits.Limits, now: Instant): BigDecimal {
private fun getRemainingOtp(currentLimit: VisaContractInfo.Limits, now: Instant): BigDecimal {
if (currentLimit.expirationDate >= now) {
return currentLimit.spendLimit.limit - currentLimit.spendLimit.spent
}
@ -52,7 +52,7 @@ internal class VisaCurrencyFactory {
return currentLimit.spendLimit.limit
}
private fun getRemainingNoOtp(currentLimit: VisaBalancesAndLimits.Limits, now: Instant): BigDecimal {
private fun getRemainingNoOtp(currentLimit: VisaContractInfo.Limits, now: Instant): BigDecimal {
if (currentLimit.expirationDate >= now) {
return currentLimit.noOtpLimit.limit - currentLimit.noOtpLimit.spent
}
@ -60,7 +60,7 @@ internal class VisaCurrencyFactory {
return currentLimit.noOtpLimit.limit
}
private fun getLimitsExpirationDate(currentLimits: VisaBalancesAndLimits.Limits, now: Instant): DateTime {
private fun getLimitsExpirationDate(currentLimits: VisaContractInfo.Limits, now: Instant): DateTime {
val expirationDate = if (currentLimits.expirationDate >= now) {
currentLimits.expirationDate.toDateTime()
} else {

View file

@ -2,7 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.ui.components.visa
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.SecondaryButtonIconStart
import com.tangem.core.ui.components.PrimaryButtonIconStart
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.DepositButtonState
@ -10,7 +10,7 @@ private const val DEPOSIT_BUTTON_CONTENT_TYPE = "DepositButton"
internal fun LazyListScope.depositButton(state: DepositButtonState, modifier: Modifier = Modifier) {
item(key = DEPOSIT_BUTTON_CONTENT_TYPE, contentType = DEPOSIT_BUTTON_CONTENT_TYPE) {
SecondaryButtonIconStart(
PrimaryButtonIconStart(
modifier = modifier,
text = "Deposit",
iconResId = R.drawable.ic_arrow_down_24,

View file

@ -1,9 +1,8 @@
package com.tangem.lib.visa
import arrow.fx.coroutines.parZip
import com.tangem.lib.visa.model.VisaBalancesAndLimits
import com.tangem.lib.visa.model.VisaBalancesAndLimits.Balances
import com.tangem.lib.visa.model.VisaBalancesAndLimits.Limits
import com.tangem.lib.visa.model.VisaContractInfo
import com.tangem.lib.visa.model.VisaContractInfo.*
import com.tangem.lib.visa.utils.toBigDecimal
import com.tangem.lib.visa.utils.toInstant
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -21,7 +20,7 @@ internal class DefaultVisaContractInfoProvider(
private val dispatchers: CoroutineDispatcherProvider,
) : VisaContractInfoProvider {
override suspend fun getBalancesAndLimits(walletAddress: String): VisaBalancesAndLimits {
override suspend fun getContractInfo(walletAddress: String): VisaContractInfo {
return parZip(
dispatchers.io,
{ loadPaymentAccount(walletAddress) },
@ -64,15 +63,35 @@ internal class DefaultVisaContractInfoProvider(
private suspend fun fetchBalancesAndLimits(
paymentAccount: TangemPaymentAccount,
paymentToken: PaymentTokenInfo,
): VisaBalancesAndLimits = parZip(
): VisaContractInfo = parZip(
dispatchers.io,
{ fetchToken(paymentAccount) },
{ fetchBalances(paymentAccount, paymentToken) },
{ fetchLimits(paymentAccount, paymentToken) },
{ balances, (oldLimit, newLimit, changeDate) ->
VisaBalancesAndLimits(balances, oldLimit, newLimit, changeDate)
{ token, balances, (oldLimit, newLimit, changeDate) ->
VisaContractInfo(token, balances, oldLimit, newLimit, changeDate)
},
)
private suspend fun fetchToken(paymentAccount: TangemPaymentAccount): Token {
val paymentTokenContractAddress = paymentAccount.paymentToken().send()
val paymentTokenContract = ERC20.load(paymentTokenContractAddress, web3j, transactionManager, gasProvider)
return parZip(
dispatchers.io,
{ paymentTokenContract.name().send() },
{ paymentTokenContract.symbol().send() },
{ paymentTokenContract.decimals().send() },
) { name, symbol, decimals ->
Token(
name = name,
symbol = symbol,
decimals = decimals.toInt(),
address = paymentTokenContractAddress,
)
}
}
private suspend fun fetchBalances(paymentAccount: TangemPaymentAccount, paymentToken: PaymentTokenInfo): Balances {
return parZip(
dispatchers.io,

View file

@ -2,7 +2,7 @@ package com.tangem.lib.visa
import com.ihsanbal.logging.Level
import com.ihsanbal.logging.LoggingInterceptor
import com.tangem.lib.visa.model.VisaBalancesAndLimits
import com.tangem.lib.visa.model.VisaContractInfo
import com.tangem.lib.visa.utils.VisaConfig
import com.tangem.lib.visa.utils.VisaConfig.NETWORK_LOGS_TAG
import com.tangem.lib.visa.utils.toHexString
@ -21,7 +21,7 @@ import java.util.concurrent.TimeUnit
interface VisaContractInfoProvider {
suspend fun getBalancesAndLimits(walletAddress: String): VisaBalancesAndLimits
suspend fun getContractInfo(walletAddress: String): VisaContractInfo
class Builder(
private val isNetworkLoggingEnabled: Boolean,

View file

@ -4,13 +4,21 @@ import org.joda.time.Instant
import java.math.BigDecimal
import java.math.BigInteger
data class VisaBalancesAndLimits(
data class VisaContractInfo(
val token: Token,
val balances: Balances,
val oldLimits: Limits,
val newLimits: Limits,
val limitsChangeDate: Instant,
) {
data class Token(
val name: String,
val symbol: String,
val decimals: Int,
val address: String,
)
data class Balances(
val total: BigDecimal,
val verified: BigDecimal,