From f6447d202018bae3b5451c0c1deb0d4d0affae5e Mon Sep 17 00:00:00 2001 From: M Date: Mon, 2 Mar 2026 19:43:24 +1100 Subject: [PATCH] feat: wire Namecoin search into SearchBarViewModel Resolve .bit, d/, and id/ identifiers from the search bar via ElectrumX blockchain lookups. Typing any Namecoin identifier format (m@testls.bit, testls.bit, d/testls, id/alice) now queries the Namecoin blockchain and shows the resolved user at the top of results. The namecoinResolvedUser flow in SearchBarViewModel detects Namecoin identifiers, resolves them through NamecoinNameService, and prepends the result to the standard local cache search results. Updated docs with search integration details and manual testing guide. --- .../loggedIn/search/SearchBarViewModel.kt | 38 +++++++++++++- docs/namecoin-nip05-design.md | 50 ++++++++++++++++--- 2 files changed, 80 insertions(+), 8 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchBarViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchBarViewModel.kt index 8004ccdde0..e712185e2a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchBarViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchBarViewModel.kt @@ -32,8 +32,11 @@ import androidx.lifecycle.viewModelScope import com.vitorpamplona.amethyst.commons.ui.feeds.InvalidatableContent import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache +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.ui.dal.DefaultFeedOrder +import com.vitorpamplona.quartz.nip05.namecoin.NamecoinNameResolver import com.vitorpamplona.quartz.nip10Notes.content.findHashtags import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.FlowPreview @@ -43,7 +46,9 @@ import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update @@ -69,12 +74,41 @@ class SearchBarViewModel( val searchDataSourceState = SearchQueryState(MutableStateFlow(searchValue), account) + /** + * Resolves Namecoin identifiers (.bit / d/ / id/) via ElectrumX and + * returns the matching [User] from LocalCache, or null. + */ + private val namecoinResolvedUser = + searchValueFlow + .debounce(400) + .distinctUntilChanged() + .filter { NamecoinNameResolver.isNamecoinIdentifier(it) } + .map { term -> + try { + val result = NamecoinNameService.getInstance().resolve(term) + if (result != null) { + LocalCache.getOrCreateUser(result.pubkey) + } else { + null + } + } catch (_: Exception) { + null + } + }.flowOn(Dispatchers.IO) + .stateIn(viewModelScope, WhileSubscribed(5000), null) + val searchResultsUsers = combine( searchValueFlow.debounce(100), invalidations.debounce(100), - ) { term, version -> - LocalCache.findUsersStartingWith(term, account) + namecoinResolvedUser, + ) { term, version, namecoinUser -> + val localResults = LocalCache.findUsersStartingWith(term, account) + if (namecoinUser != null && localResults.none { it.pubkeyHex == namecoinUser.pubkeyHex }) { + listOf(namecoinUser) + localResults + } else { + localResults + } }.flowOn(Dispatchers.IO) .stateIn(viewModelScope, WhileSubscribed(5000), emptyList()) diff --git a/docs/namecoin-nip05-design.md b/docs/namecoin-nip05-design.md index 468c9f5f19..8289ac9d20 100644 --- a/docs/namecoin-nip05-design.md +++ b/docs/namecoin-nip05-design.md @@ -154,6 +154,17 @@ The integration is minimal and non-invasive: 3. Non-Namecoin identifiers are completely unaffected 4. **`AppModules`** wires up the resolver at construction time +## Search Integration + +The search bar resolves Namecoin identifiers in real-time via `SearchBarViewModel`: + +1. A `namecoinResolvedUser` flow watches the search input with a 400ms debounce +2. If the input matches any Namecoin format (`d/*`, `id/*`, `*.bit`, `*@*.bit`), it resolves via `NamecoinNameService` → `ElectrumxClient` → blockchain +3. The resolved pubkey is used to get/create a `User` in `LocalCache` +4. The Namecoin-resolved user is prepended to the standard local search results (deduplicated) + +This means typing `alice@example.bit`, `example.bit`, `d/example`, or `id/alice` into the search bar will query the Namecoin blockchain and show the resolved user profile at the top of results. + ## Default ElectrumX Server ``` @@ -202,6 +213,7 @@ Search bar integration. When a user types a `.bit` identifier, shows a loading s ### Modified files - `amethyst/.../AppModules.kt` — Wire up `NamecoinNameResolver` into `Nip05Client` +- `amethyst/.../ui/screen/loggedIn/search/SearchBarViewModel.kt` — Namecoin search resolution - `quartz/.../nip05DnsIdentifiers/Nip05Client.kt` — Route `.bit` identifiers to Namecoin resolver - `amethyst/.../relays/RelayInformationScreen.kt` — Import reordering (spotless) @@ -212,13 +224,34 @@ Search bar integration. When a user types a `.bit` identifier, shows a loading s ./gradlew :quartz:jvmTest --tests "*NamecoinNameResolverTest*" ``` -### Manual testing (emulator) -1. Build and install: `./gradlew :amethyst:installFdroidDebug` -2. Search for `m@testls.bit` — should resolve to pubkey `6cdebcca...18667d` -3. Search for `testls.bit` — root domain lookup -4. Search for `d/testls` — direct namespace format +### Manual testing (emulator or device) -### Live verification +Build and install the debug APK: +```bash +./gradlew assemblePlayDebug +adb install -r amethyst/build/outputs/apk/play/debug/amethyst-play-universal-debug.apk +``` + +**Search bar tests** — open the search bar and enter each of these: + +| Search query | Expected result | What it tests | +|---|---|---| +| `m@testls.bit` | Resolves to Vitor Pamplona's profile | NIP-05 style `user@domain.bit` | +| `testls.bit` | Resolves to Vitor Pamplona's profile (root `_` entry) | Bare domain `.bit` lookup | +| `d/testls` | Resolves to Vitor Pamplona's profile | Direct `d/` namespace | +| `id/someuser` | Resolves if registered on-chain | Direct `id/` namespace | + +**Verification test** — if a profile has a `.bit` address in its `nip05` field, the NIP-05 badge should verify via the blockchain instead of HTTP. + +**Network verification** — to confirm ElectrumX calls are being made: +```bash +# Monitor traffic to ElectrumX ports on the emulator +adb root +adb shell tcpdump -i any -nn port 50002 or port 50006 +``` +You should see TCP connections to `162.212.154.52:50002` (electrumx.testls.space) when searching for `.bit` identifiers. + +### Live test data The name `d/testls` is registered on the Namecoin blockchain (block 551519+, last updated block 814278) with value: ```json { @@ -229,3 +262,8 @@ The name `d/testls` is registered on the Namecoin blockchain (block 551519+, las } } ``` + +This means: +- `m@testls.bit` → resolves `m` entry → pubkey `6cdebcca...18667d` +- `testls.bit` → resolves root `_` entry → falls back to first available entry +- `d/testls` → same as `testls.bit` (root lookup)