Updated on 2026-08-14
This commit is contained in:
parent
b28711ef43
commit
ed26d23a78
9 changed files with 219 additions and 8 deletions
|
|
@ -67,7 +67,7 @@ internal class DefaultMultiNetworkStatusFetcher @Inject constructor(
|
|||
commonNetworkStatusFetcher.fetch(
|
||||
userWalletId = params.userWalletId,
|
||||
network = network,
|
||||
networkCurrencies = networksCurrencies[network].orEmpty().toSet(),
|
||||
networkCurrencies = networksCurrencies[network].orEmpty().toSet() + params.extraTokens,
|
||||
xpub = xpubByNetwork[network],
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ internal class DefaultSingleNetworkStatusFetcher @Inject constructor(
|
|||
params = MultiNetworkStatusFetcher.Params(
|
||||
userWalletId = params.userWalletId,
|
||||
networks = setOf(params.network),
|
||||
extraTokens = params.extraTokens,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,9 @@ import com.tangem.data.common.currency.CryptoCurrencyFactory
|
|||
import com.tangem.data.common.network.NetworkFactory
|
||||
import com.tangem.domain.card.common.visa.VisaUtilities
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.requireUserWalletsSync
|
||||
import com.tangem.domain.common.wallets.getSyncStrict
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.TangemPayCurrencyFactory
|
||||
import javax.inject.Inject
|
||||
|
|
@ -23,9 +24,7 @@ internal class DefaultTangemPayCurrencyFactory @Inject constructor(
|
|||
}
|
||||
|
||||
override fun create(userWalletId: UserWalletId): CryptoCurrency.Token {
|
||||
val userWallet = userWalletsListRepository.requireUserWalletsSync()
|
||||
.firstOrNull { it.walletId == userWalletId }
|
||||
?: error("User wallet with id $userWalletId not found")
|
||||
val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)
|
||||
val network = networkFactory.create(
|
||||
blockchain = VisaUtilities.visaBlockchain,
|
||||
userWallet = userWallet,
|
||||
|
|
@ -40,4 +39,21 @@ internal class DefaultTangemPayCurrencyFactory @Inject constructor(
|
|||
decimals = TangemPayCurrencyFactory.TOKEN_DECIMALS,
|
||||
)
|
||||
}
|
||||
|
||||
override fun createVirtualAccountToken(userWalletId: UserWalletId): CryptoCurrency.Token {
|
||||
val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)
|
||||
val network = networkFactory.create(
|
||||
blockchain = VisaUtilities.visaBlockchain,
|
||||
derivationPath = Network.DerivationPath.Custom(VisaUtilities.virtualAccountDerivationPath.rawPath),
|
||||
userWallet = userWallet,
|
||||
)
|
||||
return cryptoCurrencyFactory.createToken(
|
||||
network = requireNotNull(network),
|
||||
rawId = TangemPayCurrencyFactory.TOKEN_ID,
|
||||
name = TangemPayCurrencyFactory.TOKEN_NAME,
|
||||
symbol = TangemPayCurrencyFactory.TOKEN_NAME,
|
||||
contractAddress = TangemPayCurrencyFactory.TOKEN_CONTRACT_ADDRESS,
|
||||
decimals = TangemPayCurrencyFactory.TOKEN_DECIMALS,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,41 @@
|
|||
package com.tangem.data.virtualaccount.flow
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.data.common.network.NetworkFactory
|
||||
import com.tangem.data.virtualaccount.store.VirtualAccountStatusesStore
|
||||
import com.tangem.domain.card.common.visa.VisaUtilities
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.common.wallets.getSyncStrict
|
||||
import com.tangem.domain.core.utils.catchOn
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.account.VirtualAccountStatusValue
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.network.NetworkStatus
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusProducer
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||
import com.tangem.domain.pay.TangemPayCurrencyFactory
|
||||
import com.tangem.domain.virtualaccount.flow.VirtualAccountStatusFetcher
|
||||
import com.tangem.domain.wallets.extension.hasDerivation
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import java.math.BigDecimal
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class DefaultVirtualAccountStatusFetcher @Inject constructor(
|
||||
private val virtualAccountStatusesStore: VirtualAccountStatusesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val networkFactory: NetworkFactory,
|
||||
private val tangemPayCurrencyFactory: TangemPayCurrencyFactory,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher,
|
||||
private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier,
|
||||
) : VirtualAccountStatusFetcher {
|
||||
|
||||
override suspend fun invoke(params: VirtualAccountStatusFetcher.Params) = Either.catchOn(dispatchers.default) {
|
||||
|
|
@ -21,6 +43,7 @@ internal class DefaultVirtualAccountStatusFetcher @Inject constructor(
|
|||
// TODO([REDACTED_TASK_KEY]): Replace with the real VA status fetch (provisioning state, balance and banking
|
||||
// details) from the backend once Virtual Account status endpoints are available. Until then the
|
||||
// account is surfaced as NotCreated so the entity flows through the app end-to-end.
|
||||
getBalance(params.userWalletId)
|
||||
virtualAccountStatusesStore.store(
|
||||
userWalletId = params.userWalletId,
|
||||
status = AccountStatus.Virtual(account = account, value = VirtualAccountStatusValue.NotCreated),
|
||||
|
|
@ -31,4 +54,55 @@ internal class DefaultVirtualAccountStatusFetcher @Inject constructor(
|
|||
source = StatusSource.ONLY_CACHE,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getBalance(userWalletId: UserWalletId): Either<VirtualAccountStatusValue, BigDecimal> {
|
||||
val userWallet = userWalletsListRepository.getSyncStrict(userWalletId)
|
||||
|
||||
val hasVirtualAccountDerivation = userWallet.hasDerivation(
|
||||
blockchain = VisaUtilities.visaBlockchain,
|
||||
derivationPath = VisaUtilities.virtualAccountDerivationPath.rawPath,
|
||||
)
|
||||
if (!hasVirtualAccountDerivation) {
|
||||
// TODO: Doston(VA) Derive will be implemented in [REDACTED_TASK_KEY]
|
||||
TangemLogger.withTag(TAG).d("Virtual account is not derived")
|
||||
return VirtualAccountStatusValue.Error.NotSynced.left()
|
||||
}
|
||||
|
||||
val network = networkFactory.create(
|
||||
blockchain = VisaUtilities.visaBlockchain,
|
||||
derivationPath =
|
||||
Network.DerivationPath.Custom(VisaUtilities.virtualAccountDerivationPath.rawPath),
|
||||
userWallet = userWallet,
|
||||
)
|
||||
if (network == null) {
|
||||
TangemLogger.withTag(TAG).d("Can not create network for Virtual account")
|
||||
return VirtualAccountStatusValue.Error.Unavailable.left()
|
||||
}
|
||||
val token = tangemPayCurrencyFactory.createVirtualAccountToken(userWalletId)
|
||||
|
||||
singleNetworkStatusFetcher(
|
||||
SingleNetworkStatusFetcher.Params(
|
||||
userWalletId = userWalletId,
|
||||
network = network,
|
||||
extraTokens = setOf(token),
|
||||
),
|
||||
)
|
||||
|
||||
val verifiedStatus = singleNetworkStatusSupplier
|
||||
.getSyncOrNull(SingleNetworkStatusProducer.Params(userWalletId, network))
|
||||
?.value as? NetworkStatus.Verified
|
||||
val balance = (verifiedStatus?.amounts?.get(token.id) as? NetworkStatus.Amount.Loaded)?.value
|
||||
|
||||
return if (balance != null) {
|
||||
TangemLogger.withTag(TAG).d("VA on-chain balance = $balance")
|
||||
balance.right()
|
||||
} else {
|
||||
TangemLogger.withTag(TAG).d("Can not get VA balance")
|
||||
VirtualAccountStatusValue.Error.Unavailable.left()
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val TAG = "VirtualAccountStatusFetcher"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
package com.tangem.data.virtualaccount.flow
|
||||
|
||||
import arrow.core.right
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.data.common.network.NetworkFactory
|
||||
import com.tangem.data.virtualaccount.store.VirtualAccountStatusesStore
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusFetcher
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||
import com.tangem.domain.pay.TangemPayCurrencyFactory
|
||||
import com.tangem.domain.virtualaccount.flow.VirtualAccountStatusFetcher
|
||||
import com.tangem.domain.wallets.extension.hasDerivation
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.mockkStatic
|
||||
import io.mockk.unmockkStatic
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
private const val USER_WALLET_EXTENSIONS = "com.tangem.domain.wallets.extension.UserWalletExtensionsKt"
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class DefaultVirtualAccountStatusFetcherTest {
|
||||
|
||||
private val virtualAccountStatusesStore: VirtualAccountStatusesStore = mockk(relaxed = true)
|
||||
private val dispatchers = TestingCoroutineDispatcherProvider()
|
||||
private val networkFactory: NetworkFactory = mockk()
|
||||
private val tangemPayCurrencyFactory: TangemPayCurrencyFactory = mockk()
|
||||
private val userWalletsListRepository: UserWalletsListRepository = mockk()
|
||||
private val singleNetworkStatusFetcher: SingleNetworkStatusFetcher = mockk()
|
||||
private val singleNetworkStatusSupplier: SingleNetworkStatusSupplier = mockk(relaxed = true)
|
||||
|
||||
private val fetcher = DefaultVirtualAccountStatusFetcher(
|
||||
virtualAccountStatusesStore = virtualAccountStatusesStore,
|
||||
dispatchers = dispatchers,
|
||||
networkFactory = networkFactory,
|
||||
tangemPayCurrencyFactory = tangemPayCurrencyFactory,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
singleNetworkStatusFetcher = singleNetworkStatusFetcher,
|
||||
singleNetworkStatusSupplier = singleNetworkStatusSupplier,
|
||||
)
|
||||
|
||||
private val userWalletId = UserWalletId("011")
|
||||
private val userWallet: UserWallet = mockk { every { walletId } returns userWalletId }
|
||||
private val network: Network = mockk()
|
||||
private val token: CryptoCurrency.Token = mockk()
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
mockkStatic(USER_WALLET_EXTENSIONS)
|
||||
clearMocks(networkFactory, tangemPayCurrencyFactory, singleNetworkStatusFetcher, userWalletsListRepository)
|
||||
every { userWalletsListRepository.userWallets } returns MutableStateFlow(listOf(userWallet))
|
||||
every {
|
||||
networkFactory.create(any<Blockchain>(), any<Network.DerivationPath>(), any<UserWallet>())
|
||||
} returns network
|
||||
every { tangemPayCurrencyFactory.createVirtualAccountToken(userWalletId) } returns token
|
||||
coEvery { singleNetworkStatusFetcher(any()) } returns Unit.right()
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
unmockkStatic(USER_WALLET_EXTENSIONS)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN VA derivation missing WHEN invoke THEN on-chain fetch skipped`() = runTest {
|
||||
// Arrange
|
||||
every { userWallet.hasDerivation(any(), any()) } returns false
|
||||
|
||||
// Act
|
||||
fetcher.invoke(VirtualAccountStatusFetcher.Params(userWalletId))
|
||||
|
||||
// Assert
|
||||
coVerify(exactly = 0) { singleNetworkStatusFetcher(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN VA derivation present WHEN invoke THEN on-chain fetch performed`() = runTest {
|
||||
// Arrange
|
||||
every { userWallet.hasDerivation(any(), any()) } returns true
|
||||
|
||||
// Act
|
||||
fetcher.invoke(VirtualAccountStatusFetcher.Params(userWalletId))
|
||||
|
||||
// Assert
|
||||
coVerify(exactly = 1) { singleNetworkStatusFetcher(any()) }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue