Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show all metadata and mark the locked ones with lock icon #1010

Merged
merged 18 commits into from
Jun 28, 2024
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.babylon.wallet.android.data.gateway.extensions

import com.babylon.wallet.android.data.gateway.apis.StateApi
import com.babylon.wallet.android.data.gateway.generated.models.EntityMetadataItem
import com.babylon.wallet.android.data.gateway.generated.models.FungibleResourcesCollection
import com.babylon.wallet.android.data.gateway.generated.models.FungibleResourcesCollectionItem
import com.babylon.wallet.android.data.gateway.generated.models.LedgerState
Expand All @@ -15,6 +16,7 @@ import com.babylon.wallet.android.data.gateway.generated.models.StateEntityDetai
import com.babylon.wallet.android.data.gateway.generated.models.StateEntityDetailsResponseItem
import com.babylon.wallet.android.data.gateway.generated.models.StateEntityFungiblesPageRequest
import com.babylon.wallet.android.data.gateway.generated.models.StateEntityFungiblesPageRequestOptIns
import com.babylon.wallet.android.data.gateway.generated.models.StateEntityMetadataPageRequest
import com.babylon.wallet.android.data.gateway.generated.models.StateEntityNonFungibleIdsPageRequest
import com.babylon.wallet.android.data.gateway.generated.models.StateEntityNonFungiblesPageRequest
import com.babylon.wallet.android.data.gateway.generated.models.StateEntityNonFungiblesPageRequestOptIns
Expand Down Expand Up @@ -386,3 +388,28 @@ suspend fun StateApi.paginateNonFungibles(
onPage(response)
}
}

