mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-07-22 17:15:48 +00:00
Add read-only Tron (TRC20) chain family support (#9)
* Add read-only Tron (TRC20) chain family support Adds Tron as a third chain family alongside Substrate and EVM: address derivation (BIP44 coin type 195, secp256k1 reused from the existing Ethereum path, Base58Check encoding), TronGrid-backed TRX and TRC20 USDT balance display, and a Room migration for the new per-account Tron key/address columns. Read-only only — no send, signing, or broadcast support yet. * fix: exhaustive when branches for Trc20/TronNative asset types ChainExt.kt's onChainAssetId and Common.kt's existentialDepositError both switch exhaustively over Chain.Asset.Type and didn't have cases for the new Trc20/TronNative variants, breaking compilation. Trc20 mirrors EvmErc20 (contract address as the on-chain id, dust burns rather than transfers on removal); TronNative mirrors EvmNative.
This commit is contained in:
@@ -14,7 +14,11 @@ import io.novafoundation.nova.common.utils.emptySubstrateAccountId
|
||||
import io.novafoundation.nova.common.utils.findIsInstanceOrNull
|
||||
import io.novafoundation.nova.common.utils.formatNamed
|
||||
import io.novafoundation.nova.common.utils.removeHexPrefix
|
||||
import io.novafoundation.nova.common.utils.emptyTronAccountId
|
||||
import io.novafoundation.nova.common.utils.substrateAccountId
|
||||
import io.novafoundation.nova.common.utils.toTronAddress
|
||||
import io.novafoundation.nova.common.utils.tronAddressToAccountId
|
||||
import io.novafoundation.nova.common.utils.tronPublicKeyToAccountId
|
||||
import io.novafoundation.nova.core_db.model.AssetAndChainId
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain.Asset.StakingType.ALEPH_ZERO
|
||||
@@ -266,10 +270,10 @@ fun Chain.supportsLegacyAddressFormat() = legacyAddressPrefix != null
|
||||
fun Chain.requireGenesisHash() = requireNotNull(genesisHash)
|
||||
|
||||
fun Chain.addressOf(accountId: ByteArray): String {
|
||||
return if (isEthereumBased) {
|
||||
accountId.toEthereumAddress()
|
||||
} else {
|
||||
accountId.toAddress(addressPrefix.toShort())
|
||||
return when {
|
||||
isTronBased -> accountId.toTronAddress()
|
||||
isEthereumBased -> accountId.toEthereumAddress()
|
||||
else -> accountId.toAddress(addressPrefix.toShort())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,7 +282,7 @@ fun Chain.addressOf(accountId: AccountIdKey): String {
|
||||
}
|
||||
|
||||
fun Chain.legacyAddressOfOrNull(accountId: ByteArray): String? {
|
||||
return if (isEthereumBased) {
|
||||
return if (isEthereumBased || isTronBased) {
|
||||
null
|
||||
} else {
|
||||
legacyAddressPrefix?.let { accountId.toAddress(it.toShort()) }
|
||||
@@ -290,10 +294,10 @@ fun ByteArray.toEthereumAddress(): String {
|
||||
}
|
||||
|
||||
fun Chain.accountIdOf(address: String): ByteArray {
|
||||
return if (isEthereumBased) {
|
||||
address.asEthereumAddress().toAccountId().value
|
||||
} else {
|
||||
address.toAccountId()
|
||||
return when {
|
||||
isTronBased -> address.tronAddressToAccountId()
|
||||
isEthereumBased -> address.asEthereumAddress().toAccountId().value
|
||||
else -> address.toAccountId()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,10 +327,10 @@ fun Chain.accountIdOrNull(address: String): ByteArray? {
|
||||
return runCatching { accountIdOf(address) }.getOrNull()
|
||||
}
|
||||
|
||||
fun Chain.emptyAccountId() = if (isEthereumBased) {
|
||||
emptyEthereumAccountId()
|
||||
} else {
|
||||
emptySubstrateAccountId()
|
||||
fun Chain.emptyAccountId() = when {
|
||||
isTronBased -> emptyTronAccountId()
|
||||
isEthereumBased -> emptyEthereumAccountId()
|
||||
else -> emptySubstrateAccountId()
|
||||
}
|
||||
|
||||
fun Chain.emptyAccountIdKey() = emptyAccountId().intoKey()
|
||||
@@ -336,10 +340,10 @@ fun Chain.accountIdOrDefault(maybeAddress: String): ByteArray {
|
||||
}
|
||||
|
||||
fun Chain.accountIdOf(publicKey: ByteArray): ByteArray {
|
||||
return if (isEthereumBased) {
|
||||
publicKey.asEthereumPublicKey().toAccountId().value
|
||||
} else {
|
||||
publicKey.substrateAccountId()
|
||||
return when {
|
||||
isTronBased -> publicKey.tronPublicKeyToAccountId()
|
||||
isEthereumBased -> publicKey.asEthereumPublicKey().toAccountId().value
|
||||
else -> publicKey.substrateAccountId()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,6 +529,26 @@ fun Chain.Asset.requireErc20(): Type.EvmErc20 {
|
||||
return type
|
||||
}
|
||||
|
||||
fun Chain.Asset.requireTrc20(): Type.Trc20 {
|
||||
require(type is Type.Trc20)
|
||||
|
||||
return type
|
||||
}
|
||||
|
||||
/**
|
||||
* The TronGrid-style REST API base url for a Tron-based chain.
|
||||
* Unlike EVM chains (where balance/rpc calls go through the chain's `nodes` JSON-RPC/WS endpoints and only tx-history
|
||||
* REST APIs are sourced from a separate `externalApis` entry), Tron has no JSON-RPC/WS node concept at all - the
|
||||
* configured `nodes` entry directly *is* the TronGrid REST API base url used for both balance and history.
|
||||
*/
|
||||
fun Chain.requireTronGridBaseUrl(): String {
|
||||
require(isTronBased) { "Chain $id is not Tron-based" }
|
||||
|
||||
return requireNotNull(nodes.nodes.minByOrNull { it.orderId }?.unformattedUrl) {
|
||||
"No TronGrid node configured for chain $id"
|
||||
}
|
||||
}
|
||||
|
||||
fun Chain.Asset.requireEquilibrium(): Type.Equilibrium {
|
||||
require(type is Type.Equilibrium)
|
||||
|
||||
@@ -588,6 +612,8 @@ val Chain.Asset.onChainAssetId: String?
|
||||
is Type.EvmErc20 -> this.type.contractAddress
|
||||
is Type.Native -> null
|
||||
is Type.EvmNative -> null
|
||||
is Type.Trc20 -> this.type.contractAddress
|
||||
Type.TronNative -> null
|
||||
Type.Unsupported -> error("Unsupported assetId type: ${this.type::class.simpleName}")
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -9,6 +9,9 @@ const val ASSET_UNSUPPORTED = "unsupported"
|
||||
const val ASSET_EVM_ERC20 = "evm"
|
||||
const val ASSET_EVM_NATIVE = "evmNative"
|
||||
|
||||
const val ASSET_TRON_NATIVE = "tronNative"
|
||||
const val ASSET_TRC20 = "trc20"
|
||||
|
||||
const val ASSET_EQUILIBRIUM = "equilibrium"
|
||||
const val ASSET_EQUILIBRIUM_ON_CHAIN_ID = "assetId"
|
||||
|
||||
|
||||
+7
@@ -59,6 +59,12 @@ fun mapChainAssetTypeToRaw(type: Chain.Asset.Type): Pair<String, Map<String, Any
|
||||
|
||||
is Chain.Asset.Type.EvmNative -> ASSET_EVM_NATIVE to null
|
||||
|
||||
is Chain.Asset.Type.TronNative -> ASSET_TRON_NATIVE to null
|
||||
|
||||
is Chain.Asset.Type.Trc20 -> ASSET_TRC20 to mapOf(
|
||||
EVM_EXTRAS_CONTRACT_ADDRESS to type.contractAddress
|
||||
)
|
||||
|
||||
is Chain.Asset.Type.Equilibrium -> ASSET_EQUILIBRIUM to mapOf(
|
||||
ASSET_EQUILIBRIUM_ON_CHAIN_ID to type.id.toString()
|
||||
)
|
||||
@@ -121,6 +127,7 @@ fun mapChainToLocal(chain: Chain, gson: Gson): ChainLocal {
|
||||
prefix = chain.addressPrefix,
|
||||
legacyPrefix = chain.legacyAddressPrefix,
|
||||
isEthereumBased = chain.isEthereumBased,
|
||||
isTronBased = chain.isTronBased,
|
||||
isTestNet = chain.isTestNet,
|
||||
hasSubstrateRuntime = chain.hasSubstrateRuntime,
|
||||
pushSupport = chain.pushSupport,
|
||||
|
||||
+9
@@ -85,6 +85,14 @@ private fun mapChainAssetTypeFromRaw(type: String?, typeExtras: Map<String, Any?
|
||||
|
||||
ASSET_EVM_NATIVE -> Chain.Asset.Type.EvmNative
|
||||
|
||||
ASSET_TRON_NATIVE -> Chain.Asset.Type.TronNative
|
||||
|
||||
ASSET_TRC20 -> {
|
||||
Chain.Asset.Type.Trc20(
|
||||
contractAddress = typeExtras!![EVM_EXTRAS_CONTRACT_ADDRESS] as String
|
||||
)
|
||||
}
|
||||
|
||||
ASSET_EQUILIBRIUM -> Chain.Asset.Type.Equilibrium((typeExtras!![ASSET_EQUILIBRIUM_ON_CHAIN_ID] as String).toBigInteger())
|
||||
|
||||
else -> Chain.Asset.Type.Unsupported
|
||||
@@ -267,6 +275,7 @@ fun mapChainLocalToChain(
|
||||
addressPrefix = prefix,
|
||||
legacyAddressPrefix = legacyPrefix,
|
||||
isEthereumBased = isEthereumBased,
|
||||
isTronBased = isTronBased,
|
||||
isTestNet = isTestNet,
|
||||
hasCrowdloans = hasCrowdloans,
|
||||
pushSupport = pushSupport,
|
||||
|
||||
+2
@@ -20,6 +20,7 @@ import io.novafoundation.nova.runtime.multiNetwork.chain.remote.model.ChainAsset
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.remote.model.ChainRemote
|
||||
|
||||
private const val ETHEREUM_OPTION = "ethereumBased"
|
||||
private const val TRON_OPTION = "tronBased"
|
||||
private const val CROWDLOAN_OPTION = "crowdloans"
|
||||
private const val TESTNET_OPTION = "testnet"
|
||||
private const val PROXY_OPTION = "proxy"
|
||||
@@ -90,6 +91,7 @@ fun mapRemoteChainToLocal(
|
||||
prefix = addressPrefix,
|
||||
legacyPrefix = legacyAddressPrefix,
|
||||
isEthereumBased = ETHEREUM_OPTION in optionsOrEmpty,
|
||||
isTronBased = TRON_OPTION in optionsOrEmpty,
|
||||
isTestNet = TESTNET_OPTION in optionsOrEmpty,
|
||||
hasCrowdloans = CROWDLOAN_OPTION in optionsOrEmpty,
|
||||
supportProxy = PROXY_OPTION in optionsOrEmpty,
|
||||
|
||||
+14
@@ -32,6 +32,7 @@ data class Chain(
|
||||
val legacyAddressPrefix: Int?,
|
||||
val types: Types?,
|
||||
val isEthereumBased: Boolean,
|
||||
val isTronBased: Boolean,
|
||||
val isTestNet: Boolean,
|
||||
val source: Source,
|
||||
val hasSubstrateRuntime: Boolean,
|
||||
@@ -124,6 +125,19 @@ data class Chain(
|
||||
val id: BigInteger
|
||||
) : Type()
|
||||
|
||||
/**
|
||||
* Native TRX balance on a Tron-based chain.
|
||||
* Balance is fetched from a TronGrid-style REST API rather than JSON-RPC.
|
||||
*/
|
||||
object TronNative : Type()
|
||||
|
||||
/**
|
||||
* TRC-20 token on a Tron-based chain (EVM-compatible token standard, different REST API shape from Ethereum's ERC-20).
|
||||
*/
|
||||
data class Trc20(
|
||||
val contractAddress: String
|
||||
) : Type()
|
||||
|
||||
object Unsupported : Type()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user