Moves electrum client to the jvmAndroid scope
This commit is contained in:
@@ -68,6 +68,10 @@ import com.vitorpamplona.quartz.nip03Timestamp.VerificationStateCache
|
|||||||
import com.vitorpamplona.quartz.nip03Timestamp.ots.OtsBlockHeightCache
|
import com.vitorpamplona.quartz.nip03Timestamp.ots.OtsBlockHeightCache
|
||||||
import com.vitorpamplona.quartz.nip05DnsIdentifiers.Nip05Client
|
import com.vitorpamplona.quartz.nip05DnsIdentifiers.Nip05Client
|
||||||
import com.vitorpamplona.quartz.nip05DnsIdentifiers.OkHttpNip05Fetcher
|
import com.vitorpamplona.quartz.nip05DnsIdentifiers.OkHttpNip05Fetcher
|
||||||
|
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.DEFAULT_ELECTRUMX_SERVERS
|
||||||
|
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.ElectrumXClient
|
||||||
|
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinNameResolver
|
||||||
|
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.TOR_ELECTRUMX_SERVERS
|
||||||
import com.vitorpamplona.quartz.utils.Log
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -143,23 +147,20 @@ class AppModules(
|
|||||||
// Custom fetcher that considers tor settings and avoids forwarding.
|
// Custom fetcher that considers tor settings and avoids forwarding.
|
||||||
val nip05Fetcher = OkHttpNip05Fetcher(roleBasedHttpClientBuilder::okHttpClientForNip05)
|
val nip05Fetcher = OkHttpNip05Fetcher(roleBasedHttpClientBuilder::okHttpClientForNip05)
|
||||||
val namecoinElectrumxClient =
|
val namecoinElectrumxClient =
|
||||||
com.vitorpamplona.quartz.nip05.namecoin.ElectrumxClient(
|
ElectrumXClient(
|
||||||
socketFactory = { roleBasedHttpClientBuilder.socketFactoryForNip05() },
|
socketFactory = { roleBasedHttpClientBuilder.socketFactoryForNip05() },
|
||||||
)
|
)
|
||||||
val namecoinResolver =
|
val namecoinResolver =
|
||||||
com.vitorpamplona.quartz.nip05.namecoin.NamecoinNameResolver(
|
NamecoinNameResolver(
|
||||||
electrumxClient = namecoinElectrumxClient,
|
electrumxClient = namecoinElectrumxClient,
|
||||||
serverListProvider = {
|
serverListProvider = {
|
||||||
if (roleBasedHttpClientBuilder.shouldUseTorForNIP05("https://electrumx.example.com")) {
|
if (roleBasedHttpClientBuilder.shouldUseTorForNIP05("https://electrumx.example.com")) {
|
||||||
com.vitorpamplona.quartz.nip05.namecoin.ElectrumxClient.TOR_SERVERS
|
TOR_ELECTRUMX_SERVERS
|
||||||
} else {
|
} else {
|
||||||
com.vitorpamplona.quartz.nip05.namecoin.ElectrumxClient.DEFAULT_SERVERS
|
DEFAULT_ELECTRUMX_SERVERS
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
val namecoinNameService =
|
|
||||||
com.vitorpamplona.amethyst.service.namecoin.NamecoinNameService
|
|
||||||
.init(namecoinElectrumxClient)
|
|
||||||
val nip05Client = Nip05Client(nip05Fetcher, namecoinResolver)
|
val nip05Client = Nip05Client(nip05Fetcher, namecoinResolver)
|
||||||
|
|
||||||
// Application-wide block height request cache
|
// Application-wide block height request cache
|
||||||
|
|||||||
+6
-22
@@ -20,11 +20,11 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.service.namecoin
|
package com.vitorpamplona.amethyst.service.namecoin
|
||||||
|
|
||||||
import com.vitorpamplona.quartz.nip05.namecoin.ElectrumxClient
|
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.ElectrumXClient
|
||||||
import com.vitorpamplona.quartz.nip05.namecoin.ElectrumxServer
|
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.ElectrumxServer
|
||||||
import com.vitorpamplona.quartz.nip05.namecoin.NamecoinLookupCache
|
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinLookupCache
|
||||||
import com.vitorpamplona.quartz.nip05.namecoin.NamecoinNameResolver
|
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinNameResolver
|
||||||
import com.vitorpamplona.quartz.nip05.namecoin.NamecoinNostrResult
|
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinNostrResult
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
@@ -39,7 +39,7 @@ import kotlinx.coroutines.launch
|
|||||||
* into Amethyst's existing `ServiceManager` infrastructure.
|
* into Amethyst's existing `ServiceManager` infrastructure.
|
||||||
*/
|
*/
|
||||||
class NamecoinNameService(
|
class NamecoinNameService(
|
||||||
electrumxClient: ElectrumxClient = ElectrumxClient(),
|
electrumxClient: ElectrumXClient,
|
||||||
) {
|
) {
|
||||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||||
|
|
||||||
@@ -49,22 +49,6 @@ class NamecoinNameService(
|
|||||||
// Custom server list (user-configurable)
|
// Custom server list (user-configurable)
|
||||||
private var customServers: List<ElectrumxServer> = emptyList()
|
private var customServers: List<ElectrumxServer> = emptyList()
|
||||||
|
|
||||||
companion object {
|
|
||||||
@Volatile
|
|
||||||
private var instance: NamecoinNameService? = null
|
|
||||||
|
|
||||||
fun getInstance(): NamecoinNameService =
|
|
||||||
instance ?: throw IllegalStateException(
|
|
||||||
"NamecoinNameService not initialized. Call init() first.",
|
|
||||||
)
|
|
||||||
|
|
||||||
fun init(electrumxClient: ElectrumxClient): NamecoinNameService =
|
|
||||||
synchronized(this) {
|
|
||||||
instance?.let { return it }
|
|
||||||
NamecoinNameService(electrumxClient).also { instance = it }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Public API ─────────────────────────────────────────────────────
|
// ── Public API ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+3
-3
@@ -29,14 +29,14 @@ import androidx.compose.ui.focus.FocusRequester
|
|||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
import com.vitorpamplona.amethyst.commons.ui.feeds.InvalidatableContent
|
import com.vitorpamplona.amethyst.commons.ui.feeds.InvalidatableContent
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.LocalCache
|
import com.vitorpamplona.amethyst.model.LocalCache
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.namecoin.NamecoinNameService
|
|
||||||
import com.vitorpamplona.amethyst.service.relayClient.searchCommand.SearchQueryState
|
import com.vitorpamplona.amethyst.service.relayClient.searchCommand.SearchQueryState
|
||||||
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
|
||||||
import com.vitorpamplona.quartz.nip05.namecoin.NamecoinNameResolver
|
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinNameResolver
|
||||||
import com.vitorpamplona.quartz.nip10Notes.content.findHashtags
|
import com.vitorpamplona.quartz.nip10Notes.content.findHashtags
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.FlowPreview
|
import kotlinx.coroutines.FlowPreview
|
||||||
@@ -85,7 +85,7 @@ class SearchBarViewModel(
|
|||||||
.filter { NamecoinNameResolver.isNamecoinIdentifier(it) }
|
.filter { NamecoinNameResolver.isNamecoinIdentifier(it) }
|
||||||
.map { term ->
|
.map { term ->
|
||||||
try {
|
try {
|
||||||
val result = NamecoinNameService.getInstance().resolve(term)
|
val result = Amethyst.instance.namecoinResolver.resolve(term)
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
LocalCache.getOrCreateUser(result.pubkey)
|
LocalCache.getOrCreateUser(result.pubkey)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@
|
|||||||
package com.vitorpamplona.quartz.nip05DnsIdentifiers
|
package com.vitorpamplona.quartz.nip05DnsIdentifiers
|
||||||
|
|
||||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||||
import com.vitorpamplona.quartz.nip05.namecoin.NamecoinNameResolver
|
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinNameResolver
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
|
|
||||||
data class Nip05KeyInfo(
|
data class Nip05KeyInfo(
|
||||||
|
|||||||
+70
@@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin
|
||||||
|
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of an ElectrumX name_show query.
|
||||||
|
*
|
||||||
|
* Maps to the JSON fields returned by Namecoin Core / Electrum-NMC:
|
||||||
|
* { "name": "d/example", "value": "{...}", "txid": "abc...", "height": 12345, ... }
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class NameShowResult(
|
||||||
|
val name: String,
|
||||||
|
val value: String,
|
||||||
|
val txid: String? = null,
|
||||||
|
val height: Int? = null,
|
||||||
|
val expiresIn: Int? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a single ElectrumX server endpoint.
|
||||||
|
*/
|
||||||
|
data class ElectrumxServer(
|
||||||
|
val host: String,
|
||||||
|
val port: Int,
|
||||||
|
val useSsl: Boolean = true,
|
||||||
|
/** If true, accept any certificate (self-signed, expired, etc.) */
|
||||||
|
val trustAllCerts: Boolean = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Well-known public Namecoin ElectrumX servers (clearnet). */
|
||||||
|
val DEFAULT_ELECTRUMX_SERVERS =
|
||||||
|
listOf(
|
||||||
|
ElectrumxServer("electrumx.testls.space", 50002, useSsl = true, trustAllCerts = true),
|
||||||
|
ElectrumxServer("nmc2.bitcoins.sk", 57002, useSsl = true, trustAllCerts = true),
|
||||||
|
ElectrumxServer("46.229.238.187", 57002, useSsl = true, trustAllCerts = true),
|
||||||
|
)
|
||||||
|
|
||||||
|
/** Tor-preferred server list: onion primary, clearnet fallback. */
|
||||||
|
val TOR_ELECTRUMX_SERVERS =
|
||||||
|
listOf(
|
||||||
|
ElectrumxServer(
|
||||||
|
"i665jpwsq46zlsdbnj4axgzd3s56uzey5uhotsnxzsknzbn36jaddsid.onion",
|
||||||
|
50002,
|
||||||
|
useSsl = true,
|
||||||
|
trustAllCerts = true,
|
||||||
|
),
|
||||||
|
ElectrumxServer("electrumx.testls.space", 50002, useSsl = true, trustAllCerts = true),
|
||||||
|
ElectrumxServer("nmc2.bitcoins.sk", 57002, useSsl = true, trustAllCerts = true),
|
||||||
|
)
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Vitor Pamplona
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
* this software and associated documentation files (the "Software"), to deal in
|
||||||
|
* the Software without restriction, including without limitation the rights to use,
|
||||||
|
* copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||||
|
* Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
* subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||||
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin
|
||||||
|
|
||||||
|
interface IElectrumXClient {
|
||||||
|
suspend fun nameShowWithFallback(
|
||||||
|
identifier: String,
|
||||||
|
servers: List<ElectrumxServer>,
|
||||||
|
): NameShowResult?
|
||||||
|
}
|
||||||
+1
-1
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.quartz.nip05.namecoin
|
package com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin
|
||||||
|
|
||||||
import androidx.collection.LruCache
|
import androidx.collection.LruCache
|
||||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
+3
-3
@@ -18,7 +18,7 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.quartz.nip05.namecoin
|
package com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin
|
||||||
|
|
||||||
import kotlinx.coroutines.withTimeoutOrNull
|
import kotlinx.coroutines.withTimeoutOrNull
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
@@ -50,9 +50,9 @@ data class NamecoinNostrResult(
|
|||||||
* here instead of to the HTTP-based NIP-05 path.
|
* here instead of to the HTTP-based NIP-05 path.
|
||||||
*/
|
*/
|
||||||
class NamecoinNameResolver(
|
class NamecoinNameResolver(
|
||||||
private val electrumxClient: ElectrumxClient = ElectrumxClient(),
|
private val electrumxClient: IElectrumXClient,
|
||||||
private val lookupTimeoutMs: Long = 20_000L,
|
private val lookupTimeoutMs: Long = 20_000L,
|
||||||
private val serverListProvider: () -> List<ElectrumxServer> = { ElectrumxClient.DEFAULT_SERVERS },
|
private val serverListProvider: () -> List<ElectrumxServer> = { DEFAULT_ELECTRUMX_SERVERS },
|
||||||
) {
|
) {
|
||||||
private val json =
|
private val json =
|
||||||
Json {
|
Json {
|
||||||
+19
-63
@@ -18,15 +18,16 @@
|
|||||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.vitorpamplona.quartz.nip05.namecoin
|
package com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin
|
||||||
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
import kotlinx.coroutines.sync.withLock
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.builtins.ListSerializer
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
|
import kotlinx.serialization.json.JsonNull
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.JsonPrimitive
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
@@ -41,6 +42,8 @@ import java.io.PrintWriter
|
|||||||
import java.net.InetSocketAddress
|
import java.net.InetSocketAddress
|
||||||
import java.net.Socket
|
import java.net.Socket
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import javax.net.SocketFactory
|
import javax.net.SocketFactory
|
||||||
import javax.net.ssl.SSLContext
|
import javax.net.ssl.SSLContext
|
||||||
@@ -48,32 +51,6 @@ import javax.net.ssl.SSLSocketFactory
|
|||||||
import javax.net.ssl.TrustManager
|
import javax.net.ssl.TrustManager
|
||||||
import javax.net.ssl.X509TrustManager
|
import javax.net.ssl.X509TrustManager
|
||||||
|
|
||||||
/**
|
|
||||||
* Result of an ElectrumX name_show query.
|
|
||||||
*
|
|
||||||
* Maps to the JSON fields returned by Namecoin Core / Electrum-NMC:
|
|
||||||
* { "name": "d/example", "value": "{...}", "txid": "abc...", "height": 12345, ... }
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
data class NameShowResult(
|
|
||||||
val name: String,
|
|
||||||
val value: String,
|
|
||||||
val txid: String? = null,
|
|
||||||
val height: Int? = null,
|
|
||||||
val expiresIn: Int? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a single ElectrumX server endpoint.
|
|
||||||
*/
|
|
||||||
data class ElectrumxServer(
|
|
||||||
val host: String,
|
|
||||||
val port: Int,
|
|
||||||
val useSsl: Boolean = true,
|
|
||||||
/** If true, accept any certificate (self-signed, expired, etc.) */
|
|
||||||
val trustAllCerts: Boolean = false,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lightweight, query-only ElectrumX client for Namecoin name resolution.
|
* Lightweight, query-only ElectrumX client for Namecoin name resolution.
|
||||||
*
|
*
|
||||||
@@ -95,11 +72,11 @@ data class ElectrumxServer(
|
|||||||
* val result = client.nameShow("d/example", server)
|
* val result = client.nameShow("d/example", server)
|
||||||
* ```
|
* ```
|
||||||
*/
|
*/
|
||||||
class ElectrumxClient(
|
class ElectrumXClient(
|
||||||
private val connectTimeoutMs: Long = 10_000L,
|
private val connectTimeoutMs: Long = 10_000L,
|
||||||
private val readTimeoutMs: Long = 15_000L,
|
private val readTimeoutMs: Long = 15_000L,
|
||||||
private val socketFactory: () -> SocketFactory = { SocketFactory.getDefault() },
|
private val socketFactory: () -> SocketFactory = { SocketFactory.getDefault() },
|
||||||
) {
|
) : IElectrumXClient {
|
||||||
private val json =
|
private val json =
|
||||||
Json {
|
Json {
|
||||||
ignoreUnknownKeys = true
|
ignoreUnknownKeys = true
|
||||||
@@ -109,27 +86,6 @@ class ElectrumxClient(
|
|||||||
private val mutex = Mutex()
|
private val mutex = Mutex()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/** Well-known public Namecoin ElectrumX servers (clearnet). */
|
|
||||||
val DEFAULT_SERVERS =
|
|
||||||
listOf(
|
|
||||||
ElectrumxServer("electrumx.testls.space", 50002, useSsl = true, trustAllCerts = true),
|
|
||||||
ElectrumxServer("nmc2.bitcoins.sk", 57002, useSsl = true, trustAllCerts = true),
|
|
||||||
ElectrumxServer("46.229.238.187", 57002, useSsl = true, trustAllCerts = true),
|
|
||||||
)
|
|
||||||
|
|
||||||
/** Tor-preferred server list: onion primary, clearnet fallback. */
|
|
||||||
val TOR_SERVERS =
|
|
||||||
listOf(
|
|
||||||
ElectrumxServer(
|
|
||||||
"i665jpwsq46zlsdbnj4axgzd3s56uzey5uhotsnxzsknzbn36jaddsid.onion",
|
|
||||||
50002,
|
|
||||||
useSsl = true,
|
|
||||||
trustAllCerts = true,
|
|
||||||
),
|
|
||||||
ElectrumxServer("electrumx.testls.space", 50002, useSsl = true, trustAllCerts = true),
|
|
||||||
ElectrumxServer("nmc2.bitcoins.sk", 57002, useSsl = true, trustAllCerts = true),
|
|
||||||
)
|
|
||||||
|
|
||||||
private const val PROTOCOL_VERSION = "1.4"
|
private const val PROTOCOL_VERSION = "1.4"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -162,7 +118,7 @@ class ElectrumxClient(
|
|||||||
*/
|
*/
|
||||||
suspend fun nameShow(
|
suspend fun nameShow(
|
||||||
identifier: String,
|
identifier: String,
|
||||||
server: ElectrumxServer = DEFAULT_SERVERS.first(),
|
server: ElectrumxServer = DEFAULT_ELECTRUMX_SERVERS.first(),
|
||||||
): NameShowResult? =
|
): NameShowResult? =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
mutex.withLock {
|
mutex.withLock {
|
||||||
@@ -180,11 +136,11 @@ class ElectrumxClient(
|
|||||||
* Try each server in order until one succeeds.
|
* Try each server in order until one succeeds.
|
||||||
*
|
*
|
||||||
* @param identifier Full Namecoin name, e.g. "d/example"
|
* @param identifier Full Namecoin name, e.g. "d/example"
|
||||||
* @param servers Ordered server list to try; defaults to [DEFAULT_SERVERS]
|
* @param servers Ordered server list to try; defaults to [DEFAULT_ELECTRUMX_SERVERS]
|
||||||
*/
|
*/
|
||||||
suspend fun nameShowWithFallback(
|
override suspend fun nameShowWithFallback(
|
||||||
identifier: String,
|
identifier: String,
|
||||||
servers: List<ElectrumxServer> = DEFAULT_SERVERS,
|
servers: List<ElectrumxServer>,
|
||||||
): NameShowResult? {
|
): NameShowResult? {
|
||||||
for (server in servers) {
|
for (server in servers) {
|
||||||
val result = nameShow(identifier, server)
|
val result = nameShow(identifier, server)
|
||||||
@@ -329,7 +285,7 @@ class ElectrumxClient(
|
|||||||
private fun parseHistoryResponse(raw: String): List<Pair<String, Int>>? {
|
private fun parseHistoryResponse(raw: String): List<Pair<String, Int>>? {
|
||||||
val envelope = json.parseToJsonElement(raw).jsonObject
|
val envelope = json.parseToJsonElement(raw).jsonObject
|
||||||
val error = envelope["error"]
|
val error = envelope["error"]
|
||||||
if (error != null && error !is kotlinx.serialization.json.JsonNull) return null
|
if (error != null && error !is JsonNull) return null
|
||||||
|
|
||||||
val result = envelope["result"]?.jsonArray ?: return null
|
val result = envelope["result"]?.jsonArray ?: return null
|
||||||
return result.mapNotNull { entry ->
|
return result.mapNotNull { entry ->
|
||||||
@@ -354,7 +310,7 @@ class ElectrumxClient(
|
|||||||
): NameShowResult? {
|
): NameShowResult? {
|
||||||
val envelope = json.parseToJsonElement(raw).jsonObject
|
val envelope = json.parseToJsonElement(raw).jsonObject
|
||||||
val error = envelope["error"]
|
val error = envelope["error"]
|
||||||
if (error != null && error !is kotlinx.serialization.json.JsonNull) return null
|
if (error != null && error !is JsonNull) return null
|
||||||
|
|
||||||
val result = envelope["result"]?.jsonObject ?: return null
|
val result = envelope["result"]?.jsonObject ?: return null
|
||||||
val vouts = result["vout"]?.jsonArray ?: return null
|
val vouts = result["vout"]?.jsonArray ?: return null
|
||||||
@@ -505,20 +461,20 @@ class ElectrumxClient(
|
|||||||
arrayOf<TrustManager>(
|
arrayOf<TrustManager>(
|
||||||
object : X509TrustManager {
|
object : X509TrustManager {
|
||||||
override fun checkClientTrusted(
|
override fun checkClientTrusted(
|
||||||
chain: Array<java.security.cert.X509Certificate>,
|
chain: Array<X509Certificate>,
|
||||||
authType: String,
|
authType: String,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
override fun checkServerTrusted(
|
override fun checkServerTrusted(
|
||||||
chain: Array<java.security.cert.X509Certificate>,
|
chain: Array<X509Certificate>,
|
||||||
authType: String,
|
authType: String,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
override fun getAcceptedIssuers(): Array<java.security.cert.X509Certificate> = arrayOf()
|
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
val sslContext = SSLContext.getInstance("TLS")
|
val sslContext = SSLContext.getInstance("TLS")
|
||||||
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
|
sslContext.init(null, trustAllCerts, SecureRandom())
|
||||||
return sslContext.socketFactory
|
return sslContext.socketFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -535,8 +491,8 @@ class ElectrumxClient(
|
|||||||
put(
|
put(
|
||||||
"params",
|
"params",
|
||||||
json.encodeToJsonElement(
|
json.encodeToJsonElement(
|
||||||
kotlinx.serialization.builtins.ListSerializer(
|
ListSerializer(
|
||||||
kotlinx.serialization.json.JsonElement
|
JsonElement
|
||||||
.serializer(),
|
.serializer(),
|
||||||
),
|
),
|
||||||
params.map {
|
params.map {
|
||||||
+7
-1
@@ -20,6 +20,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.quartz.nip05.namecoin
|
package com.vitorpamplona.quartz.nip05.namecoin
|
||||||
|
|
||||||
|
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinNameResolver
|
||||||
|
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinNostrResult
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertFalse
|
import org.junit.Assert.assertFalse
|
||||||
import org.junit.Assert.assertNotNull
|
import org.junit.Assert.assertNotNull
|
||||||
@@ -267,7 +269,11 @@ class NamecoinNameResolverTest {
|
|||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
return NamecoinNostrResult(pubkey = pubkey.lowercase(), relays = relays, namecoinName = namecoinName)
|
return NamecoinNostrResult(
|
||||||
|
pubkey = pubkey.lowercase(),
|
||||||
|
relays = relays,
|
||||||
|
namecoinName = namecoinName,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
|
|||||||
Reference in New Issue
Block a user