Updated on 2026-08-14

This commit is contained in:
Tangem 2024-03-26 12:02:58 +03:00
parent 328271bc26
commit 8b6944e3e4
31 changed files with 1464 additions and 818 deletions

View file

@ -29,4 +29,6 @@ fun String.toQrCode(sizePx: Int = 256, paddingPx: Int = 0): Bitmap {
} }
} }
return bmp return bmp
} }
fun String.capitalize(): String = replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }

View file

@ -59,11 +59,7 @@ object DateTimeFormatters {
DateTimeFormat.forPattern("dd.MM.yyyy HH:mm") DateTimeFormat.forPattern("dd.MM.yyyy HH:mm")
} }
fun formatTime(formatter: DateTimeFormatter = timeFormatter, time: DateTime): String { fun formatDate(date: DateTime, formatter: DateTimeFormatter = dateFormatter): String {
return formatter.print(time)
}
fun formatDate(formatter: DateTimeFormatter = dateFormatter, date: DateTime): String {
return formatter.print(date) return formatter.print(date)
} }
} }

View file

@ -29,5 +29,5 @@ fun Long.toDateFormat(formatter: DateTimeFormatter = DateTimeFormatters.dateForm
* Returns formatted time according to [formatter]. * Returns formatted time according to [formatter].
*/ */
fun Long.toTimeFormat(formatter: DateTimeFormatter = DateTimeFormatters.timeFormatter): String { fun Long.toTimeFormat(formatter: DateTimeFormatter = DateTimeFormatters.timeFormatter): String {
return DateTimeFormatters.formatTime(formatter = formatter, time = DateTime(this, DateTimeZone.getDefault())) return DateTimeFormatters.formatDate(date = DateTime(this, DateTimeZone.getDefault()), formatter = formatter)
} }

View file

@ -26,7 +26,7 @@ dependencies {
implementation(projects.domain.legacy) implementation(projects.domain.legacy)
/** Project - Libs */ /** Project - Libs */
implementation(projects.libs.visa) debugImplementation(projects.libs.visa)
/** Libs - Other */ /** Libs - Other */
implementation(deps.kotlin.coroutines) implementation(deps.kotlin.coroutines)

View file

@ -58,7 +58,7 @@ internal class DefaultVisaRepository(
override suspend fun getVisaCurrency(userWalletId: UserWalletId, isRefresh: Boolean): VisaCurrency { override suspend fun getVisaCurrency(userWalletId: UserWalletId, isRefresh: Boolean): VisaCurrency {
val address = makeAddress(userWalletId) val address = makeAddress(userWalletId)
// val address = "0x143fe062a538176aa0bf162f13d390208f90898f" // for testing // val address = "0x40d8194b7168723ece51fa34d16825c60ba03dfa" // for testing
fetchVisaCurrencyIfExpired(address, isRefresh) fetchVisaCurrencyIfExpired(address, isRefresh)
@ -97,7 +97,7 @@ internal class DefaultVisaRepository(
): Flow<PagingData<VisaTxHistoryItem>> { ): Flow<PagingData<VisaTxHistoryItem>> {
val userWallet = findVisaUserWallet(userWalletId) val userWallet = findVisaUserWallet(userWalletId)
val cardPubKey = getCardPubKey(userWallet).toHexString() val cardPubKey = getCardPubKey(userWallet).toHexString()
// val cardPubKey = "03DEF02B1FECC8BD3CFD52CE93235194479E1DE931EF0F55DC194967E7CCC3D12C" // for testing // val cardPubKey = "02C2BBA0DA1E066EA968C1EB129499F6DEBC5FD82D70D61DCAF691CDB69AF5D8B9" // for testing
val pager = Pager( val pager = Pager(
config = PagingConfig( config = PagingConfig(
pageSize = pageSize, pageSize = pageSize,
@ -125,7 +125,7 @@ internal class DefaultVisaRepository(
return withContext(dispatchers.io) { return withContext(dispatchers.io) {
val userWallet = findVisaUserWallet(userWalletId) val userWallet = findVisaUserWallet(userWalletId)
val cardPubKey = getCardPubKey(userWallet).toHexString() val cardPubKey = getCardPubKey(userWallet).toHexString()
// val cardPubKey = "03DEF02B1FECC8BD3CFD52CE93235194479E1DE931EF0F55DC194967E7CCC3D12C" // for testing // val cardPubKey = "02C2BBA0DA1E066EA968C1EB129499F6DEBC5FD82D70D61DCAF691CDB69AF5D8B9" // for testing
val transaction = fetchedHistoryItems.value[cardPubKey]?.firstOrNull { val transaction = fetchedHistoryItems.value[cardPubKey]?.firstOrNull {
it.transactionId.toString() == txId it.transactionId.toString() == txId
} }

View file

@ -0,0 +1,53 @@
package com.tangem.data.visa.di
import com.squareup.moshi.Moshi
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.visa.BuildConfig
import com.tangem.data.visa.DefaultVisaRepository
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.visa.repository.VisaRepository
import com.tangem.lib.visa.VisaContractInfoProvider
import com.tangem.lib.visa.api.VisaApiBuilder
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object ImplementedVisaDataModule {
@Provides
@Singleton
@ImplementedVisaRepository
fun provideVisaRepository(
@NetworkMoshi moshi: Moshi,
tangemTechApi: TangemTechApi,
cacheRegistry: CacheRegistry,
userWalletsStore: UserWalletsStore,
dispatchers: CoroutineDispatcherProvider,
): VisaRepository {
val contractInfoProvider = VisaContractInfoProvider.Builder(
isNetworkLoggingEnabled = BuildConfig.LOG_ENABLED,
dispatchers = dispatchers,
).build()
val visaApi = VisaApiBuilder(
useDevApi = true,
isNetworkLoggingEnabled = BuildConfig.LOG_ENABLED,
moshi = moshi,
).build()
return DefaultVisaRepository(
contractInfoProvider,
tangemTechApi,
visaApi,
cacheRegistry,
userWalletsStore,
dispatchers,
)
}
}

View file

@ -17,6 +17,7 @@ internal class VisaCurrencyFactory {
} else { } else {
balancesAndLimits.newLimits balancesAndLimits.newLimits
} }
val remainingOtpLimit = getRemainingOtp(currentLimit, now)
return VisaCurrency( return VisaCurrency(
symbol = VisaConfig.TOKEN_SYMBOL, symbol = VisaConfig.TOKEN_SYMBOL,
@ -35,8 +36,8 @@ internal class VisaCurrencyFactory {
) )
}, },
limits = VisaCurrency.Limits( limits = VisaCurrency.Limits(
remainingOtp = getRemainingOtp(currentLimit, now), remainingOtp = remainingOtpLimit,
remainingNoOtp = getRemainingNoOtp(currentLimit, now), remainingNoOtp = minOf(remainingOtpLimit, getRemainingNoOtp(currentLimit, now)),
singleTransaction = currentLimit.singleTransactionLimit, singleTransaction = currentLimit.singleTransactionLimit,
expirationDate = getLimitsExpirationDate(currentLimit, now), expirationDate = getLimitsExpirationDate(currentLimit, now),
), ),

View file

@ -0,0 +1,28 @@
package com.tangem.data.visa
import androidx.paging.PagingData
import com.tangem.domain.visa.model.VisaCurrency
import com.tangem.domain.visa.model.VisaTxDetails
import com.tangem.domain.visa.model.VisaTxHistoryItem
import com.tangem.domain.visa.repository.VisaRepository
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow
internal class DummyVisaRepository : VisaRepository {
override suspend fun getVisaCurrency(userWalletId: UserWalletId, isRefresh: Boolean): VisaCurrency {
TODO("Not implemented for this build type")
}
override suspend fun getTxHistory(
userWalletId: UserWalletId,
pageSize: Int,
isRefresh: Boolean,
): Flow<PagingData<VisaTxHistoryItem>> {
TODO("Not implemented for this build type")
}
override suspend fun getTxDetails(userWalletId: UserWalletId, txId: String): VisaTxDetails {
TODO("Not implemented for this build type")
}
}

View file

@ -0,0 +1,6 @@
package com.tangem.data.visa.di
import javax.inject.Qualifier
@Qualifier
internal annotation class ImplementedVisaRepository

View file

@ -0,0 +1,16 @@
package com.tangem.data.visa.di
import com.tangem.domain.visa.repository.VisaRepository
import dagger.BindsOptionalOf
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
internal interface ImplementedVisaRepositoryModule {
@BindsOptionalOf
@ImplementedVisaRepository
fun bindImplementedVisaRepository(): VisaRepository
}

View file

@ -1,21 +1,14 @@
package com.tangem.data.visa.di package com.tangem.data.visa.di
import com.squareup.moshi.Moshi import com.tangem.data.visa.DummyVisaRepository
import com.tangem.data.common.cache.CacheRegistry
import com.tangem.data.visa.BuildConfig
import com.tangem.data.visa.DefaultVisaRepository
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.di.NetworkMoshi
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.visa.repository.VisaRepository import com.tangem.domain.visa.repository.VisaRepository
import com.tangem.lib.visa.VisaContractInfoProvider
import com.tangem.lib.visa.api.VisaApiBuilder
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import java.util.Optional
import javax.inject.Singleton import javax.inject.Singleton
import kotlin.jvm.optionals.getOrNull
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
@ -24,29 +17,8 @@ internal object VisaDataModule {
@Provides @Provides
@Singleton @Singleton
fun provideVisaRepository( fun provideVisaRepository(
@NetworkMoshi moshi: Moshi, @ImplementedVisaRepository implementedVisaRepository: Optional<VisaRepository>,
tangemTechApi: TangemTechApi,
cacheRegistry: CacheRegistry,
userWalletsStore: UserWalletsStore,
dispatchers: CoroutineDispatcherProvider,
): VisaRepository { ): VisaRepository {
val contractInfoProvider = VisaContractInfoProvider.Builder( return implementedVisaRepository.getOrNull() ?: DummyVisaRepository()
isNetworkLoggingEnabled = BuildConfig.LOG_ENABLED,
dispatchers = dispatchers,
).build()
val visaApi = VisaApiBuilder(
useDevApi = true,
isNetworkLoggingEnabled = BuildConfig.LOG_ENABLED,
moshi = moshi,
).build()
return DefaultVisaRepository(
contractInfoProvider,
tangemTechApi,
visaApi,
cacheRegistry,
userWalletsStore,
dispatchers,
)
} }
} }

View file

@ -19,7 +19,7 @@ internal data class BalancesAndLimitsBottomSheetConfig(
data class Limit( data class Limit(
val availableBy: String, val availableBy: String,
val inStore: String, val total: String,
val other: String, val other: String,
val singleTransaction: String, val singleTransaction: String,
val onInfoClick: () -> Unit, val onInfoClick: () -> Unit,

View file

@ -48,19 +48,32 @@ internal sealed interface WalletAlertState {
override val isWarningConfirmButton: Boolean = true override val isWarningConfirmButton: Boolean = true
} }
object WrongCardIsScanned : Basic() { data class VisaLimitsInfo(
val totalLimit: String,
val otherLimit: String,
) : Basic() {
override val title: TextReference? = null
override val message: TextReference = stringReference(
value = "Limits are needed to control costs, improve security, manage risk. " +
"You can spend $totalLimit during the week for card payments in shops and " +
"$otherLimit for other transactions, e. g. subscriptions or debts.",
)
override val onConfirmClick: (() -> Unit)? = null
}
data object WrongCardIsScanned : Basic() {
override val title: TextReference = resourceReference(R.string.common_warning) override val title: TextReference = resourceReference(R.string.common_warning)
override val message: TextReference = resourceReference(R.string.error_wrong_wallet_tapped) override val message: TextReference = resourceReference(R.string.error_wrong_wallet_tapped)
override val onConfirmClick: (() -> Unit)? = null override val onConfirmClick: (() -> Unit)? = null
} }
object RescanWallets : Basic() { data object RescanWallets : Basic() {
override val title: TextReference = resourceReference(R.string.common_attention) override val title: TextReference = resourceReference(R.string.common_attention)
override val message: TextReference = resourceReference(R.string.key_invalidated_warning_description) override val message: TextReference = resourceReference(R.string.key_invalidated_warning_description)
override val onConfirmClick: (() -> Unit)? = null override val onConfirmClick: (() -> Unit)? = null
} }
object VisaBalancesInfo : Basic() { data object VisaBalancesInfo : Basic() {
override val title: TextReference? = null override val title: TextReference? = null
override val message: TextReference = stringReference( override val message: TextReference = stringReference(
value = "Available balance is actual funds available, considering pending transactions, " + value = "Available balance is actual funds available, considering pending transactions, " +
@ -68,14 +81,4 @@ internal sealed interface WalletAlertState {
) )
override val onConfirmClick: (() -> Unit)? = null override val onConfirmClick: (() -> Unit)? = null
} }
object VisaLimitsInfo : Basic() {
override val title: TextReference? = null
override val message: TextReference = stringReference(
value = "Limits are needed to control costs, improve security, manage risk. " +
"You can spend 1 000 USDT during the week for card payments in shops and " +
"100 USDT for other transactions, e. g. subscriptions or debts.",
)
override val onConfirmClick: (() -> Unit)? = null
}
} }

View file

@ -21,6 +21,9 @@ internal class BalancesAndLimitsBottomSheetConverter(
decimals = value.decimals, decimals = value.decimals,
) )
val otpLimit = value.limits.remainingOtp.let(::formatAmount)
val noOtpLimit = value.limits.remainingNoOtp.let(::formatAmount)
return BalancesAndLimitsBottomSheetConfig( return BalancesAndLimitsBottomSheetConfig(
balance = BalancesAndLimitsBottomSheetConfig.Balance( balance = BalancesAndLimitsBottomSheetConfig.Balance(
totalBalance = value.balances.total.let(::formatAmount), totalBalance = value.balances.total.let(::formatAmount),
@ -33,10 +36,10 @@ internal class BalancesAndLimitsBottomSheetConverter(
), ),
limit = BalancesAndLimitsBottomSheetConfig.Limit( limit = BalancesAndLimitsBottomSheetConfig.Limit(
availableBy = DateTimeFormatters.formatDate(date = value.limits.expirationDate), availableBy = DateTimeFormatters.formatDate(date = value.limits.expirationDate),
inStore = value.limits.remainingOtp.let(::formatAmount), total = otpLimit,
other = value.limits.remainingNoOtp.let(::formatAmount), other = noOtpLimit,
singleTransaction = value.limits.singleTransaction.let(::formatAmount), singleTransaction = value.limits.singleTransaction.let(::formatAmount),
onInfoClick = this::showLimitInfo, onInfoClick = { showLimitInfo(otpLimit, noOtpLimit) },
), ),
) )
} }
@ -45,7 +48,7 @@ internal class BalancesAndLimitsBottomSheetConverter(
eventSender.send(WalletEvent.ShowAlert(WalletAlertState.VisaBalancesInfo)) eventSender.send(WalletEvent.ShowAlert(WalletAlertState.VisaBalancesInfo))
} }
private fun showLimitInfo() { private fun showLimitInfo(totalLimit: String, otherLimit: String) {
eventSender.send(WalletEvent.ShowAlert(WalletAlertState.VisaLimitsInfo)) eventSender.send(WalletEvent.ShowAlert(WalletAlertState.VisaLimitsInfo(totalLimit, otherLimit)))
} }
} }

View file

@ -1,5 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
import com.tangem.core.ui.extensions.capitalize
import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.core.ui.utils.DateTimeFormatters
import com.tangem.domain.visa.model.VisaCurrency import com.tangem.domain.visa.model.VisaCurrency
@ -27,14 +28,14 @@ internal class VisaTxDetailsBottomSheetConverter(
private fun createTransaction(details: VisaTxDetails): VisaTxDetailsBottomSheetConfig.Transaction { private fun createTransaction(details: VisaTxDetails): VisaTxDetailsBottomSheetConfig.Transaction {
return VisaTxDetailsBottomSheetConfig.Transaction( return VisaTxDetailsBottomSheetConfig.Transaction(
id = details.id, id = details.id,
type = details.type, type = details.type.capitalize(),
status = details.status, status = details.status.capitalize(),
blockchainAmount = formatNetworkAmount(details.blockchainAmount), blockchainAmount = formatNetworkAmount(details.blockchainAmount),
blockchainFee = formatNetworkAmount(details.blockchainFee), blockchainFee = formatNetworkAmount(details.blockchainFee),
transactionAmount = formatFiatAmount(details.transactionAmount, details.fiatCurrency), transactionAmount = formatFiatAmount(details.transactionAmount, details.fiatCurrency),
transactionCurrencyCode = details.transactionCurrencyCode.toString(), transactionCurrencyCode = details.transactionCurrencyCode.toString(),
merchantName = details.merchantName ?: UNKNOWN, merchantName = details.merchantName?.capitalize() ?: UNKNOWN,
merchantCity = details.merchantCity ?: UNKNOWN, merchantCity = details.merchantCity?.capitalize() ?: UNKNOWN,
merchantCountryCode = details.merchantCountryCode ?: UNKNOWN, merchantCountryCode = details.merchantCountryCode ?: UNKNOWN,
merchantCategoryCode = details.merchantCategoryCode ?: UNKNOWN, merchantCategoryCode = details.merchantCategoryCode ?: UNKNOWN,
) )
@ -46,16 +47,16 @@ internal class VisaTxDetailsBottomSheetConverter(
return VisaTxDetailsBottomSheetConfig.Request( return VisaTxDetailsBottomSheetConfig.Request(
id = request.id, id = request.id,
type = request.requestType, type = request.requestType.capitalize(),
status = request.requestStatus, status = request.requestStatus.capitalize(),
blockchainAmount = formatNetworkAmount(request.blockchainAmount), blockchainAmount = formatNetworkAmount(request.blockchainAmount),
blockchainFee = formatNetworkAmount(request.blockchainFee), blockchainFee = formatNetworkAmount(request.blockchainFee),
transactionAmount = formatFiatAmount(request.transactionAmount, request.fiatCurrency), transactionAmount = formatFiatAmount(request.transactionAmount, request.fiatCurrency),
currencyCode = request.billingCurrencyCode.toString(), currencyCode = request.billingCurrencyCode.toString(),
errorCode = request.errorCode, errorCode = request.errorCode,
date = DateTimeFormatters.formatDate(DateTimeFormatters.dateTimeFormatter, date = localDate), date = DateTimeFormatters.formatDate(localDate, DateTimeFormatters.dateTimeFormatter),
txHash = request.txHash ?: UNKNOWN, txHash = request.txHash ?: UNKNOWN,
txStatus = request.txStatus ?: UNKNOWN, txStatus = request.txStatus?.capitalize() ?: UNKNOWN,
onExploreClick = if (exploreUrl != null) { onExploreClick = if (exploreUrl != null) {
{ clickIntents.onExploreClick(exploreUrl) } { clickIntents.onExploreClick(exploreUrl) }
} else { } else {

View file

@ -1,6 +1,7 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter
import com.tangem.core.ui.components.transactions.state.TransactionState import com.tangem.core.ui.components.transactions.state.TransactionState
import com.tangem.core.ui.extensions.capitalize
import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.core.ui.utils.DateTimeFormatters
@ -18,8 +19,8 @@ internal class VisaTxHistoryItemStateConverter(
override fun convert(value: VisaTxHistoryItem): TransactionState { override fun convert(value: VisaTxHistoryItem): TransactionState {
val localDate = value.date.withZone(DateTimeZone.getDefault()) val localDate = value.date.withZone(DateTimeZone.getDefault())
val time = DateTimeFormatters.formatTime(time = localDate) val time = DateTimeFormatters.formatDate(localDate, DateTimeFormatters.timeFormatter)
val subtitle = "$time${value.status}" val subtitle = "$time${value.status.capitalize()}"
return TransactionState.Content( return TransactionState.Content(
txHash = value.id, txHash = value.id,
@ -37,7 +38,7 @@ internal class VisaTxHistoryItemStateConverter(
status = TransactionState.Content.Status.Confirmed, status = TransactionState.Content.Status.Confirmed,
direction = TransactionState.Content.Direction.INCOMING, direction = TransactionState.Content.Direction.INCOMING,
iconRes = R.drawable.ic_arrow_up_24, iconRes = R.drawable.ic_arrow_up_24,
title = stringReference(value = value.merchantName ?: "Unknown merchant"), title = stringReference(value = value.merchantName?.capitalize() ?: "Unknown merchant"),
subtitle = stringReference(subtitle), subtitle = stringReference(subtitle),
timestamp = localDate.millis, timestamp = localDate.millis,
onClick = { clickIntents.onVisaTransactionClick(value.id) }, onClick = { clickIntents.onVisaTransactionClick(value.id) },

View file

@ -42,6 +42,7 @@ internal class VisaWalletSubscriber(
setLoadedCurrencyState(maybeCurrency) setLoadedCurrencyState(maybeCurrency)
val currency = maybeCurrency.getOrElse { val currency = maybeCurrency.getOrElse {
Timber.e(it, "Failed to load VISA currency")
setFailedTxHistoryState(it) setFailedTxHistoryState(it)
return@flow return@flow
} }

View file

@ -4,7 +4,10 @@ import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.* import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -60,7 +63,6 @@ private fun BalancesAndLimitsBlock(state: BalancesAndLimitsBlockState, modifier:
) )
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private inline fun ContentContainer( private inline fun ContentContainer(
enabled: Boolean, enabled: Boolean,
@ -147,7 +149,7 @@ private fun AvailableLimit(availableBalance: String, limitDays: Int, modifier: M
color = TangemTheme.colors.text.primary1, color = TangemTheme.colors.text.primary1,
) )
Text( Text(
text = "available $limitDays-day limit", text = "available for $limitDays day(s)",
style = TangemTheme.typography.body2, style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary, color = TangemTheme.colors.text.tertiary,
) )

View file

@ -94,8 +94,8 @@ private fun LimitsBlock(limits: BalancesAndLimitsBottomSheetConfig.Limit, modifi
title = stringReference("Limits"), title = stringReference("Limits"),
content = { content = {
BlockItem( BlockItem(
title = stringReference("In-store (otp)"), title = stringReference("Total"),
value = limits.inStore, value = limits.total,
) )
BlockItem( BlockItem(
title = stringReference("Other (no-otp)"), title = stringReference("Other (no-otp)"),
@ -193,7 +193,7 @@ private class BalancesAndLimitsBottomSheetParameterProvider :
), ),
limit = BalancesAndLimitsBottomSheetConfig.Limit( limit = BalancesAndLimitsBottomSheetConfig.Limit(
availableBy = "Nov, 11 USDT", availableBy = "Nov, 11 USDT",
inStore = "563.00 USDT", total = "563.00 USDT",
other = "100.00 USDT", other = "100.00 USDT",
singleTransaction = "100.00 USDT", singleTransaction = "100.00 USDT",
onInfoClick = {}, onInfoClick = {},

View file

@ -2,10 +2,7 @@ package com.tangem.lib.visa;
import org.web3j.abi.EventEncoder; import org.web3j.abi.EventEncoder;
import org.web3j.abi.TypeReference; import org.web3j.abi.TypeReference;
import org.web3j.abi.datatypes.Address; import org.web3j.abi.datatypes.*;
import org.web3j.abi.datatypes.Event;
import org.web3j.abi.datatypes.Function;
import org.web3j.abi.datatypes.Utf8String;
import org.web3j.abi.datatypes.generated.Uint256; import org.web3j.abi.datatypes.generated.Uint256;
import org.web3j.abi.datatypes.generated.Uint8; import org.web3j.abi.datatypes.generated.Uint8;
import org.web3j.crypto.Credentials; import org.web3j.crypto.Credentials;
@ -32,10 +29,10 @@ import io.reactivex.Flowable;
* <p>Auto generated code. * <p>Auto generated code.
* <p><strong>Do not modify!</strong> * <p><strong>Do not modify!</strong>
* <p>Please use the <a href="https://docs.web3j.io/command_line.html">web3j command line tools</a>, * <p>Please use the <a href="https://docs.web3j.io/command_line.html">web3j command line tools</a>,
* or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the * or the org.web3j.codegen.SolidityFunctionWrapperGenerator in the
* <a href="https://github.com/web3j/web3j/tree/master/codegen">codegen module</a> to update. * <a href="https://github.com/web3j/web3j/tree/master/codegen">codegen module</a> to update.
* *
* <p>Generated with web3j version 1.5.0. * <p>Generated with web3j version 1.5.2.
*/ */
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
class ERC20 extends Contract { class ERC20 extends Contract {
@ -113,6 +110,16 @@ class ERC20 extends Contract {
return typedResponse; return typedResponse;
} }
@Deprecated
public static ERC20 load(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
return new ERC20(contractAddress, web3j, credentials, gasPrice, gasLimit);
}
@Deprecated
public static ERC20 load(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) {
return new ERC20(contractAddress, web3j, transactionManager, gasPrice, gasLimit);
}
public static List<TransferEventResponse> getTransferEvents(TransactionReceipt transactionReceipt) { public static List<TransferEventResponse> getTransferEvents(TransactionReceipt transactionReceipt) {
List<EventValuesWithLog> valueList = staticExtractEventParametersWithLog(TRANSFER_EVENT, transactionReceipt); List<EventValuesWithLog> valueList = staticExtractEventParametersWithLog(TRANSFER_EVENT, transactionReceipt);
ArrayList<TransferEventResponse> responses = new ArrayList<TransferEventResponse>(valueList.size()); ArrayList<TransferEventResponse> responses = new ArrayList<TransferEventResponse>(valueList.size());
@ -127,16 +134,6 @@ class ERC20 extends Contract {
return responses; return responses;
} }
@Deprecated
public static ERC20 load(String contractAddress, Web3j web3j, Credentials credentials, BigInteger gasPrice, BigInteger gasLimit) {
return new ERC20(contractAddress, web3j, credentials, gasPrice, gasLimit);
}
@Deprecated
public static ERC20 load(String contractAddress, Web3j web3j, TransactionManager transactionManager, BigInteger gasPrice, BigInteger gasLimit) {
return new ERC20(contractAddress, web3j, transactionManager, gasPrice, gasLimit);
}
public static TransferEventResponse getTransferEventFromLog(Log log) { public static TransferEventResponse getTransferEventFromLog(Log log) {
EventValuesWithLog eventValues = staticExtractEventParametersWithLog(TRANSFER_EVENT, log); EventValuesWithLog eventValues = staticExtractEventParametersWithLog(TRANSFER_EVENT, log);
TransferEventResponse typedResponse = new TransferEventResponse(); TransferEventResponse typedResponse = new TransferEventResponse();
@ -147,14 +144,6 @@ class ERC20 extends Contract {
return typedResponse; return typedResponse;
} }
public static ERC20 load(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) {
return new ERC20(contractAddress, web3j, credentials, contractGasProvider);
}
public static ERC20 load(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) {
return new ERC20(contractAddress, web3j, transactionManager, contractGasProvider);
}
public Flowable<TransferEventResponse> transferEventFlowable(EthFilter filter) { public Flowable<TransferEventResponse> transferEventFlowable(EthFilter filter) {
return web3j.ethLogFlowable(filter).map(log -> getTransferEventFromLog(log)); return web3j.ethLogFlowable(filter).map(log -> getTransferEventFromLog(log));
} }
@ -165,6 +154,14 @@ class ERC20 extends Contract {
return transferEventFlowable(filter); return transferEventFlowable(filter);
} }
public static ERC20 load(String contractAddress, Web3j web3j, Credentials credentials, ContractGasProvider contractGasProvider) {
return new ERC20(contractAddress, web3j, credentials, contractGasProvider);
}
public static ERC20 load(String contractAddress, Web3j web3j, TransactionManager transactionManager, ContractGasProvider contractGasProvider) {
return new ERC20(contractAddress, web3j, transactionManager, contractGasProvider);
}
public Flowable<ApprovalEventResponse> approvalEventFlowable(EthFilter filter) { public Flowable<ApprovalEventResponse> approvalEventFlowable(EthFilter filter) {
return web3j.ethLogFlowable(filter).map(log -> getApprovalEventFromLog(log)); return web3j.ethLogFlowable(filter).map(log -> getApprovalEventFromLog(log));
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -7,7 +7,6 @@ import com.tangem.lib.visa.model.VisaBalancesAndLimits.Limits
import com.tangem.lib.visa.utils.toBigDecimal import com.tangem.lib.visa.utils.toBigDecimal
import com.tangem.lib.visa.utils.toInstant import com.tangem.lib.visa.utils.toInstant
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
import org.joda.time.Instant import org.joda.time.Instant
import org.web3j.protocol.Web3j import org.web3j.protocol.Web3j
import org.web3j.tx.TransactionManager import org.web3j.tx.TransactionManager
@ -18,39 +17,40 @@ internal class DefaultVisaContractInfoProvider(
private val transactionManager: TransactionManager, private val transactionManager: TransactionManager,
private val gasProvider: ContractGasProvider, private val gasProvider: ContractGasProvider,
private val bridgeProcessorAddress: String, private val bridgeProcessorAddress: String,
private val paymentAccountRegistryAddress: String,
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
) : VisaContractInfoProvider { ) : VisaContractInfoProvider {
override suspend fun getBalancesAndLimits(walletAddress: String): VisaBalancesAndLimits { override suspend fun getBalancesAndLimits(walletAddress: String): VisaBalancesAndLimits {
return withContext(dispatchers.io) { return parZip(
val tangemBridgeProcessor = TangemBridgeProcessor.load( dispatchers.io,
/* contractAddress = */ bridgeProcessorAddress, { loadPaymentAccount(walletAddress) },
/* web3j = */ web3j, { loadPaymentTokenInfo() },
/* transactionManager = */ transactionManager, { paymentAccount, paymentToken ->
/* contractGasProvider = */ gasProvider, fetchBalancesAndLimits(paymentAccount, paymentToken)
) },
)
parZip(
dispatchers.io,
{ loadPaymentAccount(walletAddress, tangemBridgeProcessor) },
{ loadPaymentTokenInfo(tangemBridgeProcessor) },
{ paymentAccount, paymentToken ->
fetchBalancesAndLimits(paymentAccount, paymentToken)
},
)
}
} }
private fun loadPaymentAccount( private fun loadPaymentAccount(walletAddress: String): TangemPaymentAccount {
walletAddress: String, val paymentAccountRegistry = TangemPaymentAccountRegistry.load(
tangemBridgeProcessor: TangemBridgeProcessor, /* contractAddress = */ paymentAccountRegistryAddress,
): TangemPaymentAccount { /* web3j = */ web3j,
val paymentAccountAddress = tangemBridgeProcessor.getPaymentAccount(walletAddress).send() /* transactionManager = */ transactionManager,
/* contractGasProvider = */ gasProvider,
)
val paymentAccountAddress = paymentAccountRegistry.paymentAccountByCard(walletAddress).send()
return TangemPaymentAccount.load(paymentAccountAddress, web3j, transactionManager, gasProvider) return TangemPaymentAccount.load(paymentAccountAddress, web3j, transactionManager, gasProvider)
} }
private fun loadPaymentTokenInfo(tangemBridgeProcessor: TangemBridgeProcessor): PaymentTokenInfo { private fun loadPaymentTokenInfo(): PaymentTokenInfo {
val tangemBridgeProcessor = TangemBridgeProcessor.load(
/* contractAddress = */ bridgeProcessorAddress,
/* web3j = */ web3j,
/* transactionManager = */ transactionManager,
/* contractGasProvider = */ gasProvider,
)
val paymentTokenContractAddress = tangemBridgeProcessor.paymentToken().send() val paymentTokenContractAddress = tangemBridgeProcessor.paymentToken().send()
val paymentTokenContract = ERC20.load(paymentTokenContractAddress, web3j, transactionManager, gasProvider) val paymentTokenContract = ERC20.load(paymentTokenContractAddress, web3j, transactionManager, gasProvider)
val paymentTokenDecimals = paymentTokenContract.decimals().send() val paymentTokenDecimals = paymentTokenContract.decimals().send()

View file

@ -28,6 +28,7 @@ interface VisaContractInfoProvider {
private val dispatchers: CoroutineDispatcherProvider, private val dispatchers: CoroutineDispatcherProvider,
private val baseUrl: String = VisaConfig.BASE_RPC_URL, private val baseUrl: String = VisaConfig.BASE_RPC_URL,
private val bridgeProcessorAddress: String = VisaConfig.BRIDGE_PROCESSOR_CONTRACT_ADDRESS, private val bridgeProcessorAddress: String = VisaConfig.BRIDGE_PROCESSOR_CONTRACT_ADDRESS,
private val paymentAccountRegistryAddress: String = VisaConfig.PAYMENT_ACCOUNT_REGISTRY_ADDRESS,
private val chainId: Long = VisaConfig.CHAIN_ID, private val chainId: Long = VisaConfig.CHAIN_ID,
private val networkTimeoutSeconds: Long = VisaConfig.NETWORK_TIMEOUT_SECONDS, private val networkTimeoutSeconds: Long = VisaConfig.NETWORK_TIMEOUT_SECONDS,
private val decimals: Int = VisaConfig.DECIMALS, private val decimals: Int = VisaConfig.DECIMALS,
@ -45,6 +46,7 @@ interface VisaContractInfoProvider {
transactionManager = transactionManager, transactionManager = transactionManager,
gasProvider = gasProvider, gasProvider = gasProvider,
bridgeProcessorAddress = bridgeProcessorAddress, bridgeProcessorAddress = bridgeProcessorAddress,
paymentAccountRegistryAddress = paymentAccountRegistryAddress,
dispatchers = dispatchers, dispatchers = dispatchers,
) )
} }

View file

@ -2,8 +2,9 @@ package com.tangem.lib.visa.utils
internal object VisaConfig { internal object VisaConfig {
const val BASE_RPC_URL = "https://rpc-mumbai.maticvigil.com/" const val BASE_RPC_URL = "https://polygon-mumbai.g.alchemy.com/v2/_1qqjXgBC_IikaXChnna8KTcV2eMMIQG/"
const val BRIDGE_PROCESSOR_CONTRACT_ADDRESS = "0x62119697e78178512bfcc456ae6d1b7dee9fbaa6" const val BRIDGE_PROCESSOR_CONTRACT_ADDRESS = "0xe32ecbbc1ec17fa9c160569cd613ad568ca50279"
const val PAYMENT_ACCOUNT_REGISTRY_ADDRESS = "0x3f4ae01073d1a9d5a92315fe118e57d1cdec7c44"
const val CHAIN_ID = 80_001L const val CHAIN_ID = 80_001L
const val DECIMALS = 9 const val DECIMALS = 9
const val GAS_LIMIT = 500_000_000L const val GAS_LIMIT = 500_000_000L