Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,7 @@ fun WalletNavGraph(

perpetualsScreen(
onOpenPerpetual = navigator::openPerpetual,
onOpenAsset = navigator::openAsset,
onOpenPortfolio = { navigator.openPortfolio(PortfolioType.Perpetuals) },
amountAction = AmountTransactionAction(navigator::openAmount),
onSelectDepositAsset = navigator::openPerpetualDepositSelect,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ data object PerpetualDepositSelectRoute : NavKey
fun EntryProviderScope<NavKey>.perpetualsScreen(
onCancel: () -> Unit,
onOpenPerpetual: AssetIdAction,
onOpenAsset: AssetIdAction,
onOpenPortfolio: () -> Unit,
amountAction: AmountTransactionAction,
onSelectDepositAsset: () -> Unit,
Expand Down Expand Up @@ -61,6 +62,7 @@ fun EntryProviderScope<NavKey>.perpetualsScreen(
confirmAction = confirmAction,
onClose = onCancel,
onTransaction = onTransaction,
onOpenAsset = onOpenAsset,
confirmContent = { input, finishAction, cancelAction, onOpenAddress ->
ConfirmTransferScreen(
input = input,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,10 @@ object PerpetualModule {
fun provideGemPerpetualDetailsService(
perpetualService: GemPerpetualService,
transactionsService: GemTransactionsService,
assetsService: GemAssetsService,
preferencesService: GemPreferencesService,
walletSessionService: GemWalletSessionService,
): GemPerpetualDetailsServiceInterface = GemPerpetualDetailsService(perpetualService, transactionsService, preferencesService, walletSessionService)
): GemPerpetualDetailsServiceInterface = GemPerpetualDetailsService(perpetualService, transactionsService, assetsService, preferencesService, walletSessionService)

@Provides
@Singleton
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ interface AssetsDao {
@Query("SELECT * FROM asset WHERE id IN (:ids)")
suspend fun getAssetsByIds(ids: List<String>): List<DbAsset>

@Query("SELECT * FROM asset WHERE id IN (:ids)")
fun getAssets(ids: List<String>): Flow<List<DbAsset>>

@Query("SELECT id FROM asset WHERE id IN (:ids)")
suspend fun getAssetIds(ids: List<String>): List<String>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
package com.gemwallet.android.data.services.store.queries

import com.gemwallet.android.data.services.store.database.AssetsDao
import com.gemwallet.android.data.services.store.database.PerpetualDao
import com.gemwallet.android.data.services.store.database.entities.toDTO
import com.gemwallet.android.ext.toIdentifier
import com.wallet.core.primitives.Asset
import com.wallet.core.primitives.AssetAssociationType
import com.wallet.core.primitives.AssetId
import com.wallet.core.primitives.PerpetualData
import com.wallet.core.primitives.PerpetualId
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import javax.inject.Inject

class PerpetualQuery @Inject constructor(private val perpetualDao: PerpetualDao) {
@OptIn(ExperimentalCoroutinesApi::class)
class PerpetualQuery @Inject constructor(private val perpetualDao: PerpetualDao, private val assetsDao: AssetsDao) {

operator fun invoke(assetId: AssetId): Flow<PerpetualData?> = perpetualDao.getPerpetualByAssetId(assetId.toIdentifier()).map { it?.toDTO() }

operator fun invoke(perpetualId: PerpetualId): Flow<PerpetualData?> = perpetualDao.getPerpetual(perpetualId.toIdentifier()).map { it?.toDTO() }

fun associatedAssets(assetId: AssetId): Flow<List<Asset>> = perpetualDao.getPerpetualByAssetId(assetId.toIdentifier())
.map { perpetual -> perpetual?.asset?.associations.orEmpty().filter { it.type == AssetAssociationType.Official }.map { it.assetId.toIdentifier() } }
.distinctUntilChanged()
.flatMapLatest { assetIds -> assetsDao.getAssets(assetIds) }
.map { assets -> assets.mapNotNull { it.toDTO() } }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@ import androidx.room.Room
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.gemwallet.android.data.services.store.database.GemDatabase
import com.gemwallet.android.data.services.store.database.entities.DbAsset
import com.gemwallet.android.data.services.store.database.entities.toRecord
import com.gemwallet.android.data.services.store.queries.PerpetualQuery
import com.gemwallet.android.ext.toIdentifier
import com.gemwallet.android.testkit.mockAsset
import com.gemwallet.android.testkit.mockAssetId
import com.gemwallet.android.testkit.mockPerpetual
import com.wallet.core.primitives.AssetAssociation
import com.wallet.core.primitives.AssetAssociationType
import com.wallet.core.primitives.AssetId
import com.wallet.core.primitives.AssetType
import com.wallet.core.primitives.Chain
import com.wallet.core.primitives.PerpetualData
import com.wallet.core.primitives.PerpetualId
Expand All @@ -30,7 +36,7 @@ class PerpetualQueryTest {
InstrumentationRegistry.getInstrumentation().targetContext,
GemDatabase::class.java,
).build()
private val query = PerpetualQuery(database.perpetualDao())
private val query = PerpetualQuery(database.perpetualDao(), database.assetsDao())
private val bitcoinAsset = mockAsset(id = mockAssetId(chain = Chain.Bitcoin), name = "Bitcoin", symbol = "BTC", decimals = 8u)
private val ethereumAsset = mockAsset(id = mockAssetId(chain = Chain.Ethereum), name = "Ethereum", symbol = "ETH", decimals = 18u)
private val bitcoin = PerpetualData(
Expand Down Expand Up @@ -70,6 +76,30 @@ class PerpetualQueryTest {
assertEquals(ethereum, query(ethereum.perpetual.id).first())
}

@Test
fun theMarketListsItsOfficialAssociatedAssets() = runBlocking(Dispatchers.IO) {
val perpetualAssetId = AssetId(Chain.HyperCore, "perpetual::CAKE")
val wrapped = AssetId(Chain.Ethereum, "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599")
database.assetsDao().insert(
listOf(
DbAsset(
id = perpetualAssetId.toIdentifier(),
chain = Chain.HyperCore,
name = "CAKE",
symbol = "CAKE",
decimals = 1,
type = AssetType.PERPETUAL,
associations = listOf(AssetAssociation(ethereumAsset.id, AssetAssociationType.Official), AssetAssociation(wrapped, AssetAssociationType.Wrapped)),
),
DbAsset(id = wrapped.toIdentifier(), chain = Chain.Ethereum, name = "Wrapped BTC", symbol = "WBTC", decimals = 8, type = AssetType.ERC20),
),
)
database.perpetualDao().insert(listOf(mockPerpetual().copy(id = PerpetualId(PerpetualProvider.Hypercore, "CAKE"), name = "CAKE", assetId = perpetualAssetId, identifier = "2").toRecord()))

assertEquals(listOf(ethereumAsset.id), query.associatedAssets(perpetualAssetId).first().map { it.id })
assertEquals(emptyList<AssetId>(), query.associatedAssets(ethereumAsset.id).first().map { it.id })
}

@Test
fun anUnknownAssetOrIdHasNoMarket() = runBlocking(Dispatchers.IO) {
assertNull(query(mockAssetId(chain = Chain.Solana)).first())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.gemwallet.android.features.perpetuals.presents.perpetual

import com.wallet.core.primitives.AssetId
import com.wallet.core.primitives.ChartPeriod
import com.wallet.core.primitives.PerpetualDirection
import com.wallet.core.primitives.TransactionId
Expand All @@ -17,4 +18,5 @@ internal sealed interface PerpetualAction {
data class ZoomChart(val magnification: Float, val anchor: Float) : PerpetualAction
data class PanChart(val fraction: Float) : PerpetualAction
data class OpenTransaction(val transactionId: TransactionId) : PerpetualAction
data class OpenAsset(val assetId: AssetId) : PerpetualAction
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import com.gemwallet.android.ext.toPrimitives
import com.gemwallet.android.features.perpetuals.presents.components.PerpetualActions
import com.gemwallet.android.features.perpetuals.presents.components.PerpetualChartSection
import com.gemwallet.android.features.perpetuals.presents.components.PerpetualModifyBottomSheet
import com.gemwallet.android.features.perpetuals.presents.components.positionProperties
import com.gemwallet.android.ui.R
import com.gemwallet.android.ui.components.clickable
import com.gemwallet.android.ui.components.list_item.AssetListItem
import com.gemwallet.android.ui.components.list_item.GemListRowView
import com.gemwallet.android.ui.components.list_item.SubheaderItem
import com.gemwallet.android.ui.components.list_item.property.DataBadgeChevron
import com.gemwallet.android.ui.components.list_item.property.itemsPositioned
import com.gemwallet.android.ui.components.list_item.rememberDateSections
import com.gemwallet.android.ui.components.list_item.transaction.transactionsList
Expand Down Expand Up @@ -106,6 +110,14 @@ internal fun PerpetualScene(
}
}
}
itemsPositioned(details?.associatedAssets.orEmpty()) { position, associatedAsset ->
AssetListItem(
row = associatedAsset.row,
listPosition = position,
modifier = Modifier.clickable { onAction(PerpetualAction.OpenAsset(associatedAsset.asset.toPrimitives().id)) },
accessory = { DataBadgeChevron() },
)
}
if (transactions.isNotEmpty()) {
transactionsList(transactionSections, onTransactionClick)
}
Expand Down Expand Up @@ -157,6 +169,7 @@ private fun PerpetualScenePreview() {
),
position = null,
positionRow = null,
associatedAssets = emptyList(),
),
transactions = emptyList(),
chart = StateViewType.Loading,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import com.gemwallet.android.ui.components.screen.ModalBottomSheet
import com.gemwallet.android.ui.components.screen.SheetExpansion
import com.gemwallet.android.ui.components.screen.rememberSnackbarState
import com.gemwallet.android.ui.models.actions.AmountTransactionAction
import com.gemwallet.android.ui.models.actions.AssetIdAction
import com.gemwallet.android.ui.models.actions.ConfirmTransactionAction
import com.gemwallet.android.ui.models.actions.FinishConfirmAction
import com.wallet.core.primitives.TransactionId
Expand All @@ -29,6 +30,7 @@ fun PerpetualScreen(
confirmAction: ConfirmTransactionAction,
onClose: () -> Unit,
onTransaction: (TransactionId) -> Unit,
onOpenAsset: AssetIdAction,
confirmContent: AutocloseConfirmContent,
viewModel: PerpetualViewModel = hiltViewModel(),
) {
Expand Down Expand Up @@ -77,6 +79,7 @@ fun PerpetualScreen(
is PerpetualAction.ZoomChart -> viewModel.onZoom(action.magnification, action.anchor)
is PerpetualAction.PanChart -> viewModel.onPan(action.fraction)
is PerpetualAction.OpenTransaction -> onTransaction(action.transactionId)
is PerpetualAction.OpenAsset -> onOpenAsset(action.assetId)
}
},
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ class PerpetualViewModel @Inject constructor(
.flowOn(ioDispatcher)
.stateIn(viewModelScope, SharingStarted.Eagerly, null)

val details: StateFlow<GemPerpetualDetails?> = combine(perpetual, position) { perpetual, position ->
perpetual?.let { service.details(it.perpetual.toGem(), it.asset.toGem(), listOfNotNull(position?.toGem())) }
val details: StateFlow<GemPerpetualDetails?> = combine(perpetual, position, perpetualQuery.associatedAssets(assetId)) { perpetual, position, associatedAssets ->
perpetual?.let { service.details(it.perpetual.toGem(), it.asset.toGem(), listOfNotNull(position?.toGem()), associatedAssets.map { asset -> asset.toGem() }) }
}
.flowOn(ioDispatcher)
.stateIn(viewModelScope, SharingStarted.Eagerly, null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class PerpetualViewModelTest {
}
val perpetual: PerpetualQuery = mockk {
every { this@mockk(any<AssetId>()) } returns perpetuals
every { associatedAssets(any()) } returns flowOf(emptyList())
}
val transactions: GetTransactions = mockk {
every { getTransactions(any(), any()) } returns emptyFlow()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1629,12 +1629,14 @@ fun mockGemPerpetualDetails(
modifyButtons: List<uniffi.gemstone.GemPerpetualButtonRow> = emptyList(),
position: uniffi.gemstone.PerpetualPosition? = null,
positionRow: uniffi.gemstone.GemPerpetualPositionRow? = null,
associatedAssets: List<uniffi.gemstone.GemPerpetualAssociatedAssetRow> = emptyList(),
) = uniffi.gemstone.GemPerpetualDetails(
title = title,
sections = sections,
modifyButtons = modifyButtons,
position = position,
positionRow = positionRow,
associatedAssets = associatedAssets,
)

fun mockGemPerpetualTransferData(
Expand Down
2 changes: 1 addition & 1 deletion core/gemstone/src/services/assets/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ pub fn asset_text(asset: &Asset) -> GemAssetText {
}
}

fn asset_row_text(asset: &Asset, style: GemAssetRowStyle) -> GemAssetRowText {
pub fn asset_row_text(asset: &Asset, style: GemAssetRowStyle) -> GemAssetRowText {
let chain_asset = ChainAsset::from_chain(asset.chain());
let title = match style.title {
GemAssetTitleStyle::Asset => asset.name.clone(),
Expand Down
25 changes: 21 additions & 4 deletions core/gemstone/src/services/perpetual/details.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use super::model::{GemPerpetualDetails, GemPerpetualPositionAction, GemPerpetual
use super::{GemPerpetualService, rules};
use crate::models::perpetual::GemPerpetualSubscription;
use crate::models::state::GemLoadState;
use crate::services::assets::GemAssetsService;
use crate::services::error::GemServiceError;
use crate::services::failures::record_result;
use crate::services::preferences::GemPreferencesService;
Expand All @@ -18,17 +19,19 @@ use crate::services::wallet_session::GemWalletSessionService;
pub struct GemPerpetualDetailsService {
perpetuals: Arc<GemPerpetualService>,
transactions: Arc<GemTransactionsService>,
assets: Arc<GemAssetsService>,
preferences: Arc<GemPreferencesService>,
session: Arc<GemWalletSessionService>,
}

#[uniffi::export]
impl GemPerpetualDetailsService {
#[uniffi::constructor]
pub fn new(perpetuals: Arc<GemPerpetualService>, transactions: Arc<GemTransactionsService>, preferences: Arc<GemPreferencesService>, session: Arc<GemWalletSessionService>) -> Self {
pub fn new(perpetuals: Arc<GemPerpetualService>, transactions: Arc<GemTransactionsService>, assets: Arc<GemAssetsService>, preferences: Arc<GemPreferencesService>, session: Arc<GemWalletSessionService>) -> Self {
Self {
perpetuals,
transactions,
assets,
preferences,
session,
}
Expand All @@ -38,8 +41,8 @@ impl GemPerpetualDetailsService {
self.perpetuals.is_available()
}

pub fn details(&self, perpetual: Perpetual, asset: Asset, positions: Vec<PerpetualPosition>) -> GemPerpetualDetails {
rules::details(&perpetual, &asset, positions)
pub fn details(&self, perpetual: Perpetual, asset: Asset, positions: Vec<PerpetualPosition>, associated_assets: Vec<Asset>) -> GemPerpetualDetails {
rules::details(&perpetual, &asset, positions, associated_assets)
}

pub fn position_action(&self, perpetual: Perpetual, asset: Asset, position: Option<PerpetualPosition>, kind: GemPerpetualPositionKind) -> Result<GemPerpetualPositionAction, GemServiceError> {
Expand Down Expand Up @@ -77,9 +80,10 @@ impl GemPerpetualDetailsService {

pub async fn refresh(&self, asset_id: AssetId) -> Vec<GemPerpetualRefreshFailure> {
let mut failures = Vec::new();
let (positions, transactions) = futures::join!(self.sync_positions(), self.sync_transactions(asset_id));
let (positions, transactions, asset) = futures::join!(self.sync_positions(), self.sync_transactions(asset_id.clone()), self.assets.update_asset(asset_id));
record_result(&mut failures, GemPerpetualRefreshStep::Positions, positions);
record_result(&mut failures, GemPerpetualRefreshStep::Transactions, transactions);
record_result(&mut failures, GemPerpetualRefreshStep::Asset, asset);
failures
}
}
Expand All @@ -103,6 +107,7 @@ impl GemPerpetualDetailsService {
#[cfg(test)]
mod tests {
use futures::executor::block_on;
use gem_hypercore::models::metadata::perpetual_asset_id;
use primitives::Chain;

use super::super::testkit::PerpetualTestkit;
Expand All @@ -122,6 +127,18 @@ mod tests {
})
}

#[test]
fn test_refresh() {
block_on(async {
let testkit = PerpetualTestkit::new().details_service();

testkit.service.refresh(perpetual_asset_id("CAKE")).await;

let asset_paths: Vec<String> = testkit.provider.requested_paths().into_iter().filter(|path| path.starts_with("/v1/assets/")).collect();
assert_eq!(asset_paths, vec!["/v1/assets/hypercore_perpetual::CAKE"]);
})
}

#[test]
fn test_a_candle_load_remembers_the_period_it_was_asked_for() {
block_on(async {
Expand Down
Loading
Loading