diff --git a/app/src/main/java/org/p2p/wallet/bridge/send/ui/BridgeSendPresenter.kt b/app/src/main/java/org/p2p/wallet/bridge/send/ui/BridgeSendPresenter.kt index 74d966c3a8..8ff87bb73b 100644 --- a/app/src/main/java/org/p2p/wallet/bridge/send/ui/BridgeSendPresenter.kt +++ b/app/src/main/java/org/p2p/wallet/bridge/send/ui/BridgeSendPresenter.kt @@ -11,6 +11,7 @@ import org.p2p.core.common.TextContainer import org.p2p.core.common.di.AppScope import org.p2p.core.model.CurrencyMode import org.p2p.core.token.Token +import org.p2p.core.utils.SOL_DECIMALS import org.p2p.core.utils.asNegativeUsdTransaction import org.p2p.core.utils.asUsd import org.p2p.core.utils.formatTokenWithSymbol @@ -300,7 +301,7 @@ class BridgeSendPresenter( } is CurrencyMode.Token -> { val tokenSymbol = mode.symbol - amountPlusTotalFee.formatTokenWithSymbol(tokenSymbol) + amountPlusTotalFee.formatTokenWithSymbol(tokenSymbol, SOL_DECIMALS) } else -> { emptyString() diff --git a/app/src/main/java/org/p2p/wallet/deeplinks/AppDeeplinksManager.kt b/app/src/main/java/org/p2p/wallet/deeplinks/AppDeeplinksManager.kt index a514abdd50..f4b6b2250a 100644 --- a/app/src/main/java/org/p2p/wallet/deeplinks/AppDeeplinksManager.kt +++ b/app/src/main/java/org/p2p/wallet/deeplinks/AppDeeplinksManager.kt @@ -12,6 +12,7 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.onCompletion import org.p2p.wallet.R +import org.p2p.wallet.common.feature_toggles.toggles.remote.SendViaLinkFeatureToggle import org.p2p.wallet.intercom.IntercomDeeplinkManager import org.p2p.wallet.notification.NotificationType import org.p2p.wallet.root.RootActivity @@ -25,7 +26,8 @@ class AppDeeplinksManager( private val context: Context, private val swapDeeplinkHandler: SwapDeeplinkHandler, private val referralDeeplinkHandler: ReferralDeeplinkHandler, - private val intercomDeeplinkManager: IntercomDeeplinkManager + private val intercomDeeplinkManager: IntercomDeeplinkManager, + private val sendViaLinkFeatureToggle: SendViaLinkFeatureToggle ) { companion object { const val NOTIFICATION_TYPE = "eventType" @@ -114,6 +116,8 @@ class AppDeeplinksManager( * or keyapp://t/... if came from website */ private fun isTransferDeeplink(data: Uri): Boolean { + if (!sendViaLinkFeatureToggle.isFeatureEnabled) return false + val transferHostMain = context.getString(R.string.transfer_app_host) val transferSchemeMain = "https" val transferHostAlternative = "t" diff --git a/app/src/main/java/org/p2p/wallet/history/ui/detailsbottomsheet/HistoryTransactionDetailsBottomSheetFragment.kt b/app/src/main/java/org/p2p/wallet/history/ui/detailsbottomsheet/HistoryTransactionDetailsBottomSheetFragment.kt index a065945d88..caf8359f76 100644 --- a/app/src/main/java/org/p2p/wallet/history/ui/detailsbottomsheet/HistoryTransactionDetailsBottomSheetFragment.kt +++ b/app/src/main/java/org/p2p/wallet/history/ui/detailsbottomsheet/HistoryTransactionDetailsBottomSheetFragment.kt @@ -16,7 +16,7 @@ import com.google.android.material.snackbar.Snackbar import org.koin.android.ext.android.inject import org.p2p.core.crypto.Base58String import org.p2p.core.glide.GlideManager -import org.p2p.core.utils.DEFAULT_DECIMAL +import org.p2p.core.utils.SOL_DECIMALS import org.p2p.core.utils.formatFiat import org.p2p.core.utils.formatToken import org.p2p.core.utils.lessThenMinValue @@ -324,7 +324,7 @@ class HistoryTransactionDetailsBottomSheetFragment : val highlightedText = "($formattedUsdAmount)" val commonText = buildString { - append(feeAmount.totalInTokens.formatToken(feeAmount.tokensDecimals ?: DEFAULT_DECIMAL)) + append(feeAmount.totalInTokens.formatToken(feeAmount.tokensDecimals ?: SOL_DECIMALS)) appendWhitespace() append(feeAmount.tokenSymbol.orEmpty()) appendWhitespace() diff --git a/app/src/main/java/org/p2p/wallet/jupiter/JupiterSwapModule.kt b/app/src/main/java/org/p2p/wallet/jupiter/JupiterSwapModule.kt index cbd0948db0..a315e80597 100644 --- a/app/src/main/java/org/p2p/wallet/jupiter/JupiterSwapModule.kt +++ b/app/src/main/java/org/p2p/wallet/jupiter/JupiterSwapModule.kt @@ -51,7 +51,6 @@ import org.p2p.wallet.jupiter.statemanager.token_selector.SwapInitialTokenSelect import org.p2p.wallet.jupiter.statemanager.token_selector.SwapInitialTokensData import org.p2p.wallet.jupiter.statemanager.validator.SwapMinimumSolAmountValidator import org.p2p.wallet.jupiter.statemanager.validator.SwapValidator -import org.p2p.wallet.jupiter.ui.info.SwapInfoMapper import org.p2p.wallet.jupiter.ui.main.JupiterSwapContract import org.p2p.wallet.jupiter.ui.main.JupiterSwapPresenter import org.p2p.wallet.jupiter.ui.main.SwapShareDeeplinkBuilder @@ -230,7 +229,6 @@ object JupiterSwapModule : InjectionModule { } factoryOf(::SwapCommonSettingsMapper) - factoryOf(::SwapInfoMapper) factoryOf(::SwapSelectRoutesMapper) factoryOf(::SwapEmptySettingsMapper) factoryOf(::SwapLoadingSettingsMapper) diff --git a/app/src/main/java/org/p2p/wallet/jupiter/repository/model/JupiterSwapRouteV6.kt b/app/src/main/java/org/p2p/wallet/jupiter/repository/model/JupiterSwapRouteV6.kt index 0d97d7835b..2543f033f9 100644 --- a/app/src/main/java/org/p2p/wallet/jupiter/repository/model/JupiterSwapRouteV6.kt +++ b/app/src/main/java/org/p2p/wallet/jupiter/repository/model/JupiterSwapRouteV6.kt @@ -9,6 +9,8 @@ import org.p2p.core.crypto.Base58String data class JupiterSwapRouteV6( val inAmountInLamports: BigInteger, val outAmountInLamports: BigInteger, + val inTokenMint: Base58String, + val outTokenMint: Base58String, val priceImpactPercent: BigDecimal, @IntRange(from = 0, to = 10000) val slippageBps: Int, diff --git a/app/src/main/java/org/p2p/wallet/jupiter/repository/v6/JupiterSwapRoutesRepositoryV6Mapper.kt b/app/src/main/java/org/p2p/wallet/jupiter/repository/v6/JupiterSwapRoutesRepositoryV6Mapper.kt index aeca028187..debcce27de 100644 --- a/app/src/main/java/org/p2p/wallet/jupiter/repository/v6/JupiterSwapRoutesRepositoryV6Mapper.kt +++ b/app/src/main/java/org/p2p/wallet/jupiter/repository/v6/JupiterSwapRoutesRepositoryV6Mapper.kt @@ -57,13 +57,15 @@ class JupiterSwapRoutesRepositoryV6Mapper( return JupiterSwapRouteV6( inAmountInLamports = response.inAmount.toBigInteger(), outAmountInLamports = response.outAmount.toBigInteger(), + inTokenMint = response.inputMint.toBase58Instance(), + outTokenMint = response.outputMint.toBase58Instance(), priceImpactPercent = response.priceImpactPct.toBigDecimal(), slippageBps = response.slippageBps, otherAmountThreshold = response.otherAmountThreshold, swapMode = response.swapMode, originalRoute = gson.toJsonObject(response), routePlans = plans, - fees = fees + fees = fees, ) } diff --git a/app/src/main/java/org/p2p/wallet/jupiter/statemanager/rate/SwapRateTickerManager.kt b/app/src/main/java/org/p2p/wallet/jupiter/statemanager/rate/SwapRateTickerManager.kt index 9df1ffc223..21455683cf 100644 --- a/app/src/main/java/org/p2p/wallet/jupiter/statemanager/rate/SwapRateTickerManager.kt +++ b/app/src/main/java/org/p2p/wallet/jupiter/statemanager/rate/SwapRateTickerManager.kt @@ -131,7 +131,7 @@ class SwapRateTickerManager( private suspend fun findJupiterTokenRate(to: SwapTokenModel.JupiterToken): BigDecimal? { val tokenData = userLocalRepository.findTokenData(to.mintAddress.base58Value) ?: return null - val cachedPrice = tokenServiceRepository.findTokenPriceByAddress( + val cachedPrice = tokenServiceRepository.getTokenPriceByAddress( tokenAddress = tokenData.mintAddress, networkChain = TokenServiceNetwork.SOLANA ) diff --git a/app/src/main/java/org/p2p/wallet/jupiter/ui/info/SwapFeeInfoBottomSheet.kt b/app/src/main/java/org/p2p/wallet/jupiter/ui/info/SwapFeeInfoBottomSheet.kt index fa0b96b8ca..53a9dd1b91 100644 --- a/app/src/main/java/org/p2p/wallet/jupiter/ui/info/SwapFeeInfoBottomSheet.kt +++ b/app/src/main/java/org/p2p/wallet/jupiter/ui/info/SwapFeeInfoBottomSheet.kt @@ -8,20 +8,17 @@ import android.os.Bundle import android.view.View import com.hannesdorfmann.adapterdelegates4.AdapterDelegate import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding +import org.koin.android.ext.android.get import org.koin.android.ext.android.inject -import org.koin.core.parameter.parametersOf +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.p2p.core.dispatchers.CoroutineDispatchers -import org.p2p.uikit.components.finance_block.MainCellModel import org.p2p.uikit.components.finance_block.baseCellDelegate import org.p2p.uikit.components.info_block.InfoBlockCellModel import org.p2p.uikit.model.AnyCellItem @@ -31,14 +28,9 @@ import org.p2p.wallet.common.adapter.CommonAnyCellAdapter import org.p2p.wallet.common.ui.bottomsheet.BaseBottomSheet import org.p2p.wallet.databinding.DialogSwapInfoBinding import org.p2p.wallet.databinding.ItemSwapInfoBannerBinding -import org.p2p.wallet.jupiter.interactor.SwapTokensInteractor -import org.p2p.wallet.jupiter.interactor.model.SwapTokenModel -import org.p2p.wallet.jupiter.repository.model.JupiterSwapRoutePlanV6 -import org.p2p.wallet.jupiter.repository.tokens.JupiterSwapTokensRepository import org.p2p.wallet.jupiter.statemanager.SwapState import org.p2p.wallet.jupiter.statemanager.SwapStateManager import org.p2p.wallet.jupiter.statemanager.SwapStateManagerHolder -import org.p2p.wallet.jupiter.ui.main.SwapRateLoaderState import org.p2p.wallet.utils.args import org.p2p.wallet.utils.viewbinding.viewBinding import org.p2p.wallet.utils.withArgs @@ -55,8 +47,6 @@ enum class SwapInfoType { TOKEN_2022_TRANSFER } -private typealias LoadRateBox = Triple - private fun swapInfoBannerDelegate(): AdapterDelegate> = adapterDelegateViewBinding( viewBinding = { inflater, parent -> ItemSwapInfoBannerBinding.inflate(inflater, parent, false) } @@ -96,13 +86,12 @@ class SwapFeeInfoBottomSheet : BaseBottomSheet(R.layout.dialog_swap_info) { private val swapInfoType: SwapInfoType by args(ARG_INFO_TYPE_KEY) private val managerHolder: SwapStateManagerHolder by inject() - private val mapper: SwapInfoMapper by inject() - private val interactor: SwapTokensInteractor by inject { - parametersOf(stateManagerKey) + private val mapper = SwapInfoMapper() + private val liquidityFeeMapper: SwapInfoLiquidityFeeMapper by lazy { + SwapInfoLiquidityFeeMapper(swapTokensRepository = get(), tokenServiceRepository = get()) } - private val swapTokensRepository: JupiterSwapTokensRepository by inject() - private val coroutineDispatchers: CoroutineDispatchers by inject() + private val dispatchers: CoroutineDispatchers by inject() private val stateManager: SwapStateManager get() = managerHolder.get(stateManagerKey) @@ -144,82 +133,39 @@ class SwapFeeInfoBottomSheet : BaseBottomSheet(R.layout.dialog_swap_info) { setExpanded(true) } + @OptIn(ExperimentalCoroutinesApi::class) private fun observeFeatureState() { stateManager.observe() - .flatMapLatest { handleFeatureState(it) } - .flowOn(coroutineDispatchers.io) - .onEach { adapter.items = it } + .flatMapLatest(transform = ::mapLiquidityCellsFlow) + .flowOn(dispatchers.io) + .onEach(adapter::setItems) .flowWithLifecycle(lifecycle) .launchIn(lifecycleScope) } - private suspend fun handleFeatureState(state: SwapState): Flow> { + private fun mapLiquidityCellsFlow(state: SwapState): Flow> { return when (state) { SwapState.InitialLoading, is SwapState.LoadingRoutes, is SwapState.TokenANotZero, is SwapState.TokenAZero -> { - flowOf(mapper.mapEmptyLiquidityFee()) + flowOf(liquidityFeeMapper.mapNoRouteLoaded()) } is SwapState.SwapException -> { - handleFeatureState(state.previousFeatureState) + mapLiquidityCellsFlow(state.previousFeatureState) } is SwapState.RoutesLoaded, + is SwapState.LoadingTransaction, is SwapState.SwapLoaded -> { val route = when (state) { is SwapState.RoutesLoaded -> state.route is SwapState.SwapLoaded -> state.route - else -> return flowOf(mapper.mapEmptyLiquidityFee()) - } - flow { - val rateLoaderList = mutableListOf>() - val feeCells = route.routePlans.map { plan -> - val loadingCell = mapper.getLiquidityFeeCell(plan) - rateLoaderList += getRateLoaderFlow(plan, loadingCell) - loadingCell - } - var fullUiList = mapper.mapEmptyLiquidityFee().plus(feeCells) - emit(fullUiList) - - rateLoaderList.merge() - .collect { - val routePlan = it.first - val loadingCell = it.second - val rateLoaderState = it.third - val indexOf = fullUiList.indexOf(loadingCell) - if (indexOf >= 0) { - val newCell = mapper.updateLiquidityFee( - marketInfo = routePlan, - oldCell = loadingCell, - state = rateLoaderState - ) - val newList = fullUiList.toMutableList().apply { set(indexOf, newCell) } - fullUiList = newList - emit(newList) - } - } - } - } - is SwapState.LoadingTransaction -> { - flow { - val fullUiList = mapper.mapLoadingLiquidityFee(state.route) - emit(fullUiList) - } - } - } - } + is SwapState.LoadingTransaction -> state.route + else -> null + } ?: return flowOf(liquidityFeeMapper.mapNoRouteLoaded()) - private suspend fun getRateLoaderFlow( - routePlan: JupiterSwapRoutePlanV6, - loadingCell: MainCellModel, - ): Flow { - val lpToken = swapTokensRepository.findTokenByMint(routePlan.feeMint) - val loadingCellFlow = flowOf(routePlan to loadingCell) - val rateLoaderFlow = lpToken - ?.let { stateManager.getTokenRate(SwapTokenModel.JupiterToken(it)) } - ?: flowOf(SwapRateLoaderState.Error) - return loadingCellFlow.combine(rateLoaderFlow) { a, b -> - Triple(a.first, a.second, b) + liquidityFeeMapper.mapLiquidityFees(route) + } } } diff --git a/app/src/main/java/org/p2p/wallet/jupiter/ui/info/SwapInfoLiquidityFeeMapper.kt b/app/src/main/java/org/p2p/wallet/jupiter/ui/info/SwapInfoLiquidityFeeMapper.kt new file mode 100644 index 0000000000..0e6c538dec --- /dev/null +++ b/app/src/main/java/org/p2p/wallet/jupiter/ui/info/SwapInfoLiquidityFeeMapper.kt @@ -0,0 +1,186 @@ +package org.p2p.wallet.jupiter.ui.info + +import androidx.annotation.DrawableRes +import java.math.BigDecimal +import java.math.BigInteger +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import org.p2p.core.common.DrawableContainer +import org.p2p.core.common.TextContainer +import org.p2p.core.crypto.Base58String +import org.p2p.core.utils.asUsdSwap +import org.p2p.core.utils.formatTokenWithSymbol +import org.p2p.core.utils.fromLamports +import org.p2p.token.service.model.TokenServiceNetwork +import org.p2p.token.service.repository.TokenServiceRepository +import org.p2p.uikit.components.finance_block.MainCellModel +import org.p2p.uikit.components.finance_block.MainCellStyle +import org.p2p.uikit.components.icon_wrapper.IconWrapperCellModel +import org.p2p.uikit.components.info_block.InfoBlockCellModel +import org.p2p.uikit.components.left_side.LeftSideCellModel +import org.p2p.uikit.components.right_side.RightSideCellModel +import org.p2p.uikit.model.AnyCellItem +import org.p2p.uikit.utils.drawable.DrawableCellModel +import org.p2p.uikit.utils.drawable.shape.shapeCircle +import org.p2p.uikit.utils.image.ImageViewCellModel +import org.p2p.uikit.utils.skeleton.SkeletonCellModel +import org.p2p.uikit.utils.text.TextViewCellModel +import org.p2p.uikit.utils.toPx +import org.p2p.wallet.R +import org.p2p.wallet.jupiter.repository.model.JupiterSwapRoutePlanV6 +import org.p2p.wallet.jupiter.repository.model.JupiterSwapRouteV6 +import org.p2p.wallet.jupiter.repository.tokens.JupiterSwapTokensRepository + +class SwapInfoLiquidityFeeMapper( + private val swapTokensRepository: JupiterSwapTokensRepository, + private val tokenServiceRepository: TokenServiceRepository +) { + + fun mapNoRouteLoaded(): List { + return listOf(createLiquidityFeeBanner()) + } + + fun mapLiquidityFees( + route: JupiterSwapRouteV6, + ): Flow> = flow { + val keyAppFee = route.fees.platformFeeTokenB + val tokenBMint = route.outTokenMint + + val cellModels = mutableListOf() + + cellModels += createLiquidityFeeBanner() + route.routePlans.forEach { routePlan -> + cellModels += mapLiquidityFeeCellLoadingRate(routePlan) + } + + val keyAppFeeCellModel = createKeyAppFeeCellNoRate(keyAppFee, tokenBMint) + if (keyAppFeeCellModel != null) { + cellModels += keyAppFeeCellModel + } + // emit info without rates + emit(cellModels) + + // another collection, we can't modify cellModels + val cellModelsWithRates = mutableListOf() + // start loading rates for liquidity fees + cellModelsWithRates += createLiquidityFeeBanner() + route.routePlans.forEach { routePlan -> + val feeUsd = getUsdAmountForTokenAmount(routePlan.feeAmount, routePlan.feeMint) + + cellModelsWithRates += mapLiquidityFeeCellLoadingRate(routePlan) + .copy(rightSideCellModel = feeUsd?.let(::createUsdRateCellModel)) + } + + val keyAppFeeUsd = getUsdAmountForTokenAmount(keyAppFee, tokenBMint) + val updatedKeyAppFeeCell = createKeyAppFeeCellNoRate(keyAppFee, tokenBMint)?.copy( + rightSideCellModel = keyAppFeeUsd?.let(::createUsdRateCellModel) + ) + if (updatedKeyAppFeeCell != null) { + cellModelsWithRates += updatedKeyAppFeeCell + } + // emit info with rates + emit(cellModelsWithRates) + } + + private suspend fun mapLiquidityFeeCellLoadingRate( + routePlan: JupiterSwapRoutePlanV6, + ): MainCellModel { + val label = routePlan.label + val liquidityToken = swapTokensRepository.findTokenByMint(routePlan.feeMint) + val liquidityFee = routePlan.feeAmount + + val feePercent = "${routePlan.percent}%" + val firstLineText = TextViewCellModel.Raw( + text = TextContainer(R.string.swap_info_details_liquidity_cell_title, label, feePercent), + maxLines = 2 + ) + + val secondLineText = liquidityToken?.let { + val feeInTokenLamports = liquidityFee + .fromLamports(it.decimals) + .formatTokenWithSymbol(it.tokenSymbol, it.decimals) + TextViewCellModel.Raw(text = TextContainer(feeInTokenLamports)) + } + + return MainCellModel( + styleType = MainCellStyle.BASE_CELL, + leftSideCellModel = LeftSideCellModel.IconWithText( + firstLineText = firstLineText, + secondLineText = secondLineText + ), + rightSideCellModel = rightSideSkeleton(), + ) + } + + private suspend fun createKeyAppFeeCellNoRate(keyAppFee: BigInteger, tokenBMint: Base58String): MainCellModel? { + val tokenB = swapTokensRepository.findTokenByMint(tokenBMint) ?: return null + val feeAmount = keyAppFee.fromLamports(tokenB.decimals) + .formatTokenWithSymbol(tokenB.tokenSymbol, tokenB.decimals) + + return MainCellModel( + leftSideCellModel = LeftSideCellModel.IconWithText( + firstLineText = TextViewCellModel.Raw( + TextContainer(R.string.swap_info_details_key_app_fee_title) + ), + secondLineText = TextViewCellModel.Raw( + TextContainer(feeAmount) + ) + ), + rightSideCellModel = rightSideSkeleton(), + styleType = MainCellStyle.BASE_CELL, + ) + } + + private fun createUsdRateCellModel( + usdAmount: BigDecimal + ): RightSideCellModel { + return RightSideCellModel.SingleTextTwoIcon( + text = TextViewCellModel.Raw(TextContainer(usdAmount.asUsdSwap())) + ) + } + + private fun rightSideSkeleton(): RightSideCellModel.SingleTextTwoIcon = RightSideCellModel.SingleTextTwoIcon( + text = TextViewCellModel.Skeleton( + skeleton = SkeletonCellModel( + height = 20.toPx(), + width = 60.toPx(), + radius = 5f.toPx(), + ), + ), + ) + + private fun createLiquidityFeeBanner(): AnyCellItem = SwapInfoBannerCellModel( + banner = R.drawable.ic_fee_banner, + infoCell = InfoBlockCellModel( + icon = getIcon(R.drawable.ic_lightning), + firstLineText = TextViewCellModel.Raw( + text = TextContainer(R.string.swap_info_details_liquidity_fee_title) + ), + secondLineText = TextViewCellModel.Raw( + text = TextContainer(R.string.swap_info_details_liquidity_fee_subtitle) + ) + ) + ) + + private fun getIcon(@DrawableRes icon: Int): IconWrapperCellModel.SingleIcon = IconWrapperCellModel.SingleIcon( + icon = ImageViewCellModel( + icon = DrawableContainer(icon), + iconTint = R.color.icons_mountain, + background = DrawableCellModel(tint = R.color.bg_smoke), + clippingShape = shapeCircle(), + ) + ) + + private suspend fun getUsdAmountForTokenAmount( + amount: BigInteger, + tokenMint: Base58String, + ): BigDecimal? { + val token = swapTokensRepository.findTokenByMint(tokenMint) ?: return null + val tokenRate = tokenServiceRepository.getTokenPriceByAddress( + tokenAddress = tokenMint.base58Value, + networkChain = TokenServiceNetwork.SOLANA + )?.usdRate ?: return null + + return amount.fromLamports(token.decimals) * tokenRate + } +} diff --git a/app/src/main/java/org/p2p/wallet/jupiter/ui/info/SwapInfoMapper.kt b/app/src/main/java/org/p2p/wallet/jupiter/ui/info/SwapInfoMapper.kt index 881860ebc6..12da7d5864 100644 --- a/app/src/main/java/org/p2p/wallet/jupiter/ui/info/SwapInfoMapper.kt +++ b/app/src/main/java/org/p2p/wallet/jupiter/ui/info/SwapInfoMapper.kt @@ -3,33 +3,19 @@ package org.p2p.wallet.jupiter.ui.info import androidx.annotation.DrawableRes import org.p2p.core.common.DrawableContainer import org.p2p.core.common.TextContainer -import org.p2p.core.utils.asUsdSwap -import org.p2p.core.utils.formatTokenWithSymbol -import org.p2p.core.utils.fromLamports -import org.p2p.uikit.components.finance_block.MainCellModel -import org.p2p.uikit.components.finance_block.MainCellStyle import org.p2p.uikit.components.icon_wrapper.IconWrapperCellModel import org.p2p.uikit.components.info_block.InfoBlockCellModel -import org.p2p.uikit.components.left_side.LeftSideCellModel -import org.p2p.uikit.components.right_side.RightSideCellModel import org.p2p.uikit.model.AnyCellItem import org.p2p.uikit.utils.drawable.DrawableCellModel import org.p2p.uikit.utils.drawable.shape.shapeCircle import org.p2p.uikit.utils.drawable.shape.shapeRoundedAll import org.p2p.uikit.utils.drawable.shapeDrawable import org.p2p.uikit.utils.image.ImageViewCellModel -import org.p2p.uikit.utils.skeleton.SkeletonCellModel import org.p2p.uikit.utils.text.TextViewCellModel import org.p2p.uikit.utils.toPx import org.p2p.wallet.R -import org.p2p.wallet.jupiter.repository.model.JupiterSwapRoutePlanV6 -import org.p2p.wallet.jupiter.repository.model.JupiterSwapRouteV6 -import org.p2p.wallet.jupiter.repository.tokens.JupiterSwapTokensRepository -import org.p2p.wallet.jupiter.ui.main.SwapRateLoaderState -class SwapInfoMapper( - private val swapTokensRepository: JupiterSwapTokensRepository -) { +class SwapInfoMapper { fun mapNetworkFee(): List = buildList { this += SwapInfoBannerCellModel( @@ -87,73 +73,6 @@ class SwapInfoMapper( ) } - suspend fun mapLoadingLiquidityFee( - route: JupiterSwapRouteV6? = null, - ): List = buildList { - addAll(mapEmptyLiquidityFee()) - if (route == null) return@buildList - - route.routePlans.forEach { routePlan -> - this += getLiquidityFeeCell(routePlan) - } - } - - suspend fun getLiquidityFeeCell( - routePlan: JupiterSwapRoutePlanV6, - ): MainCellModel { - val label = routePlan.label - val liquidityToken = swapTokensRepository.findTokenByMint(routePlan.feeMint) - val liquidityFee = routePlan.feeAmount - - val feePercent = routePlan.percent + "%" - val firstLineText = TextViewCellModel.Raw( - text = TextContainer(R.string.swap_info_details_liquidity_cell_title, label, feePercent), - maxLines = 2 - ) - - val secondLineText = liquidityToken?.let { - val feeInTokenLamports = liquidityFee.fromLamports(it.decimals) - .formatTokenWithSymbol(it.tokenSymbol, it.decimals) - TextViewCellModel.Raw( - text = TextContainer(feeInTokenLamports) - ) - } - - return MainCellModel( - styleType = MainCellStyle.BASE_CELL, - leftSideCellModel = LeftSideCellModel.IconWithText( - firstLineText = firstLineText, - secondLineText = secondLineText - ), - rightSideCellModel = rightSideSkeleton(), - ) - } - - private fun rightSideSkeleton(): RightSideCellModel.SingleTextTwoIcon = RightSideCellModel.SingleTextTwoIcon( - text = TextViewCellModel.Skeleton( - skeleton = SkeletonCellModel( - height = 20.toPx(), - width = 60.toPx(), - radius = 5f.toPx(), - ), - ), - ) - - fun mapEmptyLiquidityFee(): List = buildList { - this += SwapInfoBannerCellModel( - banner = R.drawable.ic_fee_banner, - infoCell = InfoBlockCellModel( - icon = getIcon(R.drawable.ic_lightning), - firstLineText = TextViewCellModel.Raw( - text = TextContainer(R.string.swap_info_details_liquidity_fee_title) - ), - secondLineText = TextViewCellModel.Raw( - text = TextContainer(R.string.swap_info_details_liquidity_fee_subtitle) - ) - ) - ) - } - fun mapMinimumReceived(): List = buildList { this += SwapInfoBannerCellModel( banner = R.drawable.ic_about_earn_3, @@ -188,37 +107,4 @@ class SwapInfoMapper( clippingShape = shapeCircle(), ) ) - - fun updateLiquidityFee( - marketInfo: JupiterSwapRoutePlanV6, - oldCell: MainCellModel, - state: SwapRateLoaderState - ): MainCellModel { - return when (state) { - SwapRateLoaderState.Empty -> { - oldCell - } - is SwapRateLoaderState.NoRateAvailable, is SwapRateLoaderState.Error -> { - oldCell.copy(rightSideCellModel = null) - } - SwapRateLoaderState.Loading -> { - oldCell.copy(rightSideCellModel = rightSideSkeleton()) - } - is SwapRateLoaderState.Loaded -> { - val rate = state.rate - val token = state.token - val feeInUsd = marketInfo.feeAmount - .fromLamports(token.decimals) - .multiply(rate) - .asUsdSwap() - oldCell.copy( - rightSideCellModel = RightSideCellModel.SingleTextTwoIcon( - text = TextViewCellModel.Raw( - text = TextContainer(feeInUsd) - ), - ) - ) - } - } - } } diff --git a/app/src/main/java/org/p2p/wallet/jupiter/ui/main/JupiterSwapPresenter.kt b/app/src/main/java/org/p2p/wallet/jupiter/ui/main/JupiterSwapPresenter.kt index 33579699d7..d48807656a 100644 --- a/app/src/main/java/org/p2p/wallet/jupiter/ui/main/JupiterSwapPresenter.kt +++ b/app/src/main/java/org/p2p/wallet/jupiter/ui/main/JupiterSwapPresenter.kt @@ -21,6 +21,7 @@ import org.p2p.core.common.TextContainer import org.p2p.core.dispatchers.CoroutineDispatchers import org.p2p.core.network.data.ServerException import org.p2p.core.utils.asUsd +import org.p2p.core.utils.emptyString import org.p2p.core.utils.formatToken import org.p2p.core.utils.isZero import org.p2p.core.utils.toBigDecimalOrZero @@ -517,6 +518,7 @@ class JupiterSwapPresenter( private suspend fun handleLoadingRoutes(state: SwapState.LoadingRoutes) { rateTickerManager.handleRoutesLoading(state) + showRoutesForDebug(bestRoute = null, slippage = state.slippage) mapWidgetStates(state) updateWidgets() @@ -684,7 +686,15 @@ class JupiterSwapPresenter( view?.setSecondTokenWidgetState(state = widgetBState) } - private fun showRoutesForDebug(bestRoute: JupiterSwapRouteV6, slippage: Slippage) { + private fun showRoutesForDebug( + bestRoute: JupiterSwapRouteV6?, + slippage: Slippage + ) { + if (bestRoute == null) { + view?.showDebugInfo(TextViewCellModel.Raw(TextContainer(emptyString()))) + return + } + val info = buildString { append("Route: ") @@ -708,10 +718,13 @@ class JupiterSwapPresenter( } appendLine() + appendLine("Slippage: ${slippage.percentValue}") appendLine() - append("Slippage: ${slippage.percentValue}") - appendLine() - append("KeyApp fee: NONE for v6") + val keyApp = bestRoute.originalRoute.getAsJsonObject("keyapp") + appendLine("fee: ${keyApp.get("fee")}") + appendLine("fees: ${keyApp.getAsJsonObject("fees")}") + appendLine("KeyApp fee lamports: ${bestRoute.fees.platformFeeTokenB}") + append("KeyApp fee %: ${bestRoute.fees.platformFeePercent}") } view?.showDebugInfo( diff --git a/app/src/main/java/org/p2p/wallet/jupiter/ui/main/SwapTokenRateLoader.kt b/app/src/main/java/org/p2p/wallet/jupiter/ui/main/SwapTokenRateLoader.kt index ab1949b73e..6963c11da1 100644 --- a/app/src/main/java/org/p2p/wallet/jupiter/ui/main/SwapTokenRateLoader.kt +++ b/app/src/main/java/org/p2p/wallet/jupiter/ui/main/SwapTokenRateLoader.kt @@ -66,7 +66,7 @@ class SwapTokenRateLoader( ) { emitAndSaveState(SwapRateLoaderState.Loading) try { - val tokenPrice = tokenServiceRepository.findTokenPriceByAddress( + val tokenPrice = tokenServiceRepository.getTokenPriceByAddress( tokenAddress = token.details.tokenMint.base58Value, networkChain = TokenServiceNetwork.SOLANA )?.usdRate diff --git a/app/src/main/java/org/p2p/wallet/jupiter/ui/main/widget/SwapWidget.kt b/app/src/main/java/org/p2p/wallet/jupiter/ui/main/widget/SwapWidget.kt index 63c9918dde..193f0e6abc 100644 --- a/app/src/main/java/org/p2p/wallet/jupiter/ui/main/widget/SwapWidget.kt +++ b/app/src/main/java/org/p2p/wallet/jupiter/ui/main/widget/SwapWidget.kt @@ -17,7 +17,7 @@ import org.koin.core.component.KoinComponent import org.koin.core.component.inject import org.p2p.core.glide.GlideManager import org.p2p.core.textwatcher.AmountFractionTextWatcher -import org.p2p.core.utils.DEFAULT_DECIMAL +import org.p2p.core.utils.SOL_DECIMALS import org.p2p.core.utils.emptyString import org.p2p.core.utils.hideKeyboard import org.p2p.uikit.utils.drawable.shape.shapeRounded16dp @@ -156,7 +156,7 @@ class SwapWidget @JvmOverloads constructor( val readOnly = model.isStatic || newAmount is TextViewCellModel.Skeleton val inputType = if (readOnly) InputType.TYPE_NULL else initInputType editTextAmount.setReadOnly(readOnly, inputType) - val amountMaxDecimals = model.amountMaxDecimals ?: DEFAULT_DECIMAL + val amountMaxDecimals = model.amountMaxDecimals ?: SOL_DECIMALS updateFormatter(amountMaxDecimals) val oldAmountRaw = editTextAmount.text?.toString() ?: emptyString() val newAmountRaw = (newAmount as? TextViewCellModel.Raw)?.text?.getString(context) diff --git a/app/src/main/java/org/p2p/wallet/jupiter/ui/settings/presenter/SwapContentSettingsMapper.kt b/app/src/main/java/org/p2p/wallet/jupiter/ui/settings/presenter/SwapContentSettingsMapper.kt index cecdfe31f0..c325136486 100644 --- a/app/src/main/java/org/p2p/wallet/jupiter/ui/settings/presenter/SwapContentSettingsMapper.kt +++ b/app/src/main/java/org/p2p/wallet/jupiter/ui/settings/presenter/SwapContentSettingsMapper.kt @@ -118,22 +118,14 @@ class SwapContentSettingsMapper( this += token2022Fee.cellModel } - val liquidityFee = swapFeeBuilder.buildLiquidityFeeCell(route) - if (liquidityFee != null) { - this += liquidityFee.cellModel - } - - val keyAppFee = swapFeeBuilder.buildKeyAppFee(route, tokenB) - if (keyAppFee != null) { - this += keyAppFee.cellModel - } + val liquidityFee = swapFeeBuilder.buildLiquidityFeeCell(route, tokenB) + this += liquidityFee.cellModel val estimatedFee = swapFeeBuilder.buildEstimatedFeeString( networkFees = networkFeeCell, accountFee = accountFee, liquidityFees = liquidityFee, token2022Fee = token2022Fee, - keyAppFee = keyAppFee ) if (estimatedFee != null) { this += estimatedFee diff --git a/app/src/main/java/org/p2p/wallet/jupiter/ui/settings/presenter/SwapFeeCellsBuilder.kt b/app/src/main/java/org/p2p/wallet/jupiter/ui/settings/presenter/SwapFeeCellsBuilder.kt index 0c46f4f18d..a43a0a2351 100644 --- a/app/src/main/java/org/p2p/wallet/jupiter/ui/settings/presenter/SwapFeeCellsBuilder.kt +++ b/app/src/main/java/org/p2p/wallet/jupiter/ui/settings/presenter/SwapFeeCellsBuilder.kt @@ -2,22 +2,21 @@ package org.p2p.wallet.jupiter.ui.settings.presenter import timber.log.Timber import java.math.BigDecimal -import kotlinx.coroutines.async -import kotlinx.coroutines.awaitAll -import kotlinx.coroutines.flow.filterIsInstance -import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.flow.onEach +import java.math.BigInteger import kotlinx.coroutines.supervisorScope import org.p2p.core.common.DrawableContainer import org.p2p.core.common.TextContainer +import org.p2p.core.crypto.Base58String import org.p2p.core.utils.asUsdSwap import org.p2p.core.utils.divideSafe -import org.p2p.core.utils.formatFiat -import org.p2p.core.utils.formatToken import org.p2p.core.utils.formatTokenWithSymbol import org.p2p.core.utils.fromLamports +import org.p2p.core.utils.isNotZero import org.p2p.core.utils.isZero import org.p2p.core.utils.orZero +import org.p2p.token.service.model.TokenServiceNetwork +import org.p2p.token.service.model.TokenServicePrice +import org.p2p.token.service.repository.TokenServiceRepository import org.p2p.uikit.components.finance_block.MainCellModel import org.p2p.uikit.components.finance_block.MainCellStyle import org.p2p.uikit.components.left_side.LeftSideCellModel @@ -26,11 +25,10 @@ import org.p2p.uikit.utils.image.ImageViewCellModel import org.p2p.uikit.utils.text.TextViewCellModel import org.p2p.wallet.R import org.p2p.wallet.jupiter.interactor.model.SwapTokenModel +import org.p2p.wallet.jupiter.repository.model.JupiterSwapRoutePlanV6 import org.p2p.wallet.jupiter.repository.model.JupiterSwapRouteV6 import org.p2p.wallet.jupiter.repository.model.JupiterSwapToken import org.p2p.wallet.jupiter.repository.tokens.JupiterSwapTokensRepository -import org.p2p.wallet.jupiter.ui.main.SwapRateLoaderState -import org.p2p.wallet.jupiter.ui.main.SwapTokenRateLoader class SwapSettingsFeeBox( val cellModel: MainCellModel, @@ -38,20 +36,24 @@ class SwapSettingsFeeBox( ) class SwapFeeCellsBuilder( - private val rateLoader: SwapTokenRateLoader, + private val tokenServiceRepository: TokenServiceRepository, private val swapTokensRepository: JupiterSwapTokensRepository ) { + private class SwapFeeBuildFailed( + message: String, + cause: Throwable? = null + ) : Exception(message, cause) + suspend fun buildNetworkFeeCell( activeRoute: JupiterSwapRouteV6?, solToken: JupiterSwapToken, ): SwapSettingsFeeBox { - if (activeRoute != null) { - val networkFee = activeRoute.fees.signatureFee.fromLamports(solToken.decimals) - val solTokenRate: BigDecimal? = loadRateForToken(SwapTokenModel.JupiterToken(solToken))?.rate - val feeUsd: BigDecimal? = solTokenRate?.let { networkFee.multiply(it) } - // amount is not used, network fee is free right now - } + // amount is not used, network fee is free right now +// val networkFee = activeRoute.fees.signatureFee.fromLamports(solToken.decimals) +// val solTokenRate: BigDecimal? = loadRateForToken(SwapTokenModel.JupiterToken(solToken))?.rate +// val feeUsd: BigDecimal? = solTokenRate?.let { networkFee.multiply(it) } + val cellModel = MainCellModel( leftSideCellModel = LeftSideCellModel.IconWithText( firstLineText = TextViewCellModel.Raw( @@ -92,12 +94,12 @@ class SwapFeeCellsBuilder( return null } - val solRate = loadRateForToken(SwapTokenModel.JupiterToken(solToken))?.rate ?: kotlin.run { - Timber.e(IllegalStateException("Sol rate is null")) + val solRate = loadRateForToken(solToken.tokenMint)?.usdRate ?: kotlin.run { + Timber.e(SwapFeeBuildFailed("Sol rate is null")) return null } - val tokenBRate = loadRateForToken(tokenB)?.rate ?: kotlin.run { - Timber.e(IllegalStateException("Token B (${tokenB.mintAddress} rate is null")) + val tokenBRate = loadRateForToken(tokenB.mintAddress)?.usdRate ?: kotlin.run { + Timber.e(SwapFeeBuildFailed("Token B (${tokenB.mintAddress} rate is null")) return null } val ataFeeInTokenB: BigDecimal = solRate.divideSafe(tokenBRate) * ataFeeInSol @@ -136,24 +138,20 @@ class SwapFeeCellsBuilder( suspend fun buildLiquidityFeeCell( activeRoute: JupiterSwapRouteV6, - ): SwapSettingsFeeBox? = supervisorScope { - val lpTokensRates = activeRoute.routePlans.map { routePlan -> - val lpToken = swapTokensRepository.findTokenByMint(routePlan.feeMint) ?: return@supervisorScope null - async { loadRateForToken(SwapTokenModel.JupiterToken(lpToken)) } - } - .awaitAll() - .filterNotNull() - - val feeAmountFormatted = formatLiquidityFeeString(activeRoute) - - val usdAmounts = activeRoute.routePlans.map { routePlan -> - val (lpToken, tokenRate) = lpTokensRates - .find { it.token.mintAddress == routePlan.feeMint } - ?: return@map null + tokenB: SwapTokenModel, + ): SwapSettingsFeeBox = supervisorScope { + val keyAppFee = activeRoute.fees.platformFeeTokenB - val amountLamports = routePlan.feeAmount.fromLamports(lpToken.decimals) - amountLamports * tokenRate - } + val feeAmountFormatted = formatLiquidityFeeString( + routePlans = activeRoute.routePlans, + keyAppFee = keyAppFee, + tokenB = tokenB + ) + val usdAmounts = getLiquidityFeeUsdRates( + routePlans = activeRoute.routePlans, + keyAppFee = keyAppFee, + tokenB = tokenB + ) val someAmountsUsdNotLoaded = usdAmounts.any { it == null } val liquidityFeeInUsd = if (someAmountsUsdNotLoaded) { @@ -186,72 +184,66 @@ class SwapFeeCellsBuilder( SwapSettingsFeeBox(cellModel, liquidityFeeInUsd) } - private suspend fun formatLiquidityFeeString(route: JupiterSwapRouteV6): String { + /** + * Append KeyApp fee to liquidity fee string + */ + private suspend fun formatLiquidityFeeString( + routePlans: List, + keyAppFee: BigInteger, + tokenB: SwapTokenModel + ): String { return buildString { - route.routePlans.forEachIndexed { index, routePlan -> - val lpFee = routePlan.feeAmount + routePlans.forEachIndexed { index, routePlan -> val lpToken = swapTokensRepository.findTokenByMint(routePlan.feeMint) ?: return@forEachIndexed + val lpFee = routePlan.feeAmount val feeAmount = lpFee .fromLamports(lpToken.decimals) - .formatToken(lpToken.decimals) - val fee = "$feeAmount ${lpToken.tokenSymbol}" - append(fee) - if (index != route.routePlans.lastIndex) { + .formatTokenWithSymbol(lpToken.tokenSymbol, lpToken.decimals) + append(feeAmount) + if (index != routePlans.lastIndex) { append(", ") } } + if (keyAppFee.isNotZero()) { + append(", ") + val feeAmount = keyAppFee + .fromLamports(tokenB.decimals) + .formatTokenWithSymbol(tokenB.tokenSymbol, tokenB.decimals) + append(feeAmount) + } } } - suspend fun buildKeyAppFee(activeRoute: JupiterSwapRouteV6, tokenB: SwapTokenModel): SwapSettingsFeeBox? { - val platformFee = activeRoute.fees.platformFeeTokenB - if (platformFee.isZero()) { - return null - } - - val tokenBRate = loadRateForToken(tokenB)?.rate - - val platformFeeUsd = activeRoute.fees.platformFeeTokenB - .fromLamports(tokenB.decimals) - .multiply(tokenBRate.orZero()) + private suspend fun getLiquidityFeeUsdRates( + routePlans: List, + keyAppFee: BigInteger, + tokenB: SwapTokenModel, + ): List { + val tokenBRate = loadRateForToken(tokenB.mintAddress)?.usdRate + val keyAppFeeUsd = tokenBRate?.let { it * keyAppFee.fromLamports(tokenB.decimals) } - val formattedPlatformFee = "${activeRoute.fees.platformFeePercent.formatFiat()} %" + return routePlans.map { routePlan -> + val lpToken = swapTokensRepository.findTokenByMint(routePlan.feeMint) ?: return@map null + val usdRate = loadRateForToken(lpToken.tokenMint)?.usdRate ?: return@map null + val amountLamports = routePlan.feeAmount.fromLamports(lpToken.decimals) - val cellModel = MainCellModel( - leftSideCellModel = LeftSideCellModel.IconWithText( - firstLineText = TextViewCellModel.Raw( - text = TextContainer("KeyApp Fee"), - ), - secondLineText = TextViewCellModel.Raw( - text = TextContainer(formattedPlatformFee) - ), - ), - rightSideCellModel = null, - styleType = MainCellStyle.BASE_CELL, - ) - return SwapSettingsFeeBox( - cellModel = cellModel, - feeInUsd = platformFeeUsd - ) + amountLamports * usdRate + } + .plus(keyAppFeeUsd) } fun buildEstimatedFeeString( networkFees: SwapSettingsFeeBox, accountFee: SwapSettingsFeeBox?, - liquidityFees: SwapSettingsFeeBox?, + liquidityFees: SwapSettingsFeeBox, token2022Fee: SwapSettingsFeeBox?, - keyAppFee: SwapSettingsFeeBox?, ): MainCellModel? { - if (liquidityFees?.feeInUsd == null) { - return null - } - - val totalFee: String = liquidityFees.feeInUsd + val totalFee = liquidityFees.feeInUsd.orZero() .plus(accountFee?.feeInUsd.orZero()) .plus(token2022Fee?.feeInUsd.orZero()) - .plus(keyAppFee?.feeInUsd.orZero()) - .asUsdSwap() + + if (totalFee.isZero()) return null return MainCellModel( leftSideCellModel = LeftSideCellModel.IconWithText( @@ -261,19 +253,21 @@ class SwapFeeCellsBuilder( ), ), rightSideCellModel = RightSideCellModel.SingleTextTwoIcon( - text = totalFee.let { - TextViewCellModel.Raw(text = TextContainer(it)) - }, + text = TextViewCellModel.Raw(text = TextContainer(totalFee.asUsdSwap())) ), payload = SwapSettingsPayload.ESTIMATED_FEE, styleType = MainCellStyle.BASE_CELL, ) } - private suspend fun loadRateForToken(token: SwapTokenModel): SwapRateLoaderState.Loaded? { - return rateLoader.getRate(token) - .onEach { Timber.i("JupiterSwapFeeBuilder getting rate for ${token.tokenSymbol}") } - .filterIsInstance() - .firstOrNull() + private suspend fun loadRateForToken(tokenMint: Base58String): TokenServicePrice? { + return kotlin.runCatching { + tokenServiceRepository.getTokenPriceByAddress( + tokenAddress = tokenMint.base58Value, + networkChain = TokenServiceNetwork.SOLANA + ) + } + .onFailure { Timber.e(SwapFeeBuildFailed("Failed to get rate for $tokenMint", it)) } + .getOrNull() } } diff --git a/app/src/main/java/org/p2p/wallet/jupiter/ui/tokens/presenter/SwapTokensCommonMapper.kt b/app/src/main/java/org/p2p/wallet/jupiter/ui/tokens/presenter/SwapTokensCommonMapper.kt index 24d96f00e1..24417726d3 100644 --- a/app/src/main/java/org/p2p/wallet/jupiter/ui/tokens/presenter/SwapTokensCommonMapper.kt +++ b/app/src/main/java/org/p2p/wallet/jupiter/ui/tokens/presenter/SwapTokensCommonMapper.kt @@ -82,6 +82,7 @@ class SwapTokensCommonMapper( tokenName = tokenName, tokenSymbol = tokenSymbol, tokenMint = mintAddress, + tokenDecimals = decimals, totalTokenAmount = total, totalTokenPriceInUsd = totalInUsd?.formatFiat(), payload = SwapTokensCellModelPayload( @@ -104,6 +105,7 @@ class SwapTokensCommonMapper( tokenName = tokenName, tokenSymbol = tokenSymbol, tokenMint = tokenMint.base58Value, + tokenDecimals = decimals, totalTokenAmount = null, totalTokenPriceInUsd = null, addPopularLabel = isPopularToken, @@ -121,6 +123,7 @@ class SwapTokensCommonMapper( tokenName: String, tokenSymbol: String, tokenMint: String, + tokenDecimals: Int, totalTokenAmount: BigDecimal?, totalTokenPriceInUsd: String?, payload: SwapTokensCellModelPayload, @@ -138,7 +141,12 @@ class SwapTokensCommonMapper( rightSideCellModel = if (addPopularLabel) { createPopularRightSideModel() } else { - createPriceRightSideModel(totalTokenAmount, tokenSymbol, totalTokenPriceInUsd) + createPriceRightSideModel( + totalTokenAmount = totalTokenAmount, + tokenSymbol = tokenSymbol, + tokenDecimals = tokenDecimals, + totalTokenPriceInUsd = totalTokenPriceInUsd, + ) }, payload = payload ) @@ -195,6 +203,7 @@ class SwapTokensCommonMapper( private fun createPriceRightSideModel( totalTokenAmount: BigDecimal?, tokenSymbol: String, + tokenDecimals: Int, totalTokenPriceInUsd: String? ): RightSideCellModel.TwoLineText? { val usdAmount = totalTokenPriceInUsd?.let { @@ -204,7 +213,7 @@ class SwapTokensCommonMapper( } val tokenAmount = totalTokenAmount?.let { TextViewCellModel.Raw( - TextContainer.Raw(it.formatTokenWithSymbol(tokenSymbol)), + TextContainer.Raw(it.formatTokenWithSymbol(tokenSymbol, tokenDecimals)), ) } diff --git a/app/src/main/java/org/p2p/wallet/restore/interactor/SeedPhraseInteractor.kt b/app/src/main/java/org/p2p/wallet/restore/interactor/SeedPhraseInteractor.kt index 03ddf7e525..c9b63ad491 100644 --- a/app/src/main/java/org/p2p/wallet/restore/interactor/SeedPhraseInteractor.kt +++ b/app/src/main/java/org/p2p/wallet/restore/interactor/SeedPhraseInteractor.kt @@ -7,23 +7,23 @@ import org.bitcoinj.crypto.MnemonicException import timber.log.Timber import java.math.BigInteger import kotlinx.coroutines.withContext +import org.p2p.core.crypto.Base58String +import org.p2p.core.crypto.toBase58Instance +import org.p2p.core.dispatchers.CoroutineDispatchers +import org.p2p.core.utils.SOL_DECIMALS import org.p2p.core.utils.fromLamports -import org.p2p.core.utils.scaleLong import org.p2p.solanaj.core.Account +import org.p2p.solanaj.core.toBase58Instance import org.p2p.solanaj.crypto.DerivationPath import org.p2p.uikit.organisms.seedphrase.SeedPhraseWord import org.p2p.wallet.auth.analytics.AdminAnalytics import org.p2p.wallet.auth.interactor.UsernameInteractor import org.p2p.wallet.auth.repository.AuthRepository -import org.p2p.core.dispatchers.CoroutineDispatchers import org.p2p.wallet.infrastructure.network.provider.TokenKeyProvider import org.p2p.wallet.restore.model.DerivableAccount import org.p2p.wallet.restore.model.SeedPhraseVerifyResult import org.p2p.wallet.rpc.repository.balance.RpcBalanceRepository -import org.p2p.core.crypto.Base58String import org.p2p.wallet.utils.mnemoticgenerator.English -import org.p2p.core.crypto.toBase58Instance -import org.p2p.solanaj.core.toBase58Instance // duck-taped, extract to storage some day const val KEY_IS_AUTH_BY_SEED_PHRASE = "KEY_IS_AUTH_BY_SEED_PHRASE" @@ -71,15 +71,15 @@ class SeedPhraseInteractor( balances: List>, path: DerivationPath ): List = accounts.mapNotNull { account -> - val balance = balances.find { it.first == account.publicKey.toBase58() } + val solBalance = balances.find { it.first == account.publicKey.toBase58() } ?.second ?: return@mapNotNull null - val total = balance.fromLamports().scaleLong() + val totalSol = solBalance.fromLamports(SOL_DECIMALS) DerivableAccount( path = path, account = account, - totalInSol = total + totalInSol = totalSol ) } diff --git a/app/src/main/java/org/p2p/wallet/restore/ui/derivable/DerivableAccountsPresenter.kt b/app/src/main/java/org/p2p/wallet/restore/ui/derivable/DerivableAccountsPresenter.kt index 004ed5f31b..30449f1204 100644 --- a/app/src/main/java/org/p2p/wallet/restore/ui/derivable/DerivableAccountsPresenter.kt +++ b/app/src/main/java/org/p2p/wallet/restore/ui/derivable/DerivableAccountsPresenter.kt @@ -3,6 +3,7 @@ package org.p2p.wallet.restore.ui.derivable import timber.log.Timber import kotlin.properties.Delegates.observable import kotlinx.coroutines.launch +import org.p2p.core.analytics.constants.ScreenNames import org.p2p.core.utils.Constants import org.p2p.solanaj.crypto.DerivationPath import org.p2p.token.service.model.TokenServiceNetwork @@ -11,7 +12,6 @@ import org.p2p.token.service.repository.TokenServiceRepository import org.p2p.wallet.auth.analytics.OnboardingAnalytics import org.p2p.wallet.auth.analytics.RestoreWalletAnalytics import org.p2p.wallet.auth.analytics.RestoreWalletAnalytics.UsernameRestoreMethod -import org.p2p.core.analytics.constants.ScreenNames import org.p2p.wallet.common.mvp.BasePresenter import org.p2p.wallet.restore.interactor.SeedPhraseInteractor import org.p2p.wallet.restore.model.DerivableAccount @@ -42,9 +42,10 @@ class DerivableAccountsPresenter( launch { try { val tokenAddress = Constants.TOKEN_SERVICE_NATIVE_SOL_TOKEN - val solRate = tokenServiceRepository.fetchTokenPriceByAddress( + val solRate = tokenServiceRepository.getTokenPriceByAddress( networkChain = TokenServiceNetwork.SOLANA, - tokenAddress = tokenAddress + tokenAddress = tokenAddress, + forceRemote = true ).also { solRate = it } ?: return@launch allAccounts = allAccounts.updateWithTotalInUsd(solRate).toMutableList() diff --git a/app/src/main/java/org/p2p/wallet/send/model/SendFeeTotal.kt b/app/src/main/java/org/p2p/wallet/send/model/SendFeeTotal.kt index 29a7477803..26b0cdf7d7 100644 --- a/app/src/main/java/org/p2p/wallet/send/model/SendFeeTotal.kt +++ b/app/src/main/java/org/p2p/wallet/send/model/SendFeeTotal.kt @@ -7,6 +7,7 @@ import java.math.BigInteger import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.Parcelize import org.p2p.core.model.TextHighlighting +import org.p2p.core.utils.SOL_DECIMALS import org.p2p.core.utils.asApproximateUsd import org.p2p.core.utils.formatTokenWithSymbol import org.p2p.core.utils.orZero @@ -142,7 +143,7 @@ class SendFeeTotal constructor( get() = receiveUsd?.asApproximateUsd().orEmpty() private val totalWithSymbolFormatted: String - get() = currentAmount.formatTokenWithSymbol(sourceSymbol) + get() = currentAmount.formatTokenWithSymbol(sourceSymbol, SOL_DECIMALS) val totalSumWithSymbol: String get() { @@ -160,6 +161,6 @@ class SendFeeTotal constructor( } } .plus(transferFee.orZero()) - return totalSum.formatTokenWithSymbol(sourceSymbol) + return totalSum.formatTokenWithSymbol(sourceSymbol, SOL_DECIMALS) } } diff --git a/app/src/main/java/org/p2p/wallet/svl/interactor/ReceiveViaLinkInteractor.kt b/app/src/main/java/org/p2p/wallet/svl/interactor/ReceiveViaLinkInteractor.kt index 1110c76ab9..ba77fb0e7d 100644 --- a/app/src/main/java/org/p2p/wallet/svl/interactor/ReceiveViaLinkInteractor.kt +++ b/app/src/main/java/org/p2p/wallet/svl/interactor/ReceiveViaLinkInteractor.kt @@ -124,16 +124,11 @@ class ReceiveViaLinkInteractor( } private suspend fun fetchPriceForToken(mintAddress: String): TokenServicePrice? { - val price = tokenServiceRepository.findTokenPriceByAddress( - tokenAddress = mintAddress, - networkChain = TokenServiceNetwork.SOLANA - ) - if (price != null) return price - return kotlin.runCatching { - tokenServiceRepository.fetchTokenPriceByAddress( + tokenServiceRepository.getTokenPriceByAddress( + tokenAddress = mintAddress, networkChain = TokenServiceNetwork.SOLANA, - tokenAddress = mintAddress + forceRemote = true ) } .onFailure { Timber.i(it) } diff --git a/app/src/main/java/org/p2p/wallet/swap/model/orca/SwapFee.kt b/app/src/main/java/org/p2p/wallet/swap/model/orca/SwapFee.kt index 6a57d63d84..a184e857a0 100644 --- a/app/src/main/java/org/p2p/wallet/swap/model/orca/SwapFee.kt +++ b/app/src/main/java/org/p2p/wallet/swap/model/orca/SwapFee.kt @@ -1,8 +1,11 @@ package org.p2p.wallet.swap.model.orca +import java.math.BigDecimal +import java.math.BigInteger import kotlinx.parcelize.IgnoredOnParcel import org.p2p.core.token.Token import org.p2p.core.utils.Constants.SOL_SYMBOL +import org.p2p.core.utils.SOL_DECIMALS import org.p2p.core.utils.asApproximateUsd import org.p2p.core.utils.formatToken import org.p2p.core.utils.fromLamports @@ -13,8 +16,6 @@ import org.p2p.core.utils.toUsd import org.p2p.wallet.feerelayer.model.FeePayerSelectionStrategy import org.p2p.wallet.send.model.FeePayerState import org.p2p.wallet.swap.model.FeeRelayerSwapFee -import java.math.BigDecimal -import java.math.BigInteger class SwapFee constructor( private val fee: FeeRelayerSwapFee, @@ -70,7 +71,7 @@ class SwapFee constructor( get() = fee.feeInPayingToken.fromLamports(feePayerToken.decimals).scaleMedium() val feeAmountInSol: BigDecimal - get() = fee.feeInSol.fromLamports().scaleMedium() + get() = fee.feeInSol.fromLamports(SOL_DECIMALS).scaleMedium() val feePayerSymbol: String = feePayerToken.tokenSymbol diff --git a/app/src/main/java/org/p2p/wallet/user/interactor/UserInteractor.kt b/app/src/main/java/org/p2p/wallet/user/interactor/UserInteractor.kt index 1d50c9e886..524b1f619a 100644 --- a/app/src/main/java/org/p2p/wallet/user/interactor/UserInteractor.kt +++ b/app/src/main/java/org/p2p/wallet/user/interactor/UserInteractor.kt @@ -10,9 +10,9 @@ import org.p2p.solanaj.core.PublicKey import org.p2p.token.service.model.TokenServiceNetwork import org.p2p.token.service.repository.TokenServiceRepository import org.p2p.wallet.home.model.TokenConverter +import org.p2p.wallet.rpc.repository.balance.RpcBalanceRepository import org.p2p.wallet.send.model.SearchResult import org.p2p.wallet.send.repository.RecipientsLocalRepository -import org.p2p.wallet.rpc.repository.balance.RpcBalanceRepository import org.p2p.wallet.user.repository.UserLocalRepository import org.p2p.wallet.user.repository.UserTokensLocalRepository import org.p2p.wallet.utils.emptyString @@ -33,7 +33,7 @@ class UserInteractor( suspend fun findTokenData(mintAddress: String): Token? { val tokenData = userLocalRepository.findTokenData(mintAddress) val price = tokenData?.let { - tokenServiceRepository.findTokenPriceByAddress( + tokenServiceRepository.getTokenPriceByAddress( tokenAddress = it.mintAddress, networkChain = TokenServiceNetwork.SOLANA ) @@ -94,7 +94,7 @@ class UserInteractor( private suspend fun findTokenDataBySymbol(symbol: String): Token.Other? { val tokenData = userLocalRepository.findTokenDataBySymbol(symbol) val price = tokenData?.let { - tokenServiceRepository.findTokenPriceByAddress( + tokenServiceRepository.getTokenPriceByAddress( tokenAddress = it.mintAddress, networkChain = TokenServiceNetwork.SOLANA ) diff --git a/app/src/main/java/org/p2p/wallet/user/interactor/UserTokensInteractor.kt b/app/src/main/java/org/p2p/wallet/user/interactor/UserTokensInteractor.kt index 8f22d7aa4d..f682288cf8 100644 --- a/app/src/main/java/org/p2p/wallet/user/interactor/UserTokensInteractor.kt +++ b/app/src/main/java/org/p2p/wallet/user/interactor/UserTokensInteractor.kt @@ -135,9 +135,10 @@ class UserTokensInteractor( val tokenMetadataData = userLocalRepository.findTokenData(mintAddress.base58Value) ?: return null - val price = tokenServiceRepository.fetchTokenPriceByAddress( + val price = tokenServiceRepository.getTokenPriceByAddress( networkChain = TokenServiceNetwork.SOLANA, - tokenAddress = mintAddress.base58Value + tokenAddress = mintAddress.base58Value, + forceRemote = true ) return TokenConverter.createToken( diff --git a/app/src/main/java/org/p2p/wallet/user/repository/UserInMemoryRepository.kt b/app/src/main/java/org/p2p/wallet/user/repository/UserInMemoryRepository.kt index 445468cbc0..7f207e0526 100644 --- a/app/src/main/java/org/p2p/wallet/user/repository/UserInMemoryRepository.kt +++ b/app/src/main/java/org/p2p/wallet/user/repository/UserInMemoryRepository.kt @@ -136,7 +136,7 @@ class UserInMemoryRepository( override suspend fun findTokenByMint(mintAddress: String): Token? { val tokenMetadata: TokenMetadata? = findTokenData(mintAddress) return if (tokenMetadata != null) { - val price = tokenServiceRepository.findTokenPriceByAddress( + val price = tokenServiceRepository.getTokenPriceByAddress( tokenAddress = tokenMetadata.mintAddress, networkChain = TokenServiceNetwork.SOLANA ) diff --git a/app/src/main/java/org/p2p/wallet/user/repository/UserTokensDatabaseRepository.kt b/app/src/main/java/org/p2p/wallet/user/repository/UserTokensDatabaseRepository.kt index 52b62290fb..98f77cfafc 100644 --- a/app/src/main/java/org/p2p/wallet/user/repository/UserTokensDatabaseRepository.kt +++ b/app/src/main/java/org/p2p/wallet/user/repository/UserTokensDatabaseRepository.kt @@ -69,7 +69,7 @@ class UserTokensDatabaseRepository( val oldTokens = getUserTokens() val newTokens = oldTokens.map { token -> - val newTokenRate = prices.firstOrNull { token.tokenServiceAddress == it.address } + val newTokenRate = prices.firstOrNull { token.tokenServiceAddress == it.tokenAddress } val oldTokenRate = token.rate val tokenRate = newTokenRate?.usdRate ?: oldTokenRate token.copy(rate = tokenRate, totalInUsd = tokenRate?.let { token.total.times(it) }) diff --git a/app/src/main/java/org/p2p/wallet/user/repository/UserTokensRemoteRepository.kt b/app/src/main/java/org/p2p/wallet/user/repository/UserTokensRemoteRepository.kt index 1ab368810c..9b840cbd47 100644 --- a/app/src/main/java/org/p2p/wallet/user/repository/UserTokensRemoteRepository.kt +++ b/app/src/main/java/org/p2p/wallet/user/repository/UserTokensRemoteRepository.kt @@ -41,7 +41,7 @@ class UserTokensRemoteRepository( val tokenData = userLocalRepository.findTokenData(mintAddress) ?: return@mapNotNull null if (tokenData.decimals == NFT_DECIMALS) return@mapNotNull null - val tokenPrice = tokenServiceRepository.findTokenPriceByAddress( + val tokenPrice = tokenServiceRepository.getTokenPriceByAddress( tokenAddress = tokenData.mintAddress, networkChain = TokenServiceNetwork.SOLANA ) @@ -58,7 +58,7 @@ class UserTokensRemoteRepository( * */ val solBalance = rpcBalanceRepository.getBalance(publicKey) val tokenData = userLocalRepository.findTokenData(Constants.WRAPPED_SOL_MINT) ?: return tokens - val solPrice = tokenServiceRepository.findTokenPriceByAddress( + val solPrice = tokenServiceRepository.getTokenPriceByAddress( tokenAddress = Constants.TOKEN_SERVICE_NATIVE_SOL_TOKEN, networkChain = TokenServiceNetwork.SOLANA ) diff --git a/app/src/main/res/values/swap_strings.xml b/app/src/main/res/values/swap_strings.xml index 9f7a1ba92a..db1a7d8901 100644 --- a/app/src/main/res/values/swap_strings.xml +++ b/app/src/main/res/values/swap_strings.xml @@ -52,8 +52,8 @@ Estimated fees - Interest bearing - Transfer fee + Token-2022 interest bearing + Token-2022 transfer fee Minimum received %s %s @@ -101,7 +101,8 @@ Interest bearing %s Liquidity fee %s - + Swap fee + The token %s is out of the strict list Make sure the mint address for %s %s is correct before confirming Confirm selection diff --git a/app/src/test/java/org/p2p/wallet/jupiter/ui/main/JupiterSwapPresenterBaseTest.kt b/app/src/test/java/org/p2p/wallet/jupiter/ui/main/JupiterSwapPresenterBaseTest.kt index 21cfaea3aa..2b263376c3 100644 --- a/app/src/test/java/org/p2p/wallet/jupiter/ui/main/JupiterSwapPresenterBaseTest.kt +++ b/app/src/test/java/org/p2p/wallet/jupiter/ui/main/JupiterSwapPresenterBaseTest.kt @@ -277,7 +277,7 @@ open class JupiterSwapPresenterBaseTest { tokenRates: (List) -> Map, ) { coEvery { jupiterSwapTokensRepository.getTokens() } returns allTokens - coEvery { tokenServiceRepository.loadPriceForTokens(any(), any()) } answers { + coEvery { tokenServiceRepository.fetchTokenPricesForTokens(any(), any()) } answers { val tokens: List = arg(0) tokenRates(tokens) } diff --git a/app/src/test/java/org/p2p/wallet/jupiter/ui/main/JupiterSwapTestHelpers.kt b/app/src/test/java/org/p2p/wallet/jupiter/ui/main/JupiterSwapTestHelpers.kt index f4aa1a7eae..3344c62aee 100644 --- a/app/src/test/java/org/p2p/wallet/jupiter/ui/main/JupiterSwapTestHelpers.kt +++ b/app/src/test/java/org/p2p/wallet/jupiter/ui/main/JupiterSwapTestHelpers.kt @@ -201,7 +201,9 @@ object JupiterSwapTestHelpers { totalFees = BigInteger.ZERO, platformFeePercent = BigDecimal.ZERO, platformFeeTokenB = BigInteger.ZERO - ) + ), + inTokenMint = data.inputMint, + outTokenMint = data.outputMint ) } diff --git a/app/src/test/java/org/p2p/wallet/jupiter/ui/main/JupiterTestPresenterBuilder.kt b/app/src/test/java/org/p2p/wallet/jupiter/ui/main/JupiterTestPresenterBuilder.kt index 7887265a60..561fdb047b 100644 --- a/app/src/test/java/org/p2p/wallet/jupiter/ui/main/JupiterTestPresenterBuilder.kt +++ b/app/src/test/java/org/p2p/wallet/jupiter/ui/main/JupiterTestPresenterBuilder.kt @@ -102,7 +102,7 @@ class JupiterTestPresenterBuilder { else -> null } - }.associateBy { it.address.toBase58Instance() } + }.associateBy { it.tokenAddress.toBase58Instance() } } /** diff --git a/core/src/main/java/org/p2p/core/utils/AmountExtensions.kt b/core/src/main/java/org/p2p/core/utils/AmountExtensions.kt index e6a13457bf..691ef0c3c5 100644 --- a/core/src/main/java/org/p2p/core/utils/AmountExtensions.kt +++ b/core/src/main/java/org/p2p/core/utils/AmountExtensions.kt @@ -3,12 +3,11 @@ package org.p2p.core.utils import java.math.BigDecimal import java.math.BigInteger import java.math.RoundingMode -import kotlin.math.pow import org.p2p.core.token.Token import org.p2p.core.utils.Constants.FIAT_FRACTION_LENGTH -private const val POWER_VALUE = 10.0 -const val DEFAULT_DECIMAL = 9 +private val POWER_VALUE = BigDecimal("10") +const val SOL_DECIMALS = 9 const val MOONPAY_DECIMAL = 2 const val STRIGA_FIAT_DECIMALS = 2 @@ -27,8 +26,7 @@ fun String?.toBigIntegerOrZero(): BigInteger { return this?.toBigIntegerOrNull() ?: BigInteger.ZERO } -fun Int.toPowerValue(): BigDecimal = - BigDecimal(POWER_VALUE.pow(this)) +fun Int.toPowerValue(): BigDecimal = POWER_VALUE.pow(this) fun BigDecimal.scaleShortOrFirstNotZero(): BigDecimal { return if (isZero()) { @@ -56,8 +54,10 @@ fun BigDecimal.scaleLong(decimals: Int = SCALE_VALUE_LONG): BigDecimal = if (this.isZero()) this else this.setScale(decimals, RoundingMode.DOWN) .stripTrailingZeros() // removing zeros, case: 0.02000 -> 0.02 -fun BigInteger.fromLamports(decimals: Int = DEFAULT_DECIMAL): BigDecimal = - BigDecimal(this.toDouble() / (POWER_VALUE.pow(decimals))) +// do not use BigDecimal(double) sometimes it makes the amount less +// example: pass 0.030, get 0.029 +fun BigInteger.fromLamports(decimals: Int): BigDecimal = + (this.toBigDecimal() / (POWER_VALUE.pow(decimals))) .stripTrailingZeros() // removing zeros, case: 0.02000 -> 0.02 .scaleLong(decimals) @@ -77,7 +77,7 @@ fun BigDecimal.formatFiat(): String = formatWithDecimals(FIAT_FRACTION_LENGTH) // case 2: 1.0 -> 1 - default behavior // case 3: 1.0 -> 1.0 -> with keepInitialDecimals=true fun BigDecimal.formatToken( - decimals: Int = DEFAULT_DECIMAL, + decimals: Int = SOL_DECIMALS, exactDecimals: Boolean = false, keepInitialDecimals: Boolean = false, ): String = formatWithDecimals( @@ -88,7 +88,7 @@ fun BigDecimal.formatToken( fun BigDecimal.formatTokenWithSymbol( tokenSymbol: String, - decimals: Int = DEFAULT_DECIMAL, + decimals: Int, exactDecimals: Boolean = false, keepInitialDecimals: Boolean = false, ): String { @@ -133,20 +133,20 @@ fun BigDecimal.formatWithDecimals( } fun BigDecimal?.isNullOrZero(): Boolean = this == null || this.compareTo(BigDecimal.ZERO) == 0 -fun BigDecimal.isZero() = this.compareTo(BigDecimal.ZERO) == 0 -fun BigDecimal.isNotZero() = this.compareTo(BigDecimal.ZERO) != 0 -fun BigDecimal.isMoreThan(value: BigDecimal) = this.compareTo(value) == 1 -fun BigDecimal.isLessThan(value: BigDecimal) = this.compareTo(value) == -1 -fun BigDecimal.isZeroOrLess() = isZero() || isLessThan(BigDecimal.ZERO) +fun BigDecimal.isZero(): Boolean = this.compareTo(BigDecimal.ZERO) == 0 +fun BigDecimal.isNotZero(): Boolean = this.compareTo(BigDecimal.ZERO) != 0 +fun BigDecimal.isMoreThan(value: BigDecimal): Boolean = this.compareTo(value) == 1 +fun BigDecimal.isLessThan(value: BigDecimal): Boolean = this.compareTo(value) == -1 +fun BigDecimal.isZeroOrLess(): Boolean = isZero() || isLessThan(BigDecimal.ZERO) fun BigDecimal?.orZero(): BigDecimal = this ?: BigDecimal.ZERO fun BigInteger?.orZero(): BigInteger = this ?: BigInteger.ZERO -fun BigInteger.isZero() = this.compareTo(BigInteger.ZERO) == 0 -fun BigInteger.isNotZero() = this.compareTo(BigInteger.ZERO) != 0 +fun BigInteger.isZero(): Boolean = this.compareTo(BigInteger.ZERO) == 0 +fun BigInteger.isNotZero(): Boolean = this.compareTo(BigInteger.ZERO) != 0 fun BigInteger.isLessThan(value: BigInteger) = this.compareTo(value) == -1 fun BigInteger.isMoreThan(value: BigInteger) = this.compareTo(value) == 1 -fun BigInteger.isZeroOrLess() = isZero() || isLessThan(BigInteger.ZERO) +fun BigInteger.isZeroOrLess(): Boolean = isZero() || isLessThan(BigInteger.ZERO) fun BigDecimal.asCurrency(currencyUiSymbol: String): String = when { isZero() -> "${currencyUiSymbol}0" @@ -185,6 +185,6 @@ fun BigDecimal.asUsdSwap(): String = when { fun Int?.orZero(): Int = this ?: 0 // value is in (0..0.01) -fun BigDecimal.lessThenMinValue() = !isZero() && isLessThan(AMOUNT_MIN_VALUE.toBigDecimal()) -fun BigDecimal.moreThenMinValue() = isMoreThan(AMOUNT_MIN_VALUE.toBigDecimal()) +fun BigDecimal.lessThenMinValue(): Boolean = !isZero() && isLessThan(AMOUNT_MIN_VALUE.toBigDecimal()) +fun BigDecimal.moreThenMinValue(): Boolean = isMoreThan(AMOUNT_MIN_VALUE.toBigDecimal()) fun BigDecimal.divideByInt(byInt: Int): BigDecimal = divide(byInt.toBigDecimal()) diff --git a/ethereumkit/src/main/java/org/p2p/ethereumkit/external/repository/EthereumKitRepository.kt b/ethereumkit/src/main/java/org/p2p/ethereumkit/external/repository/EthereumKitRepository.kt index 9bc23c7e81..75ced37c79 100644 --- a/ethereumkit/src/main/java/org/p2p/ethereumkit/external/repository/EthereumKitRepository.kt +++ b/ethereumkit/src/main/java/org/p2p/ethereumkit/external/repository/EthereumKitRepository.kt @@ -15,8 +15,8 @@ import org.p2p.ethereumkit.external.model.EthTokenConverter import org.p2p.ethereumkit.external.model.EthTokenKeyProvider import org.p2p.ethereumkit.external.model.EthTokenMetadata import org.p2p.ethereumkit.external.model.EthereumClaimToken -import org.p2p.ethereumkit.external.token.EthereumTokensLocalRepository import org.p2p.ethereumkit.external.token.EthereumTokenRepository +import org.p2p.ethereumkit.external.token.EthereumTokensLocalRepository import org.p2p.ethereumkit.internal.core.TransactionSignerEip1559 import org.p2p.ethereumkit.internal.core.TransactionSignerLegacy import org.p2p.ethereumkit.internal.core.signer.Signer @@ -131,7 +131,7 @@ internal class EthereumKitRepository( this += erc20TokensAddresses } // Fetch tokens metadata - val tokensMetadata = tokenServiceRepository.loadMetadataForTokens( + val tokensMetadata = tokenServiceRepository.fetchMetadataForTokens( chain = TokenServiceNetwork.ETHEREUM, tokenAddresses = allTokensAddresses ) diff --git a/ethereumkit/src/main/java/org/p2p/ethereumkit/external/token/EthereumTokensInMemoryRepository.kt b/ethereumkit/src/main/java/org/p2p/ethereumkit/external/token/EthereumTokensInMemoryRepository.kt index a5a79a78f1..fa3c7a02d3 100644 --- a/ethereumkit/src/main/java/org/p2p/ethereumkit/external/token/EthereumTokensInMemoryRepository.kt +++ b/ethereumkit/src/main/java/org/p2p/ethereumkit/external/token/EthereumTokensInMemoryRepository.kt @@ -25,14 +25,14 @@ class EthereumTokensInMemoryRepository : EthereumTokensLocalRepository { override suspend fun updateTokensRate(tokensRate: List) { val newTokenRates = buildList { - val nativePrice = tokensRate.firstOrNull { it.address == ERC20Tokens.ETH.contractAddress } + val nativePrice = tokensRate.firstOrNull { it.tokenAddress == ERC20Tokens.ETH.contractAddress } if (nativePrice != null) { - this += nativePrice.copy(address = Constants.TOKEN_SERVICE_NATIVE_SOL_TOKEN) + this += nativePrice.copy(tokenAddress = Constants.TOKEN_SERVICE_NATIVE_SOL_TOKEN) } this += tokensRate } cachedTokens.value = cachedTokens.value.map { token -> - val foundTokenRate = newTokenRates.firstOrNull { it.address == token.tokenServiceAddress } + val foundTokenRate = newTokenRates.firstOrNull { it.tokenAddress == token.tokenServiceAddress } val totalInUsd = foundTokenRate?.usdRate?.let { token.total.times(it) } token.copy(totalInUsd = totalInUsd, rate = foundTokenRate?.usdRate) } diff --git a/token-service/src/main/java/org/p2p/token/service/api/events/manager/TokenServiceEventPublisher.kt b/token-service/src/main/java/org/p2p/token/service/api/events/manager/TokenServiceEventPublisher.kt index 70ad046c0d..31e47730f7 100644 --- a/token-service/src/main/java/org/p2p/token/service/api/events/manager/TokenServiceEventPublisher.kt +++ b/token-service/src/main/java/org/p2p/token/service/api/events/manager/TokenServiceEventPublisher.kt @@ -6,8 +6,8 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNot import kotlinx.coroutines.launch import org.p2p.core.dispatchers.CoroutineDispatchers -import org.p2p.token.service.repository.TokenServiceRepository import org.p2p.token.service.model.TokenServiceNetwork +import org.p2p.token.service.repository.TokenServiceRepository class TokenServiceEventPublisher( private val tokenServiceInteractor: TokenServiceRepository, @@ -27,7 +27,7 @@ class TokenServiceEventPublisher( eventType = TokenServiceEventType.from(networkChain), data = TokenServiceUpdate.Loading ) - tokenServiceInteractor.loadPriceForTokens( + tokenServiceInteractor.fetchTokenPricesForTokens( chain = networkChain, tokenAddresses = addresses ) diff --git a/token-service/src/main/java/org/p2p/token/service/api/mapper/TokenServiceMapper.kt b/token-service/src/main/java/org/p2p/token/service/api/mapper/TokenServiceMapper.kt index 4becaf47e6..4854d20cbe 100644 --- a/token-service/src/main/java/org/p2p/token/service/api/mapper/TokenServiceMapper.kt +++ b/token-service/src/main/java/org/p2p/token/service/api/mapper/TokenServiceMapper.kt @@ -34,7 +34,7 @@ class TokenServiceMapper { ): TokenServicePrice? { val tokenRate = response.price ?: return null return TokenServicePrice( - address = response.tokenAddress, + tokenAddress = response.tokenAddress, rate = fromNetwork(tokenRate), network = tokenServiceNetwork ) diff --git a/token-service/src/main/java/org/p2p/token/service/database/mapper/TokenServiceDatabaseMapper.kt b/token-service/src/main/java/org/p2p/token/service/database/mapper/TokenServiceDatabaseMapper.kt index ffbea25533..b1a0fd7cf9 100644 --- a/token-service/src/main/java/org/p2p/token/service/database/mapper/TokenServiceDatabaseMapper.kt +++ b/token-service/src/main/java/org/p2p/token/service/database/mapper/TokenServiceDatabaseMapper.kt @@ -11,7 +11,7 @@ internal class TokenServiceDatabaseMapper { val tokenRate = item.rate val usdRate = tokenRate.usd ?: return null return TokenServicePriceEntity( - tokenAddress = item.address, + tokenAddress = item.tokenAddress, networkChainName = item.network.networkName, usdRate = usdRate ) @@ -19,7 +19,7 @@ internal class TokenServiceDatabaseMapper { fun fromEntity(entity: TokenServicePriceEntity): TokenServicePrice { return TokenServicePrice( - address = entity.tokenAddress, + tokenAddress = entity.tokenAddress, rate = TokenRate( usd = entity.usdRate ), diff --git a/token-service/src/main/java/org/p2p/token/service/model/TokenServicePrice.kt b/token-service/src/main/java/org/p2p/token/service/model/TokenServicePrice.kt index b2c4c17fa8..20450135fb 100644 --- a/token-service/src/main/java/org/p2p/token/service/model/TokenServicePrice.kt +++ b/token-service/src/main/java/org/p2p/token/service/model/TokenServicePrice.kt @@ -4,7 +4,7 @@ import java.math.BigDecimal import org.p2p.core.utils.scaleShort data class TokenServicePrice( - val address: String, + val tokenAddress: String, val rate: TokenRate, val network: TokenServiceNetwork ) { @@ -15,6 +15,6 @@ data class TokenServicePrice( get() = rate.usd override fun toString(): String { - return "TokenServicePrice($address - ${rate.usd} - ${network.networkName})" + return "TokenServicePrice($tokenAddress - ${rate.usd} - ${network.networkName})" } } diff --git a/token-service/src/main/java/org/p2p/token/service/repository/TokenServiceRepository.kt b/token-service/src/main/java/org/p2p/token/service/repository/TokenServiceRepository.kt index 815c0996ed..636eda2728 100644 --- a/token-service/src/main/java/org/p2p/token/service/repository/TokenServiceRepository.kt +++ b/token-service/src/main/java/org/p2p/token/service/repository/TokenServiceRepository.kt @@ -6,14 +6,23 @@ import org.p2p.token.service.model.TokenServiceNetwork import org.p2p.token.service.model.TokenServicePrice interface TokenServiceRepository { - suspend fun loadPriceForTokens(chain: TokenServiceNetwork, tokenAddresses: List) - suspend fun loadMetadataForTokens( + suspend fun fetchTokenPricesForTokens( + chain: TokenServiceNetwork, + tokenAddresses: List + ) + suspend fun fetchMetadataForTokens( chain: TokenServiceNetwork, tokenAddresses: List ): List - suspend fun observeTokenPricesFlow(networkChain: TokenServiceNetwork): Flow> - suspend fun findTokenPriceByAddress(tokenAddress: String, networkChain: TokenServiceNetwork): TokenServicePrice? - suspend fun fetchTokenPriceByAddress(networkChain: TokenServiceNetwork, tokenAddress: String): TokenServicePrice? - fun findTokenMetadataByAddress(networkChain: TokenServiceNetwork, tokenAddress: String): TokenServiceMetadata? + fun observeTokenPricesFlow(networkChain: TokenServiceNetwork): Flow> + suspend fun getTokenPriceByAddress( + tokenAddress: String, + networkChain: TokenServiceNetwork, + forceRemote: Boolean = false + ): TokenServicePrice? + fun findTokenMetadataByAddress( + networkChain: TokenServiceNetwork, + tokenAddress: String + ): TokenServiceMetadata? } diff --git a/token-service/src/main/java/org/p2p/token/service/repository/TokenServiceRepositoryImpl.kt b/token-service/src/main/java/org/p2p/token/service/repository/TokenServiceRepositoryImpl.kt index 2055a9afbc..6da967b328 100644 --- a/token-service/src/main/java/org/p2p/token/service/repository/TokenServiceRepositoryImpl.kt +++ b/token-service/src/main/java/org/p2p/token/service/repository/TokenServiceRepositoryImpl.kt @@ -16,7 +16,7 @@ internal class TokenServiceRepositoryImpl( private val metadataRemoteRepository: TokenMetadataRepository ) : TokenServiceRepository { - override suspend fun loadPriceForTokens(chain: TokenServiceNetwork, tokenAddresses: List) { + override suspend fun fetchTokenPricesForTokens(chain: TokenServiceNetwork, tokenAddresses: List) { val result = priceRemoteRepository.loadTokensPrice( chain = chain, addresses = tokenAddresses @@ -25,7 +25,7 @@ internal class TokenServiceRepositoryImpl( priceLocalRepository.saveTokensPrice(tokensPrices) } - override suspend fun loadMetadataForTokens( + override suspend fun fetchMetadataForTokens( chain: TokenServiceNetwork, tokenAddresses: List ): List { @@ -38,22 +38,20 @@ internal class TokenServiceRepositoryImpl( return tokensMetadata } - override suspend fun observeTokenPricesFlow(networkChain: TokenServiceNetwork): Flow> = + override fun observeTokenPricesFlow(networkChain: TokenServiceNetwork): Flow> = priceLocalRepository.observeTokenPrices(networkChain) - override suspend fun findTokenPriceByAddress( + override suspend fun getTokenPriceByAddress( tokenAddress: String, - networkChain: TokenServiceNetwork - ): TokenServicePrice? { - return priceLocalRepository.findTokenPriceByAddress(address = tokenAddress, networkChain = networkChain) - } - - override suspend fun fetchTokenPriceByAddress( networkChain: TokenServiceNetwork, - tokenAddress: String + forceRemote: Boolean ): TokenServicePrice? { - loadPriceForTokens(chain = networkChain, tokenAddresses = listOf(tokenAddress)) - return findTokenPriceByAddress(tokenAddress = tokenAddress, networkChain = networkChain) + val localPrice = priceLocalRepository.findTokenPriceByAddress(tokenAddress, networkChain) + if (forceRemote || localPrice == null) { + fetchTokenPricesForTokens(networkChain, listOf(tokenAddress)) + } + + return priceLocalRepository.findTokenPriceByAddress(tokenAddress, networkChain) } override fun findTokenMetadataByAddress( diff --git a/token-service/src/test/java/org/p2p/token/service/database/mapper/TokenServiceDatabaseMapperTest.kt b/token-service/src/test/java/org/p2p/token/service/database/mapper/TokenServiceDatabaseMapperTest.kt index e8a9494843..9ff61b0dca 100644 --- a/token-service/src/test/java/org/p2p/token/service/database/mapper/TokenServiceDatabaseMapperTest.kt +++ b/token-service/src/test/java/org/p2p/token/service/database/mapper/TokenServiceDatabaseMapperTest.kt @@ -38,7 +38,7 @@ class TokenServiceDatabaseMapperTest { // THEN val expectedDomainModel = TokenServicePrice( - address = Constants.SOL_MINT, + tokenAddress = Constants.SOL_MINT, rate = TokenRate(usd = usdRate), network = network ) @@ -69,7 +69,7 @@ class TokenServiceDatabaseMapperTest { val usdRate = BigDecimal.TEN val network = TokenServiceNetwork.SOLANA val domain = TokenServicePrice( - address = Constants.SOL_MINT, + tokenAddress = Constants.SOL_MINT, network = network, rate = TokenRate(usd = usdRate) ) @@ -92,7 +92,7 @@ class TokenServiceDatabaseMapperTest { // GIVEN val network = TokenServiceNetwork.SOLANA val domain = TokenServicePrice( - address = Constants.SOL_MINT, + tokenAddress = Constants.SOL_MINT, network = network, rate = TokenRate(usd = null) ) diff --git a/token-service/src/test/java/org/p2p/token/service/repository/price/TokenPriceDatabaseRepositoryTest.kt b/token-service/src/test/java/org/p2p/token/service/repository/price/TokenPriceDatabaseRepositoryTest.kt index 2f0d60a07b..a938c6ed95 100644 --- a/token-service/src/test/java/org/p2p/token/service/repository/price/TokenPriceDatabaseRepositoryTest.kt +++ b/token-service/src/test/java/org/p2p/token/service/repository/price/TokenPriceDatabaseRepositoryTest.kt @@ -112,7 +112,7 @@ class TokenPriceDatabaseRepositoryTest { TokenServiceNetwork.ETHEREUM } this += TokenServicePrice( - address = Constants.SOL_MINT + it, + tokenAddress = Constants.SOL_MINT + it, rate = TokenRate(usd = it.toLong().toBigDecimal()), network = randomNetwork, )