suspend fun StateApi.getAllMetadata(
resourceAddress: ResourceAddress,
stateVersion: Long,
initialCursor: String
): List<EntityMetadataItem> {
val items = mutableListOf<EntityMetadataItem>()

var cursor: String? = initialCursor
while (cursor != null) {
val page = entityMetadataPage(
stateEntityMetadataPageRequest = StateEntityMetadataPageRequest(
address = resourceAddress.string,
cursor = initialCursor,
atLedgerState = LedgerStateSelector(
stateVersion = stateVersion
)
)
).toResult().getOrThrow()
cursor = page.nextCursor
items.addAll(page.items)
}

return items
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.babylon.wallet.android.data.gateway.generated.models.MetadataPublicKe
import com.babylon.wallet.android.data.gateway.generated.models.MetadataStringArrayValue
import com.babylon.wallet.android.data.gateway.generated.models.MetadataStringValue
import com.babylon.wallet.android.data.gateway.generated.models.MetadataTypedValue
import com.babylon.wallet.android.data.gateway.generated.models.MetadataU32ArrayValue
import com.babylon.wallet.android.data.gateway.generated.models.MetadataU32Value
import com.babylon.wallet.android.data.gateway.generated.models.MetadataU64ArrayValue
import com.babylon.wallet.android.data.gateway.generated.models.MetadataU64Value
Expand Down Expand Up @@ -62,7 +63,7 @@ object MetadataTypedValueSerializer : JsonContentPolymorphicSerializer<MetadataT
MetadataValueType.StringArray -> MetadataStringArrayValue.serializer()
MetadataValueType.BoolArray -> MetadataBoolArrayValue.serializer()
MetadataValueType.U8Array -> MetadataU8ArrayValue.serializer()
MetadataValueType.U32Array -> MetadataU32Value.serializer()
MetadataValueType.U32Array -> MetadataU32ArrayValue.serializer()
MetadataValueType.U64Array -> MetadataU64ArrayValue.serializer()
MetadataValueType.I32Array -> MetadataI32ArrayValue.serializer()
MetadataValueType.I64Array -> MetadataI64ArrayValue.serializer()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package com.babylon.wallet.android.data.repository.cache.database
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.babylon.wallet.android.data.gateway.extensions.toMetadata
import com.babylon.wallet.android.data.gateway.generated.models.StateEntityDetailsResponseItem
import com.radixdlt.sargon.AccountAddress
import com.radixdlt.sargon.extensions.init
Expand All @@ -27,7 +26,10 @@ data class DAppEntity(
companion object {
fun from(item: StateEntityDetailsResponseItem, syncedAt: Instant) = DAppEntity(
definitionAddress = AccountAddress.init(item.address),
metadata = item.explicitMetadata?.toMetadata()?.let { MetadataColumn(it) },
metadata = MetadataColumn.from(
explicitMetadata = item.explicitMetadata,
implicitMetadata = item.metadata
),
synced = syncedAt
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ data class NFTEntity(
metadata = toMetadata().takeIf {
it.isNotEmpty()
}?.let {
MetadataColumn(it)
MetadataColumn(metadata = it, implicitState = MetadataColumn.ImplicitMetadataState.Complete)
},
synced = synced
)
Expand All @@ -49,7 +49,7 @@ data class NFTEntity(
metadata = metadata.takeIf {
it.isNotEmpty()
}?.let {
MetadataColumn(it)
MetadataColumn(metadata = it, implicitState = MetadataColumn.ImplicitMetadataState.Complete)
},
synced = synced
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
import com.babylon.wallet.android.data.gateway.extensions.PoolsResponse
import com.babylon.wallet.android.data.gateway.extensions.toMetadata
import com.babylon.wallet.android.data.gateway.generated.models.StateEntityDetailsResponseItem
import com.babylon.wallet.android.data.repository.cache.database.PoolResourceJoin.Companion.asPoolResourceJoin
import com.babylon.wallet.android.data.repository.cache.database.ResourceEntity.Companion.asEntity
Expand Down Expand Up @@ -60,11 +59,14 @@ data class PoolEntity(
}

fun StateEntityDetailsResponseItem.asPoolEntity(): PoolEntity? {
val metadata = this.metadata.toMetadata()
val poolUnitResourceAddress = metadata.poolUnit() ?: return null
val metadataColumn = MetadataColumn.from(
explicitMetadata = explicitMetadata,
implicitMetadata = metadata
)
val poolUnitResourceAddress = metadataColumn.metadata.poolUnit() ?: return null
return PoolEntity(
address = PoolAddress.init(address),
metadata = MetadataColumn(metadata),
metadata = metadataColumn,
resourceAddress = poolUnitResourceAddress
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package com.babylon.wallet.android.data.repository.cache.database

import androidx.room.ProvidedTypeConverter
import androidx.room.TypeConverter
import com.babylon.wallet.android.data.gateway.extensions.toMetadata
import com.babylon.wallet.android.data.gateway.generated.models.EntityMetadataCollection
import com.babylon.wallet.android.data.repository.cache.database.MetadataColumn.ImplicitMetadataState
import com.radixdlt.sargon.AccountAddress
import com.radixdlt.sargon.Decimal192
import com.radixdlt.sargon.NonFungibleLocalId
Expand All @@ -12,6 +15,7 @@ import com.radixdlt.sargon.VaultAddress
import com.radixdlt.sargon.extensions.init
import com.radixdlt.sargon.extensions.string
import com.radixdlt.sargon.extensions.toDecimal192OrNull
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
Expand All @@ -24,7 +28,75 @@ import java.time.Instant
data class BehavioursColumn(val behaviours: Set<AssetBehaviour>)

@Serializable
data class MetadataColumn(val metadata: List<Metadata>)
data class MetadataColumn(
/**
* A union of explicit and **currently known** implicit metadata
* As a client we don't need to expose the difference between explicit
* and implicit metadata.
*/
val metadata: List<Metadata>,

/**
* The state of the next page of the implicit metadata. See [ImplicitMetadataState]
*/
@SerialName("implicit_state")
val implicitState: ImplicitMetadataState
) {

val nextCursor: String?
get() = (implicitState as? ImplicitMetadataState.Incomplete)?.nextCursor

@Serializable
sealed interface ImplicitMetadataState {
/**
* When we have no information yet regarding the existence of implicit metadata
* An example is when we query account information. In this request we receive
* resource data but with no information about implicit metadata. In order to make
* sure we received all metadata available we need to fetch details of this specific
* resource
*/
@Serializable
@SerialName("unknown")
data object Unknown : ImplicitMetadataState

/**
* We have received an answer from a details request and we know that the [MetadataColumn.metadata]
* are complete.
*/
@Serializable
@SerialName("complete")
data object Complete : ImplicitMetadataState

/**
* We have received an answer from a details request and we know that the [MetadataColumn.metadata]
* are incomplete. We need to query [nextCursor] to receive more.
*/
@Serializable
@SerialName("incomplete")
data class Incomplete(
@SerialName("next_cursor")
val nextCursor: String
) : ImplicitMetadataState
}

companion object {
fun from(
explicitMetadata: EntityMetadataCollection?,
implicitMetadata: EntityMetadataCollection
): MetadataColumn {
val explicit = explicitMetadata?.toMetadata().orEmpty().toSet()
val implicit = implicitMetadata.toMetadata().toSet()

val all = explicit union implicit
return MetadataColumn(
metadata = all.toList(),
implicitState = implicitMetadata.nextCursor?.let {
ImplicitMetadataState.Incomplete(nextCursor = it)
} ?: ImplicitMetadataState.Complete
)
}
}
}

@Suppress("TooManyFunctions")
@ProvidedTypeConverter
Expand All @@ -37,23 +109,23 @@ class StateDatabaseConverters {
// Behaviours
@TypeConverter
fun stringToBehaviours(string: String?): BehavioursColumn? {
return string?.let { BehavioursColumn(behaviours = json.decodeFromString(string)) }
return string?.let { json.decodeFromString(string) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should use same json as we use for gateway models serialization?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it is different, this is how the column is serialized into a json string. Room stores metadata in a custom type called MetadataColumn which uses this serialisation logic inside. Also the Metadata type we have defined is a domain model and not a GW one.

}

@TypeConverter
fun behavioursToString(column: BehavioursColumn?): String? {
return column?.let { json.encodeToString(it.behaviours) }
return column?.let { json.encodeToString(it) }
}

// Metadata
@TypeConverter
fun stringToMetadata(string: String?): MetadataColumn? {
return string?.let { MetadataColumn(metadata = json.decodeFromString(string)) }
return string?.let { json.decodeFromString(string) }
}

@TypeConverter
fun metadataToString(column: MetadataColumn?): String? {
return column?.let { json.encodeToString(it.metadata) }
return column?.let { json.encodeToString(it) }
}

// Decimal192
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@ import com.radixdlt.sargon.extensions.init
import com.radixdlt.sargon.extensions.string
import com.radixdlt.sargon.extensions.toDecimal192
import rdx.works.core.domain.resources.Divisibility
import rdx.works.core.domain.resources.ExplicitMetadataKey
import rdx.works.core.domain.resources.Resource
import rdx.works.core.domain.resources.metadata.Metadata
import rdx.works.core.domain.resources.metadata.MetadataType
import rdx.works.core.domain.resources.metadata.poolAddress
import rdx.works.core.domain.resources.metadata.validatorAddress
import java.time.Instant
Expand All @@ -50,17 +47,14 @@ data class ResourceEntity(
val synced: Instant
) {

val isDetailsAvailable: Boolean
get() = when (type) {
ResourceEntityType.FUNGIBLE -> supply != null && divisibility != null && behaviours != null
ResourceEntityType.NON_FUNGIBLE -> supply != null && behaviours != null
}

@Suppress("CyclomaticComplexMethod")
fun toResource(amount: Decimal192?): Resource {
val validatorAndPoolMetadata = listOf(
validatorAddress?.let {
Metadata.Primitive(ExplicitMetadataKey.VALIDATOR.key, it.string, MetadataType.Address)
},
poolAddress?.let {
Metadata.Primitive(ExplicitMetadataKey.POOL.key, it.string, MetadataType.Address)
}
).mapNotNull { it }

return when (type) {
ResourceEntityType.FUNGIBLE -> {
Resource.FungibleResource(
Expand All @@ -69,7 +63,7 @@ data class ResourceEntity(
assetBehaviours = behaviours?.behaviours?.toSet(),
currentSupply = supply,
divisibility = divisibility,
metadata = metadata?.metadata.orEmpty() + validatorAndPoolMetadata
metadata = metadata?.metadata.orEmpty()
)
}

Expand All @@ -80,7 +74,7 @@ data class ResourceEntity(
assetBehaviours = behaviours?.behaviours?.toSet(),
items = emptyList(),
currentSupply = supply?.string?.toIntOrNull(),
metadata = metadata?.metadata.orEmpty() + validatorAndPoolMetadata
metadata = metadata?.metadata.orEmpty()
)
}
}
Expand All @@ -97,9 +91,8 @@ data class ResourceEntity(
validatorAddress = metadata.validatorAddress(),
poolAddress = metadata.poolAddress(),
metadata = metadata
.filterNot { it.key in setOf(ExplicitMetadataKey.POOL.key, ExplicitMetadataKey.VALIDATOR.key) }
.takeIf { it.isNotEmpty() }
?.let { MetadataColumn(it) },
?.let { MetadataColumn(it, MetadataColumn.ImplicitMetadataState.Unknown) },
synced = synced
)

Expand All @@ -112,9 +105,8 @@ data class ResourceEntity(
validatorAddress = metadata.validatorAddress(),
poolAddress = metadata.poolAddress(),
metadata = metadata
.filterNot { it.key in setOf(ExplicitMetadataKey.POOL.key, ExplicitMetadataKey.VALIDATOR.key) }
.takeIf { it.isNotEmpty() }
?.let { MetadataColumn(it) },
?.let { MetadataColumn(it, MetadataColumn.ImplicitMetadataState.Unknown) },
synced = synced
)
}
Expand All @@ -126,7 +118,8 @@ data class ResourceEntity(
details: StateEntityDetailsResponseItemDetails? = null
): ResourceEntity = from(
address = ResourceAddress.init(resourceAddress),
metadataCollection = explicitMetadata,
explicitMetadata = explicitMetadata,
implicitMetadata = null,
details = details,
type = ResourceEntityType.FUNGIBLE,
synced = synced
Expand All @@ -139,7 +132,8 @@ data class ResourceEntity(
details: StateEntityDetailsResponseItemDetails? = null
): ResourceEntity = from(
address = ResourceAddress.init(resourceAddress),
metadataCollection = explicitMetadata,
explicitMetadata = explicitMetadata,
implicitMetadata = null,
details = details,
type = ResourceEntityType.NON_FUNGIBLE,
synced = synced
Expand All @@ -156,33 +150,44 @@ data class ResourceEntity(
}
return from(
address = ResourceAddress.init(address),
metadataCollection = metadata,
explicitMetadata = explicitMetadata,
implicitMetadata = metadata,
details = details,
type = type,
synced = synced
)
}

@Suppress("LongParameterList")
private fun from(
address: ResourceAddress,
metadataCollection: EntityMetadataCollection?,
explicitMetadata: EntityMetadataCollection?,
implicitMetadata: EntityMetadataCollection?,
details: StateEntityDetailsResponseItemDetails?,
type: ResourceEntityType,
synced: Instant
): ResourceEntity {
val metadata = metadataCollection?.toMetadata().orEmpty()
val metadataColumn = if (implicitMetadata != null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the difference between explicit/implicit metadata?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explicit metadata are the ones we request when we communicate with GW and GW ensures that we will receive them. Also known as standard metadata. For example a resource may have defined 150 metadata. If you do a simple entity details request, on the first response we will only receive the first page of them (100) and we need to paginate them in order to get the other 50.

Although we need to somehow make sure that some metadata will be received since are crucial to rendering the UI without the need of pagination, like name, symbol, description, icon_url....

So for these requests you will see that we put ExplicitMetadataKey.forAssets in order to retrieve in the explicit_metadata field these crucial ones we need for ui.

When the user navigates to asset details, we can also display the rest of the metadata (besides the explicit ones). Decided to call them implicit. If we know that there is a page to be paginated, this is the time we do it.

MetadataColumn.from(
explicitMetadata = explicitMetadata,
implicitMetadata = implicitMetadata
)
} else {
MetadataColumn(
metadata = explicitMetadata?.toMetadata().orEmpty(),
implicitState = MetadataColumn.ImplicitMetadataState.Unknown
)
}.takeIf { it.metadata.isNotEmpty() }

return ResourceEntity(
address = address,
type = type,
divisibility = details?.divisibility(),
behaviours = details?.let { BehavioursColumn(it.extractBehaviours()) },
supply = details?.totalSupply(),
validatorAddress = metadata.validatorAddress(),
poolAddress = metadata.poolAddress(),
metadata = metadata
.filterNot { it.key in setOf(ExplicitMetadataKey.VALIDATOR.key, ExplicitMetadataKey.POOL.key) }
.takeIf { it.isNotEmpty() }
?.let { MetadataColumn(it) },
validatorAddress = metadataColumn?.metadata?.validatorAddress(),
poolAddress = metadataColumn?.metadata?.poolAddress(),
metadata = metadataColumn,
synced = synced
)
}
Expand Down
Loading
Loading