mirror of
https://github.com/pezkuwichain/pezkuwi-wallet-android.git
synced 2026-07-22 21:55:53 +00:00
feat: Bitcoin API client, balance reading, and node health checks
Adds a mempool.space-style REST client (RealBitcoinApi, with the same retry-on-429 pattern as TronGridApi), a polling AssetBalance implementation, and BitcoinNodeHealthStateTester so the Networks screen can show live health for Bitcoin nodes (mirrors TronNodeHealthStateTester's rationale: mempool.space speaks plain REST, not JSON-RPC, so the existing Ethereum/ Substrate testers can't be reused). Wires Chain.Asset.Type.BitcoinNative through the asset-type mappers and TypeBasedAssetSourceRegistry, and BitcoinAssetsModule into AssetsModule - transfers/history stay on the Unsupported stubs until send support lands.
This commit is contained in:
@@ -37,6 +37,7 @@ import io.novafoundation.nova.runtime.multiNetwork.runtime.types.BaseTypeSynchro
|
||||
import io.novafoundation.nova.runtime.multiNetwork.runtime.types.TypesFetcher
|
||||
import io.novasama.substrate_sdk_android.wsrpc.SocketService
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.logging.HttpLoggingInterceptor
|
||||
import org.web3j.protocol.http.HttpService
|
||||
import javax.inject.Provider
|
||||
@@ -160,12 +161,14 @@ class ChainRegistryModule {
|
||||
socketProvider: Provider<SocketService>,
|
||||
bulkRetriever: BulkRetriever,
|
||||
connectionSecrets: ConnectionSecrets,
|
||||
web3ApiFactory: Web3ApiFactory
|
||||
web3ApiFactory: Web3ApiFactory,
|
||||
httpClient: OkHttpClient,
|
||||
) = NodeHealthStateTesterFactory(
|
||||
socketProvider,
|
||||
connectionSecrets,
|
||||
bulkRetriever,
|
||||
web3ApiFactory
|
||||
web3ApiFactory,
|
||||
httpClient
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -10,11 +10,14 @@ import io.novafoundation.nova.core_db.dao.ChainAssetDao
|
||||
import io.novafoundation.nova.core_db.dao.ChainDao
|
||||
import io.novafoundation.nova.core_db.dao.StorageDao
|
||||
import io.novasama.substrate_sdk_android.wsrpc.SocketService
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
interface RuntimeDependencies {
|
||||
|
||||
fun networkApiCreator(): NetworkApiCreator
|
||||
|
||||
fun okHttpClient(): OkHttpClient
|
||||
|
||||
fun socketServiceCreator(): SocketService
|
||||
|
||||
fun gson(): Gson
|
||||
|
||||
@@ -560,6 +560,18 @@ fun Chain.requireTronGridBaseUrl(): String {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The mempool.space-style REST API base url for a Bitcoin-based chain - same rationale as [requireTronGridBaseUrl]:
|
||||
* Bitcoin has no JSON-RPC/WS node concept at all, so the configured `nodes` entry directly *is* the REST API base url.
|
||||
*/
|
||||
fun Chain.requireMempoolSpaceBaseUrl(): String {
|
||||
require(isBitcoinBased) { "Chain $id is not Bitcoin-based" }
|
||||
|
||||
return requireNotNull(nodes.nodes.minByOrNull { it.orderId }?.unformattedUrl) {
|
||||
"No mempool.space-style node configured for chain $id"
|
||||
}
|
||||
}
|
||||
|
||||
fun Chain.Asset.requireEquilibrium(): Type.Equilibrium {
|
||||
require(type is Type.Equilibrium)
|
||||
|
||||
|
||||
+2
@@ -12,6 +12,8 @@ const val ASSET_EVM_NATIVE = "evmNative"
|
||||
const val ASSET_TRON_NATIVE = "tronNative"
|
||||
const val ASSET_TRC20 = "trc20"
|
||||
|
||||
const val ASSET_BITCOIN_NATIVE = "bitcoinNative"
|
||||
|
||||
const val ASSET_EQUILIBRIUM = "equilibrium"
|
||||
const val ASSET_EQUILIBRIUM_ON_CHAIN_ID = "assetId"
|
||||
|
||||
|
||||
+2
@@ -69,6 +69,8 @@ fun mapChainAssetTypeToRaw(type: Chain.Asset.Type): Pair<String, Map<String, Any
|
||||
ASSET_EQUILIBRIUM_ON_CHAIN_ID to type.id.toString()
|
||||
)
|
||||
|
||||
Chain.Asset.Type.BitcoinNative -> ASSET_BITCOIN_NATIVE to null
|
||||
|
||||
Chain.Asset.Type.Unsupported -> ASSET_UNSUPPORTED to null
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -95,6 +95,8 @@ private fun mapChainAssetTypeFromRaw(type: String?, typeExtras: Map<String, Any?
|
||||
|
||||
ASSET_EQUILIBRIUM -> Chain.Asset.Type.Equilibrium((typeExtras!![ASSET_EQUILIBRIUM_ON_CHAIN_ID] as String).toBigInteger())
|
||||
|
||||
ASSET_BITCOIN_NATIVE -> Chain.Asset.Type.BitcoinNative
|
||||
|
||||
else -> Chain.Asset.Type.Unsupported
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,6 +139,12 @@ data class Chain(
|
||||
val contractAddress: String
|
||||
) : Type()
|
||||
|
||||
/**
|
||||
* Native BTC balance on a Bitcoin-based chain.
|
||||
* Balance is fetched from a mempool.space-style REST API rather than JSON-RPC.
|
||||
*/
|
||||
object BitcoinNative : Type()
|
||||
|
||||
object Unsupported : Type()
|
||||
}
|
||||
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package io.novafoundation.nova.runtime.multiNetwork.connection.node.healthState
|
||||
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import kotlin.time.ExperimentalTime
|
||||
import kotlin.time.measureTime
|
||||
|
||||
/**
|
||||
* mempool.space speaks a plain REST API, not Ethereum JSON-RPC - see [TronNodeHealthStateTester]'s doc for the
|
||||
* identical rationale this mirrors. `GET /blocks/tip/height` needs no account/address context and is cheap on
|
||||
* mempool.space's side, making it a good generic liveness ping.
|
||||
*/
|
||||
class BitcoinNodeHealthStateTester(
|
||||
private val node: Chain.Node,
|
||||
private val httpClient: OkHttpClient,
|
||||
) : NodeHealthStateTester {
|
||||
|
||||
@OptIn(ExperimentalTime::class)
|
||||
override suspend fun testNodeHealthState(): Result<Long> = withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val request = Request.Builder()
|
||||
.url("${node.unformattedUrl.trimEnd('/')}/blocks/tip/height")
|
||||
.build()
|
||||
|
||||
val duration = measureTime {
|
||||
httpClient.newCall(request).execute().use { response ->
|
||||
check(response.isSuccessful) { "HTTP ${response.code}" }
|
||||
}
|
||||
}
|
||||
|
||||
duration.inWholeMilliseconds
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
-5
@@ -5,6 +5,7 @@ import io.novafoundation.nova.runtime.ethereum.Web3ApiFactory
|
||||
import io.novafoundation.nova.runtime.multiNetwork.chain.model.Chain
|
||||
import io.novafoundation.nova.runtime.multiNetwork.connection.ConnectionSecrets
|
||||
import io.novasama.substrate_sdk_android.wsrpc.SocketService
|
||||
import okhttp3.OkHttpClient
|
||||
import javax.inject.Provider
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
||||
@@ -12,15 +13,21 @@ class NodeHealthStateTesterFactory(
|
||||
private val socketServiceProvider: Provider<SocketService>,
|
||||
private val connectionSecrets: ConnectionSecrets,
|
||||
private val bulkRetriever: BulkRetriever,
|
||||
private val web3ApiFactory: Web3ApiFactory
|
||||
private val web3ApiFactory: Web3ApiFactory,
|
||||
private val httpClient: OkHttpClient,
|
||||
) {
|
||||
|
||||
fun create(chain: Chain, node: Chain.Node, coroutineScope: CoroutineScope): NodeHealthStateTester {
|
||||
val nodeIsSupported = chain.nodes.nodes.any { it.unformattedUrl == node.unformattedUrl }
|
||||
require(nodeIsSupported)
|
||||
|
||||
return if (chain.hasSubstrateRuntime) {
|
||||
SubstrateNodeHealthStateTester(
|
||||
return when {
|
||||
chain.isBitcoinBased -> BitcoinNodeHealthStateTester(
|
||||
node = node,
|
||||
httpClient = httpClient
|
||||
)
|
||||
|
||||
chain.hasSubstrateRuntime -> SubstrateNodeHealthStateTester(
|
||||
chain = chain,
|
||||
socketService = socketServiceProvider.get(),
|
||||
connectionSecrets = connectionSecrets,
|
||||
@@ -28,8 +35,8 @@ class NodeHealthStateTesterFactory(
|
||||
node = node,
|
||||
coroutineScope = coroutineScope
|
||||
)
|
||||
} else {
|
||||
EthereumNodeHealthStateTester(
|
||||
|
||||
else -> EthereumNodeHealthStateTester(
|
||||
socketService = socketServiceProvider.get(),
|
||||
connectionSecrets = connectionSecrets,
|
||||
node = node,
|
||||
|
||||
Reference in New Issue
Block a user