Updated on 2026-08-14
This commit is contained in:
commit
7db00540c5
351 changed files with 6478 additions and 1691 deletions
|
|
@ -29,6 +29,5 @@ interface ETagsStore {
|
|||
enum class Key {
|
||||
WalletAccounts,
|
||||
UserTokens,
|
||||
;
|
||||
}
|
||||
}
|
||||
|
|
@ -47,7 +47,6 @@ interface QuotesFetcher {
|
|||
value = setOf(PRICE, PRICE_CHANGE_24H, PRICE_CHANGE_1W, PRICE_CHANGE_30D).combine(),
|
||||
),
|
||||
LAST_UPDATED_AT(value = "lastUpdatedAt"),
|
||||
;
|
||||
}
|
||||
|
||||
sealed interface Error {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.data.pay.di
|
||||
|
||||
import com.tangem.data.pay.repository.DefaultKycRepository
|
||||
import com.tangem.data.pay.repository.DefaultTangemPayTxHistoryRepository
|
||||
import com.tangem.data.pay.repository.DefaultOnboardingRepository
|
||||
import com.tangem.domain.pay.repository.KycRepository
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.tangempay.repository.TangemPayTxHistoryRepository
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -21,4 +23,8 @@ internal interface TangemPayDataModule {
|
|||
@Binds
|
||||
@Singleton
|
||||
fun bindOnboardingRepository(repository: DefaultOnboardingRepository): OnboardingRepository
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindTangemPayTxHistoryRepository(repository: DefaultTangemPayTxHistoryRepository): TangemPayTxHistoryRepository
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tangem.data.pay.repository
|
||||
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.pay.TangemPayApi
|
||||
import com.tangem.domain.pay.KycStartInfo
|
||||
import com.tangem.domain.pay.repository.KycRepository
|
||||
|
|
@ -16,9 +15,9 @@ internal class DefaultKycRepository @Inject constructor(
|
|||
|
||||
override suspend fun getKycStartInfo() = withContext(dispatchers.io) {
|
||||
requestHelper.request { authHeader ->
|
||||
tangemPayApi.getKycAccess(authHeader = authHeader).getOrThrow().result
|
||||
tangemPayApi.getKycAccess(authHeader = authHeader)
|
||||
}.map {
|
||||
KycStartInfo(token = it.token, locale = it.locale)
|
||||
KycStartInfo(token = it.result.token, locale = it.result.locale)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,13 +3,11 @@ package com.tangem.data.pay.repository
|
|||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.pay.TangemPayApi
|
||||
import com.tangem.datasource.api.pay.models.request.DeeplinkValidityRequest
|
||||
import com.tangem.domain.pay.model.CustomerInfo
|
||||
import com.tangem.domain.pay.model.ProductInstance
|
||||
import com.tangem.domain.pay.repository.OnboardingRepository
|
||||
import com.tangem.domain.visa.error.VisaApiError
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val VALID_STATUS = "valid"
|
||||
|
|
@ -21,19 +19,17 @@ internal class DefaultOnboardingRepository @Inject constructor(
|
|||
|
||||
override suspend fun validateDeeplink(link: String): Either<UniversalError, Boolean> = either {
|
||||
return requestHelper.request {
|
||||
tangemPayApi.validateDeeplink(DeeplinkValidityRequest(link)).getOrThrow().result
|
||||
?: raise(VisaApiError.UnknownWithoutCode)
|
||||
}.map { result -> result.status == VALID_STATUS }
|
||||
tangemPayApi.validateDeeplink(DeeplinkValidityRequest(link))
|
||||
}.map { it.result?.status == VALID_STATUS }
|
||||
}
|
||||
|
||||
override suspend fun getCustomerInfo(): Either<UniversalError, CustomerInfo> = either {
|
||||
return requestHelper.request { authHeader ->
|
||||
val response = tangemPayApi.getCustomerMe(authHeader).getOrThrow()
|
||||
response.result ?: raise(VisaApiError.UnknownWithoutCode)
|
||||
}.map { result ->
|
||||
tangemPayApi.getCustomerMe(authHeader)
|
||||
}.map {
|
||||
CustomerInfo(
|
||||
productInstance = result.productInstance?.let { ProductInstance(id = it.id, status = it.status) },
|
||||
kycStatus = result.kyc?.status,
|
||||
productInstance = it.result?.productInstance?.let { ProductInstance(id = it.id, status = it.status) },
|
||||
kycStatus = it.result?.kyc?.status,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
package com.tangem.data.pay.repository
|
||||
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.visa.utils.TangemPayTxHistoryItemConverter
|
||||
import com.tangem.datasource.api.pay.TangemPayApi
|
||||
import com.tangem.datasource.local.visa.TangemPayTxHistoryItemsStore
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.tangempay.model.TangemPayTxHistoryListBatchFlow
|
||||
import com.tangem.domain.tangempay.model.TangemPayTxHistoryListBatchingContext
|
||||
import com.tangem.domain.tangempay.model.TangemPayTxHistoryListConfig
|
||||
import com.tangem.domain.tangempay.repository.TangemPayTxHistoryRepository
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
import com.tangem.pagination.BatchFetchResult
|
||||
import com.tangem.pagination.BatchListSource
|
||||
import com.tangem.pagination.fetcher.BatchFetcher
|
||||
import com.tangem.pagination.fetcher.CursorBatchFetcher
|
||||
import com.tangem.pagination.toBatchFlow
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val INITIAL_CURSOR = "initial_cursor_key"
|
||||
|
||||
internal class DefaultTangemPayTxHistoryRepository @Inject constructor(
|
||||
private val requestPerformer: TangemPayRequestPerformer,
|
||||
private val visaApi: TangemPayApi,
|
||||
private val cacheRegistry: CacheRegistry,
|
||||
private val txHistoryItemsStore: TangemPayTxHistoryItemsStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : TangemPayTxHistoryRepository {
|
||||
|
||||
override fun getTxHistoryBatchFlow(
|
||||
batchSize: Int,
|
||||
context: TangemPayTxHistoryListBatchingContext,
|
||||
): TangemPayTxHistoryListBatchFlow {
|
||||
return BatchListSource(
|
||||
fetchDispatcher = dispatchers.io,
|
||||
context = context,
|
||||
generateNewKey = { keys -> keys.lastOrNull()?.inc() ?: 0 },
|
||||
batchFetcher = createFetcher(batchSize),
|
||||
).toBatchFlow()
|
||||
}
|
||||
|
||||
private fun createFetcher(
|
||||
batchSize: Int,
|
||||
): BatchFetcher<TangemPayTxHistoryListConfig, List<TangemPayTxHistoryItem>> {
|
||||
return CursorBatchFetcher(
|
||||
prefetchDistance = batchSize,
|
||||
batchSize = batchSize,
|
||||
subFetcher = { request, _, _ ->
|
||||
val items = loadItems(config = request.params, cursor = request.cursor, limit = request.limit)
|
||||
BatchFetchResult.Success(
|
||||
data = items,
|
||||
last = items.size < request.limit,
|
||||
empty = items.isEmpty(),
|
||||
)
|
||||
},
|
||||
cursorFromItem = { item -> item.id }, // last item’s id becomes next cursor
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun loadItems(
|
||||
config: TangemPayTxHistoryListConfig,
|
||||
cursor: String?,
|
||||
limit: Int,
|
||||
): List<TangemPayTxHistoryItem> {
|
||||
cacheRegistry.invokeOnExpire(
|
||||
key = getCacheKey(userWalletId = config.userWalletId, cursor = cursor),
|
||||
skipCache = config.refresh,
|
||||
block = { fetch(userWalletId = config.userWalletId, cursor = cursor, pageSize = limit) },
|
||||
)
|
||||
|
||||
return txHistoryItemsStore.getSyncOrNull(
|
||||
key = config.userWalletId,
|
||||
cursor = cursor ?: INITIAL_CURSOR,
|
||||
).orEmpty()
|
||||
}
|
||||
|
||||
private fun getCacheKey(userWalletId: UserWalletId, cursor: String?): String {
|
||||
return "tangem_pay_tx_history_${userWalletId}_${cursor ?: INITIAL_CURSOR}"
|
||||
}
|
||||
|
||||
private suspend fun fetch(userWalletId: UserWalletId, cursor: String?, pageSize: Int) {
|
||||
val response = requestPerformer.request { authHeader ->
|
||||
visaApi.getTangemPayTxHistory(
|
||||
authHeader = authHeader,
|
||||
limit = pageSize,
|
||||
cursor = cursor,
|
||||
)
|
||||
}.getOrNull()
|
||||
response?.let {
|
||||
val items = TangemPayTxHistoryItemConverter.convertList(response.result.transactions)
|
||||
txHistoryItemsStore.store(key = userWalletId, cursor = cursor ?: INITIAL_CURSOR, value = items)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,9 @@ import arrow.core.Either
|
|||
import arrow.core.raise.either
|
||||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.core.error.UniversalError
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.pay.models.response.VisaErrorResponseJsonAdapter
|
||||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.visa.TangemPayStorage
|
||||
|
|
@ -44,18 +46,19 @@ internal class TangemPayRequestPerformer @Inject constructor(
|
|||
private val refreshTokensMutex = Mutex()
|
||||
private var refreshTokensJob: Deferred<Either<UniversalError, VisaAuthTokens>>? = null
|
||||
|
||||
suspend fun <T : Any> request(requestBlock: suspend (header: String) -> T): Either<UniversalError, T> = either {
|
||||
withContext(dispatchers.io) {
|
||||
performRequest(requestBlock = requestBlock, refreshTokens = ::refreshAuthTokens).bind()
|
||||
suspend fun <T : Any> request(requestBlock: suspend (header: String) -> ApiResponse<T>): Either<UniversalError, T> =
|
||||
either {
|
||||
withContext(dispatchers.io) {
|
||||
performRequest(requestBlock = requestBlock, refreshTokens = ::refreshAuthTokens).bind()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun <T : Any> performRequest(
|
||||
requestBlock: suspend (header: String) -> T,
|
||||
requestBlock: suspend (header: String) -> ApiResponse<T>,
|
||||
refreshTokens: (suspend () -> Either<UniversalError, VisaAuthTokens>)? = null,
|
||||
): Either<UniversalError, T> = either {
|
||||
runCatching {
|
||||
requestBlock("Bearer ${getAccessTokens().bind().accessToken}")
|
||||
requestBlock("Bearer ${getAccessTokens().bind().accessToken}").getOrThrow()
|
||||
}.getOrElse { error ->
|
||||
when (error) {
|
||||
is ApiResponseError.HttpException -> {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
package com.tangem.data.visa.utils
|
||||
|
||||
import com.tangem.datasource.api.pay.models.response.TangemPayTxHistoryResponse
|
||||
import com.tangem.domain.visa.model.TangemPayTxHistoryItem
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal object TangemPayTxHistoryItemConverter :
|
||||
Converter<TangemPayTxHistoryResponse.Transaction, TangemPayTxHistoryItem> {
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
override fun convert(value: TangemPayTxHistoryResponse.Transaction): TangemPayTxHistoryItem {
|
||||
val spend = value.spend
|
||||
val collateral = value.collateral
|
||||
val payment = value.payment
|
||||
val fee = value.fee
|
||||
|
||||
return TangemPayTxHistoryItem(
|
||||
id = value.id,
|
||||
date = when {
|
||||
spend != null -> spend.postedAt
|
||||
collateral != null -> collateral.postedAt
|
||||
payment != null -> payment.postedAt
|
||||
fee != null -> fee.postedAt
|
||||
else -> null
|
||||
},
|
||||
amount = when {
|
||||
spend != null -> spend.amount
|
||||
collateral != null -> collateral.amount
|
||||
payment != null -> payment.amount
|
||||
fee != null -> fee.amount
|
||||
else -> null
|
||||
},
|
||||
merchantName = when {
|
||||
spend != null -> spend.merchantName
|
||||
else -> null
|
||||
},
|
||||
status = when {
|
||||
spend != null -> spend.status
|
||||
payment != null -> payment.status
|
||||
else -> null
|
||||
},
|
||||
currency = when {
|
||||
spend != null -> spend.currency
|
||||
collateral != null -> collateral.currency
|
||||
payment != null -> payment.currency
|
||||
fee != null -> fee.currency
|
||||
else -> null
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.data.visa.utils
|
||||
|
||||
import com.tangem.datasource.api.pay.models.response.VisaTxHistoryResponse
|
||||
import com.tangem.domain.visa.model.VisaTxHistoryItem
|
||||
import com.tangem.utils.converter.Converter
|
||||
|
||||
internal object VisaTxHistoryItemConverter : Converter<VisaTxHistoryResponse.Transaction, VisaTxHistoryItem> {
|
||||
|
||||
override fun convert(value: VisaTxHistoryResponse.Transaction): VisaTxHistoryItem {
|
||||
return VisaTxHistoryItem(
|
||||
id = value.transactionId.toString(),
|
||||
date = value.transactionDt,
|
||||
amount = value.blockchainAmount,
|
||||
fiatAmount = value.transactionAmount,
|
||||
merchantName = value.merchantName,
|
||||
status = value.transactionStatus,
|
||||
fiatCurrency = findCurrencyByNumericCode(value.transactionCurrencyCode),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
package com.tangem.data.visa.utils
|
||||
|
||||
import com.tangem.datasource.api.pay.models.response.VisaTxHistoryResponse
|
||||
import com.tangem.domain.visa.model.VisaTxHistoryItem
|
||||
|
||||
internal class VisaTxHistoryItemFactory {
|
||||
|
||||
fun create(transaction: VisaTxHistoryResponse.Transaction): VisaTxHistoryItem {
|
||||
return VisaTxHistoryItem(
|
||||
id = transaction.transactionId.toString(),
|
||||
date = transaction.transactionDt,
|
||||
amount = transaction.blockchainAmount,
|
||||
fiatAmount = transaction.transactionAmount,
|
||||
merchantName = transaction.merchantName,
|
||||
status = transaction.transactionStatus,
|
||||
fiatCurrency = findCurrencyByNumericCode(transaction.transactionCurrencyCode),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,8 +20,6 @@ internal class VisaTxHistoryPagingSource(
|
|||
val requestTxHistory: suspend (offset: Int, pageSize: Int) -> VisaTxHistoryResponse,
|
||||
) : PagingSource<Int, VisaTxHistoryItem>() {
|
||||
|
||||
private val itemsFactory = VisaTxHistoryItemFactory()
|
||||
|
||||
private val cardPublicKey = params.cardPublicKey
|
||||
private val pageSize = params.pageSize
|
||||
private val isRefresh = params.isRefresh
|
||||
|
|
@ -82,7 +80,7 @@ internal class VisaTxHistoryPagingSource(
|
|||
|
||||
pagedItems.update {
|
||||
it.toMutableMap().apply {
|
||||
this[offset] = response.transactions.map(itemsFactory::create)
|
||||
this[offset] = response.transactions.map(VisaTxHistoryItemConverter::convert)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -250,7 +250,9 @@ internal class UpdateWalletManagerResultFactoryTest {
|
|||
),
|
||||
),
|
||||
currenciesAmounts = setOf(
|
||||
UpdateWalletManagerResult.CryptoCurrencyAmount.Coin(value = BigDecimal.ZERO), // default for demo
|
||||
UpdateWalletManagerResult.CryptoCurrencyAmount.Coin(
|
||||
value = BigDecimal.ZERO,
|
||||
), // default for demo
|
||||
),
|
||||
currentTransactions = emptySet(),
|
||||
),
|
||||
|
|
@ -272,7 +274,9 @@ internal class UpdateWalletManagerResultFactoryTest {
|
|||
),
|
||||
),
|
||||
currenciesAmounts = setOf(
|
||||
UpdateWalletManagerResult.CryptoCurrencyAmount.Coin(value = BigDecimal.ONE), // used demo amount
|
||||
UpdateWalletManagerResult.CryptoCurrencyAmount.Coin(
|
||||
value = BigDecimal.ONE,
|
||||
), // used demo amount
|
||||
),
|
||||
currentTransactions = emptySet(),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -415,6 +415,6 @@ internal class DefaultWalletsRepository(
|
|||
else -> ActivatePromoCodeError.ActivationFailed
|
||||
}
|
||||
return@fold error.left()
|
||||
},)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -114,62 +114,64 @@ class DefaultWalletsRepositoryTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN API returns wallets WHEN getWalletsInfo THEN should return converted wallets and update cache if requested`() = runTest {
|
||||
// GIVEN
|
||||
val applicationId = "test_app_id"
|
||||
val wallet1Id = "1234567890abcdef"
|
||||
val wallet2Id = "fedcba0987654321"
|
||||
val walletResponses = listOf(
|
||||
WalletResponse(
|
||||
id = wallet1Id,
|
||||
notifyStatus = true,
|
||||
),
|
||||
WalletResponse(
|
||||
id = wallet2Id,
|
||||
notifyStatus = false,
|
||||
),
|
||||
)
|
||||
coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses)
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns mockk()
|
||||
fun `GIVEN API returns wallets WHEN getWalletsInfo THEN should return converted wallets and update cache if requested`() =
|
||||
runTest {
|
||||
// GIVEN
|
||||
val applicationId = "test_app_id"
|
||||
val wallet1Id = "1234567890abcdef"
|
||||
val wallet2Id = "fedcba0987654321"
|
||||
val walletResponses = listOf(
|
||||
WalletResponse(
|
||||
id = wallet1Id,
|
||||
notifyStatus = true,
|
||||
),
|
||||
WalletResponse(
|
||||
id = wallet2Id,
|
||||
notifyStatus = false,
|
||||
),
|
||||
)
|
||||
coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses)
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns mockk()
|
||||
|
||||
// WHEN
|
||||
val result = repository.getWalletsInfo(applicationId, updateCache = true)
|
||||
// WHEN
|
||||
val result = repository.getWalletsInfo(applicationId, updateCache = true)
|
||||
|
||||
// THEN
|
||||
assertThat(result).hasSize(2)
|
||||
assertThat(result[0].walletId.stringValue).isEqualTo(wallet1Id)
|
||||
assertThat(result[0].isNotificationsEnabled).isTrue()
|
||||
assertThat(result[1].walletId.stringValue).isEqualTo(wallet2Id)
|
||||
assertThat(result[1].isNotificationsEnabled).isFalse()
|
||||
// THEN
|
||||
assertThat(result).hasSize(2)
|
||||
assertThat(result[0].walletId.stringValue).isEqualTo(wallet1Id)
|
||||
assertThat(result[0].isNotificationsEnabled).isTrue()
|
||||
assertThat(result[1].walletId.stringValue).isEqualTo(wallet2Id)
|
||||
assertThat(result[1].isNotificationsEnabled).isFalse()
|
||||
|
||||
coVerify(exactly = 1) { tangemTechApi.getWallets(applicationId) }
|
||||
coVerify(exactly = 2) { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
coVerify(exactly = 1) { tangemTechApi.getWallets(applicationId) }
|
||||
coVerify(exactly = 2) { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN API returns wallets WHEN getWalletsInfo with updateCache false THEN should return converted wallets without updating cache`() = runTest {
|
||||
// GIVEN
|
||||
val applicationId = "test_app_id"
|
||||
val wallet1Id = "1234567890abcdef"
|
||||
val walletResponses = listOf(
|
||||
WalletResponse(
|
||||
id = wallet1Id,
|
||||
notifyStatus = true,
|
||||
),
|
||||
)
|
||||
coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses)
|
||||
fun `GIVEN API returns wallets WHEN getWalletsInfo with updateCache false THEN should return converted wallets without updating cache`() =
|
||||
runTest {
|
||||
// GIVEN
|
||||
val applicationId = "test_app_id"
|
||||
val wallet1Id = "1234567890abcdef"
|
||||
val walletResponses = listOf(
|
||||
WalletResponse(
|
||||
id = wallet1Id,
|
||||
notifyStatus = true,
|
||||
),
|
||||
)
|
||||
coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses)
|
||||
|
||||
// WHEN
|
||||
val result = repository.getWalletsInfo(applicationId, updateCache = false)
|
||||
// WHEN
|
||||
val result = repository.getWalletsInfo(applicationId, updateCache = false)
|
||||
|
||||
// THEN
|
||||
assertThat(result).hasSize(1)
|
||||
assertThat(result[0].walletId.stringValue).isEqualTo(wallet1Id)
|
||||
assertThat(result[0].isNotificationsEnabled).isTrue()
|
||||
// THEN
|
||||
assertThat(result).hasSize(1)
|
||||
assertThat(result[0].walletId.stringValue).isEqualTo(wallet1Id)
|
||||
assertThat(result[0].isNotificationsEnabled).isTrue()
|
||||
|
||||
coVerify(exactly = 1) { tangemTechApi.getWallets(applicationId) }
|
||||
coVerify(exactly = 0) { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
coVerify(exactly = 1) { tangemTechApi.getWallets(applicationId) }
|
||||
coVerify(exactly = 0) { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN user wallets and application ID WHEN associateWallets THEN should convert and send to API`() = runTest {
|
||||
|
|
@ -271,8 +273,8 @@ class DefaultWalletsRepositoryTest {
|
|||
// GIVEN
|
||||
coEvery { tangemTechApi.activatePromoCode(any()) } returns
|
||||
ApiResponse.Error(
|
||||
HttpException(code = HttpException.Code.NOT_FOUND, message = null, errorBody = null),
|
||||
) as ApiResponse<PromocodeActivationResponse>
|
||||
HttpException(code = HttpException.Code.NOT_FOUND, message = null, errorBody = null),
|
||||
) as ApiResponse<PromocodeActivationResponse>
|
||||
|
||||
// WHEN
|
||||
val result = repository.activatePromoCode(promoCode = "PROMO", bitcoinAddress = "addr")
|
||||
|
|
@ -288,8 +290,8 @@ class DefaultWalletsRepositoryTest {
|
|||
// GIVEN
|
||||
coEvery { tangemTechApi.activatePromoCode(any()) } returns
|
||||
ApiResponse.Error(
|
||||
HttpException(code = HttpException.Code.CONFLICT, message = null, errorBody = null),
|
||||
) as ApiResponse<PromocodeActivationResponse>
|
||||
HttpException(code = HttpException.Code.CONFLICT, message = null, errorBody = null),
|
||||
) as ApiResponse<PromocodeActivationResponse>
|
||||
|
||||
// WHEN
|
||||
val result = repository.activatePromoCode(promoCode = "PROMO", bitcoinAddress = "addr")
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ internal class DefaultYieldSupplyTransactionRepository(
|
|||
override suspend fun createEnterTransactions(
|
||||
userWalletId: UserWalletId,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
maxNetworkFee: BigDecimal,
|
||||
): List<TransactionData.Uncompiled> {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
|
||||
|
|
@ -62,6 +63,7 @@ internal class DefaultYieldSupplyTransactionRepository(
|
|||
existingYieldContractAddress = existingYieldContractAddress,
|
||||
calculatedYieldContractAddress = calculatedYieldContractAddress,
|
||||
yieldTokenStatus = yieldTokenStatus,
|
||||
maxNetworkFee = maxNetworkFee,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -93,12 +95,14 @@ internal class DefaultYieldSupplyTransactionRepository(
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
private fun buildEnterTransactions(
|
||||
walletManager: WalletManager,
|
||||
cryptoCurrency: CryptoCurrency.Token,
|
||||
existingYieldContractAddress: String?,
|
||||
calculatedYieldContractAddress: String,
|
||||
yieldTokenStatus: YieldSupplyStatus?,
|
||||
maxNetworkFee: BigDecimal,
|
||||
): MutableList<TransactionData.Uncompiled> {
|
||||
val enterTransactions = mutableListOf<TransactionData.Uncompiled>()
|
||||
|
||||
|
|
@ -108,6 +112,7 @@ internal class DefaultYieldSupplyTransactionRepository(
|
|||
createDeployTransaction(
|
||||
walletManager = walletManager,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
maxNetworkFee = maxNetworkFee,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -118,6 +123,7 @@ internal class DefaultYieldSupplyTransactionRepository(
|
|||
cryptoCurrency = cryptoCurrency,
|
||||
yieldSupplyStatus = yieldTokenStatus,
|
||||
yieldContractAddress = calculatedYieldContractAddress,
|
||||
maxNetworkFee = maxNetworkFee,
|
||||
),
|
||||
)
|
||||
!yieldTokenStatus.isActive -> enterTransactions.add(
|
||||
|
|
@ -126,6 +132,7 @@ internal class DefaultYieldSupplyTransactionRepository(
|
|||
cryptoCurrency = cryptoCurrency,
|
||||
yieldSupplyStatus = yieldTokenStatus,
|
||||
yieldContractAddress = calculatedYieldContractAddress,
|
||||
maxNetworkFee = maxNetworkFee,
|
||||
),
|
||||
)
|
||||
else -> Unit
|
||||
|
|
@ -202,6 +209,7 @@ internal class DefaultYieldSupplyTransactionRepository(
|
|||
isActive = sdkSupplyStatus?.isActive == true,
|
||||
isInitialized = sdkSupplyStatus?.isInitialized == true,
|
||||
isAllowedToSpend = isAllowedToSpend,
|
||||
// maxNetworkFee = sdkSupplyStatus?.maxNetworkFee,
|
||||
)
|
||||
}.onFailure(Timber::e).getOrNull()
|
||||
}
|
||||
|
|
@ -209,11 +217,12 @@ internal class DefaultYieldSupplyTransactionRepository(
|
|||
private fun createDeployTransaction(
|
||||
walletManager: WalletManager,
|
||||
cryptoCurrency: CryptoCurrency.Token,
|
||||
maxNetworkFee: BigDecimal,
|
||||
): TransactionData.Uncompiled {
|
||||
val callData = YieldSupplyContractCallDataProviderFactory.getDeployCallData(
|
||||
tokenContractAddress = cryptoCurrency.contractAddress,
|
||||
walletAddress = walletManager.wallet.address,
|
||||
maxNetworkFee = MAX_NETWORK_FEE.convertToSdkAmount(cryptoCurrency),
|
||||
maxNetworkFee = maxNetworkFee.convertToSdkAmount(cryptoCurrency),
|
||||
)
|
||||
|
||||
val factoryContractAddress = walletManager.getYieldSupplyContractAddresses()?.factoryContractAddress
|
||||
|
|
@ -234,10 +243,11 @@ internal class DefaultYieldSupplyTransactionRepository(
|
|||
cryptoCurrency: CryptoCurrency.Token,
|
||||
yieldContractAddress: String,
|
||||
yieldSupplyStatus: YieldSupplyStatus,
|
||||
maxNetworkFee: BigDecimal,
|
||||
): TransactionData.Uncompiled {
|
||||
val callData = YieldSupplyContractCallDataProviderFactory.getInitTokenCallData(
|
||||
tokenContractAddress = cryptoCurrency.contractAddress,
|
||||
maxNetworkFee = MAX_NETWORK_FEE.convertToSdkAmount(cryptoCurrency),
|
||||
maxNetworkFee = maxNetworkFee.convertToSdkAmount(cryptoCurrency),
|
||||
)
|
||||
|
||||
return createTransaction(
|
||||
|
|
@ -255,10 +265,11 @@ internal class DefaultYieldSupplyTransactionRepository(
|
|||
cryptoCurrency: CryptoCurrency.Token,
|
||||
yieldContractAddress: String,
|
||||
yieldSupplyStatus: YieldSupplyStatus,
|
||||
maxNetworkFee: BigDecimal,
|
||||
): TransactionData.Uncompiled {
|
||||
val callData = YieldSupplyContractCallDataProviderFactory.getReactivateTokenCallData(
|
||||
tokenContractAddress = cryptoCurrency.contractAddress,
|
||||
maxNetworkFee = MAX_NETWORK_FEE.convertToSdkAmount(cryptoCurrency),
|
||||
maxNetworkFee = maxNetworkFee.convertToSdkAmount(cryptoCurrency),
|
||||
)
|
||||
|
||||
return createTransaction(
|
||||
|
|
@ -364,8 +375,4 @@ internal class DefaultYieldSupplyTransactionRepository(
|
|||
isAllowedToSpend = yieldSupplyStatus?.isAllowedToSpend ?: false,
|
||||
),
|
||||
)
|
||||
|
||||
private companion object {
|
||||
val MAX_NETWORK_FEE: BigDecimal = BigDecimal.TEN // TODO for TESTNET only
|
||||
}
|
||||
}
|
||||
|
|
@ -16,7 +16,6 @@ import com.tangem.domain.models.yield.supply.YieldSupplyStatus
|
|||
import com.tangem.domain.utils.convertToSdkAmount
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import com.tangem.blockchain.yieldsupply.providers.YieldSupplyStatus as SDKYieldSupplyStatus
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
|
@ -26,6 +25,7 @@ import org.junit.jupiter.api.BeforeEach
|
|||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import java.math.BigDecimal
|
||||
import com.tangem.blockchain.yieldsupply.providers.YieldSupplyStatus as SDKYieldSupplyStatus
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class DefaultYieldSupplyTransactionRepositoryTest {
|
||||
|
|
@ -74,7 +74,11 @@ class DefaultYieldSupplyTransactionRepositoryTest {
|
|||
coEvery { walletManager.isAllowedToSpend(any()) } returns false
|
||||
coEvery { walletManager.calculateYieldContract() } returns yieldContractAddress
|
||||
|
||||
val result = repository.createEnterTransactions(userWalletId, cryptoCurrencyStatus)
|
||||
val result = repository.createEnterTransactions(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
maxNetworkFee = BigDecimal.TEN,
|
||||
)
|
||||
|
||||
// Assert that 3 transactions are returned: deploy, approve, enter
|
||||
Truth.assertThat(result).isNotNull()
|
||||
|
|
@ -117,13 +121,18 @@ class DefaultYieldSupplyTransactionRepositoryTest {
|
|||
fun `createEnterTransactions returns init-approve-enter transactions`() = runTest {
|
||||
coEvery { walletManager.getYieldContract() } returns yieldContractAddress
|
||||
coEvery { walletManager.calculateYieldContract() } returns yieldContractAddress
|
||||
coEvery { walletManager.isAllowedToSpend(any()) } returns false
|
||||
coEvery { walletManager.getYieldSupplyStatus(any()) } returns SDKYieldSupplyStatus(
|
||||
isActive = false,
|
||||
isInitialized = false,
|
||||
maxNetworkFee = BigDecimal.TEN,
|
||||
)
|
||||
|
||||
val result = repository.createEnterTransactions(userWalletId, cryptoCurrencyStatus)
|
||||
val result = repository.createEnterTransactions(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
maxNetworkFee = BigDecimal.TEN,
|
||||
)
|
||||
|
||||
// Assert that 3 transactions are returned: init token, approve, enter
|
||||
Truth.assertThat(result).isNotNull()
|
||||
|
|
@ -165,13 +174,18 @@ class DefaultYieldSupplyTransactionRepositoryTest {
|
|||
fun `createEnterTransactions returns reactivate-approve-enter transactions`() = runTest {
|
||||
coEvery { walletManager.getYieldContract() } returns yieldContractAddress
|
||||
coEvery { walletManager.calculateYieldContract() } returns yieldContractAddress
|
||||
coEvery { walletManager.isAllowedToSpend(any()) } returns false
|
||||
coEvery { walletManager.getYieldSupplyStatus(any()) } returns SDKYieldSupplyStatus(
|
||||
isActive = false,
|
||||
isInitialized = true,
|
||||
maxNetworkFee = BigDecimal.TEN,
|
||||
)
|
||||
|
||||
val result = repository.createEnterTransactions(userWalletId, cryptoCurrencyStatus)
|
||||
val result = repository.createEnterTransactions(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
maxNetworkFee = BigDecimal.TEN,
|
||||
)
|
||||
|
||||
// Assert that 3 transactions are returned: reactivate token, approve, enter
|
||||
Truth.assertThat(result).isNotNull()
|
||||
|
|
@ -220,7 +234,11 @@ class DefaultYieldSupplyTransactionRepositoryTest {
|
|||
)
|
||||
coEvery { walletManager.isAllowedToSpend(any()) } returns true
|
||||
|
||||
val result = repository.createEnterTransactions(userWalletId, cryptoCurrencyStatus)
|
||||
val result = repository.createEnterTransactions(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
maxNetworkFee = BigDecimal.TEN,
|
||||
)
|
||||
|
||||
// Assert that 2 transactions are returned: approve, enter
|
||||
Truth.assertThat(result).isNotNull()
|
||||
|
|
@ -257,7 +275,11 @@ class DefaultYieldSupplyTransactionRepositoryTest {
|
|||
)
|
||||
coEvery { walletManager.isAllowedToSpend(any()) } returns true
|
||||
|
||||
val result = repository.createEnterTransactions(userWalletId, cryptoCurrencyStatus)
|
||||
val result = repository.createEnterTransactions(
|
||||
userWalletId = userWalletId,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
maxNetworkFee = BigDecimal.TEN,
|
||||
)
|
||||
|
||||
// Assert that transaction is returned: enter
|
||||
Truth.assertThat(result).isNotNull()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue