feat: order and label per-chain token breakdown by ecosystem + standard

The per-chain list shown when picking an action (Send/Receive/Swap/Buy/Sell/
Gift) for a token that exists on multiple chains (e.g. USDT) now orders
Ethereum and Tron right after the existing Pezkuwi/Polkadot/Kusama priority
chains instead of falling into the alphabetical "everything else" bucket,
and appends a token-standard label - "(PEZ-20)"/"(ERC-20)"/"(TRC-20)" - to
disambiguate which issuance this is, since a bare chain name alone doesn't
convey that.

The label is intentionally chain-specific, not derived from Chain.Asset.Type,
since every Statemine-type chain (Polkadot AH, Kusama AH, Pezkuwi AH) shares
the same asset type but only Pezkuwi AH's issuance needs the PEZ-20 label.
This commit is contained in:
2026-07-11 06:44:35 -07:00
parent e1679367d7
commit e055e1a84c
3 changed files with 38 additions and 10 deletions
@@ -16,8 +16,10 @@ import io.novafoundation.nova.feature_assets.presentation.flow.network.model.Net
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.AmountFormatter
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.formatAmountToAmountModel
import io.novafoundation.nova.feature_wallet_api.presentation.formatters.amount.model.AmountConfig
import io.novafoundation.nova.runtime.ext.assetStandardLabelOrNull
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.asset
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
@@ -73,7 +75,7 @@ abstract class NetworkFlowViewModel(
NetworkFlowRvItem(
it.chain.id,
it.asset.token.configuration.id,
it.chain.name,
it.chain.displayNameWithAssetStandard(),
it.chain.icon,
amountFormatter.formatAmountToAmountModel(
amount = getAssetBalance(it).amount,
@@ -83,4 +85,10 @@ abstract class NetworkFlowViewModel(
)
}
}
private fun Chain.displayNameWithAssetStandard(): String {
val standardLabel = assetStandardLabelOrNull ?: return name
return "$name ($standardLabel)"
}
}
@@ -488,6 +488,7 @@ object ChainGeneses {
object ChainIds {
const val ETHEREUM = "$EIP_155_PREFIX:1"
const val TRON = "tron:0x2b6653dc"
const val MOONBEAM = ChainGeneses.MOONBEAM
const val MOONRIVER = ChainGeneses.MOONRIVER
@@ -499,6 +500,21 @@ val Chain.Companion.Geneses
val Chain.Companion.Ids
get() = ChainIds
/**
* A short, user-facing token-standard label for chains where disambiguating "which token standard is this"
* is actually useful (multiple ecosystems all issue their own USDT/USDC etc., so a bare chain name isn't
* always enough context). Deliberately NOT derived from [Chain.Asset.Type] (e.g. every Statemine-type chain
* would get the same label) - this is chain-specific by design, matching exactly which labels are
* recognizable/expected by users (PEZ-20, ERC-20, TRC-20), not a mechanical one-label-per-asset-type mapping.
*/
val Chain.assetStandardLabelOrNull: String?
get() = when {
genesisHash == Chain.Geneses.PEZKUWI_ASSET_HUB -> "PEZ-20"
id == Chain.Ids.ETHEREUM -> "ERC-20"
id == Chain.Ids.TRON -> "TRC-20"
else -> null
}
fun Chain.Asset.requireStatemine(): Type.Statemine {
require(type is Type.Statemine)
@@ -3,19 +3,23 @@ package io.novafoundation.nova.runtime.ext
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
val Chain.mainChainsFirstAscendingOrder
get() = when (genesisHash) {
get() = when {
// Pezkuwi ecosystem first
Chain.Geneses.PEZKUWI -> 0
Chain.Geneses.PEZKUWI_ASSET_HUB -> 1
Chain.Geneses.PEZKUWI_PEOPLE -> 2
genesisHash == Chain.Geneses.PEZKUWI -> 0
genesisHash == Chain.Geneses.PEZKUWI_ASSET_HUB -> 1
genesisHash == Chain.Geneses.PEZKUWI_PEOPLE -> 2
// Then Polkadot ecosystem
Chain.Geneses.POLKADOT -> 3
Chain.Geneses.POLKADOT_ASSET_HUB -> 4
genesisHash == Chain.Geneses.POLKADOT -> 3
genesisHash == Chain.Geneses.POLKADOT_ASSET_HUB -> 4
// Then Kusama ecosystem
Chain.Geneses.KUSAMA -> 5
Chain.Geneses.KUSAMA_ASSET_HUB -> 6
genesisHash == Chain.Geneses.KUSAMA -> 5
genesisHash == Chain.Geneses.KUSAMA_ASSET_HUB -> 6
// Then Ethereum, then Tron - not identified by genesisHash (that's substrate-only), so this can't
// stay a `when (genesisHash)` subject match once these two are added
id == Chain.Ids.ETHEREUM -> 7
id == Chain.Ids.TRON -> 8
// Everything else
else -> 7
else -> 9
}
val Chain.testnetsLastAscendingOrder