merge: reconcile with feature/trc20-tron-send into one combined branch

Bitcoin was branched off main independently instead of continuing on top
of the Tron send branch, which caused real divergence: both branches touch
the same shared signing/DI code (multiChainEncryptionFor, getMetaAccountKeypair,
NodeHealthStateTesterFactory, AssetsModule/TypeBasedAssetSourceRegistry,
CloudBackup schema, Fee model), and both need to ship together in one
coordinated release once wallet-util's master is unblocked. Merging now
reconciles that before it gets any harder, and gives one branch/versionCode
history for device testing instead of two that silently diverge.

Conflict resolutions of note:
- SecretStoreV2/SecretsSigner: getKeypair and multiChainEncryptionFor now
  recognize Tron AND Bitcoin accounts (isTronBased/isBitcoinBased both
  threaded through, checked before the Ethereum fallback).
- AccountDataSourceImpl: both address-backfill migrations now run
  sequentially in one coroutine (Tron's fix for a race against the legacy
  migration applies equally to Bitcoin's backfill).
- CloudBackup: kept Tron's forward-looking Solana schema reservation
  alongside Tron's and Bitcoin's now-real fields.
- ChainRegistryModule/NodeHealthStateTesterFactory: adopted Tron's
  self-contained short-lived OkHttpClient (simpler than threading the
  shared app-wide client through RuntimeDependencies, which is reverted).
- RealSecretsMetaAccount: extended Tron's chain-account MultiChainEncryption
  fix to also cover Bitcoin, for the same underlying reason.
- ChainExt.isValidAddress: Tron's branch independently closed the
  pre-existing Tron validation gap; kept alongside Bitcoin's own check.
This commit is contained in:
2026-07-12 19:02:08 -07:00
62 changed files with 2851 additions and 265 deletions
@@ -9,6 +9,7 @@ import io.novafoundation.nova.runtime.ext.isCustomNetwork
import io.novafoundation.nova.runtime.ext.isDisabled
import io.novafoundation.nova.runtime.ext.isEnabled
import io.novafoundation.nova.runtime.ext.selectedUnformattedWssNodeUrlOrNull
import io.novafoundation.nova.runtime.ext.httpNodes
import io.novafoundation.nova.runtime.ext.wssNodes
import io.novafoundation.nova.runtime.multiNetwork.ChainRegistry
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
@@ -132,7 +133,13 @@ class RealNetworkManagementChainInteractor(
}
private fun nodesHealthState(chain: Chain, coroutineScope: CoroutineScope): Flow<List<NodeHealthState>> {
return chain.nodes.wssNodes().map {
// wssNodes() alone leaves an HTTPS-only chain (Tron today - no wss endpoint at all) with an empty list
// here, which silently renders as "nothing to show" rather than a real health state - fall back to the
// http nodes only when there are no wss ones, since a chain that genuinely has wss nodes should still
// prefer testing those.
val nodesToCheck = chain.nodes.wssNodes().ifEmpty { chain.nodes.httpNodes() }
return nodesToCheck.map {
nodeHealthState(chain, it, coroutineScope)
}.combine()
}
@@ -61,6 +61,12 @@ class RealNetworkListAdapterItemFactory(
private fun getConnectingState(network: NetworkState): ConnectionStateModel? {
if (network.chain.isDisabled) return null
// Tron chains never get a ChainConnection/SocketService (TronGrid is a plain REST API, not
// WSS JSON-RPC - see ChainRegistry.registerConnection()), so connectionState is always the
// Disconnected default here, never Connected. Treat that as "nothing to show" rather than
// falling into the generic "Connecting" state below, which would otherwise spin forever.
if (network.chain.isTronBased) return null
return when (network.connectionState) {
is SocketStateMachine.State.Connected -> null