Prompt for Unicity ID when deriving a new address#436
Merged
Conversation
Both derive entry points (AddressSelector "New", Address Manager "Derive New Address") now open a shared NewAddressModal instead of deriving silently: debounced availability check, Register / explicit Skip, and the dead inline nametag modal in AddressSelector is gone. Flow safety (useNewAddressFlow): - The switch is only trusted once getCurrentAddressIndex() reports the new index (identity:changed + polling, 30s budget) - the SDK assigns sphere.identity AFTER awaited network work, so the old 5s race-and-proceed pattern could read the PREVIOUS address identity and even register the ID onto the wrong key. - Recovered-nametag completion additionally requires a chainPubkey match; background nametag:recovered while the prompt is open completes the flow; ALREADY_INITIALIZED is treated as a lost recovery race. - register() refuses to run if the wallet moved off the new address. - Retry after a derivation failure reuses the same index (the SDK has already tracked it) instead of leaking one address per attempt. - UI validates with the SDK isValidNametag gate (no more green availability followed by VALIDATION_ERROR); phone numbers are canonicalized to E.164 for availability, registration and display. - Availability "unknown" (transport missing/disconnected/error) shows "can't verify" instead of falsely claiming the ID is free. - beforeunload guard while the Nostr binding is being published; Escape/backdrop close mirrors Skip and is blocked mid-work. Next index is computed over getAllTrackedAddresses() so a hidden address index is never re-derived. New files added to the no-direct-in-ui guard test; unit tests cover availability states, validation, skip, both recovery races, the slow-switch paths and the entry-point wiring (36 tests).
…#413) Same visual language as NametagScreen / RegisterNametagModal: wallet dialog chrome (WalletScreen asModal + ModalHeader), @unicity-suffixed input with availability-colored border (amber for cannot-verify), gradient Register with arrow, 'Skip for now' text button. Flow logic unchanged.
Same presentation as onboarding/RegisterNametagModal: WalletScreen slide-in inside the wallet panel with the screen-variant header, instead of a centered overlay dialog. AddressSelector renders it outside its relative wrapper so the panel shell is the positioned ancestor; portal removed.
The slide-in screen rendered from the header painted BELOW the wallet content (same z-10, earlier DOM order) - tab bar bled through. WalletPanel now owns the flow like RegisterNametagModal (rendered last in the panel); AddressSelector delegates via the new onNewAddress prop and keeps internal hosting only as a fallback.
igmahl
approved these changes
Jul 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #413.
Both derive entry points — AddressSelector "New" and Address Manager "Derive New Address" — now open a single shared Unicity ID prompt instead of deriving silently. The dead inline nametag modal in AddressSelector is removed.
UX
NametagScreen/RegisterNametagModal):@unicity-suffixed input, availability-colored border, gradient Register, Skip for now. Hosted at WalletPanel level (rendered last in the panel) so it paints above the wallet content; Address Manager hosts its own instance the same way.VALIDATION_ERROR.beforeunloadguard during the Nostr publish.Flow safety (
useNewAddressFlow)getCurrentAddressIndex()reports the new index (identity:changedevent + polling, 30s budget, "network is slow" hint). The SDK assignssphere.identityafter awaited network work, so the previous 5s race-and-proceed pattern could read the OLD address identity — and even register the ID onto the wrong key (permanent relay binding).chainPubkeymatch; a backgroundnametag:recoveredwhile the prompt is open completes the flow;ALREADY_INITIALIZEDis treated as a lost recovery race.register()refuses to run if the wallet moved off the new address.getAllTrackedAddresses()so a hidden index is never re-derived.Tests
useNewAddressFlow: availability states (incl. cannot-verify), SDK-aligned validation, skip, both recovery races, slow-switch paths (fake timers), retry index reuse, wrong-address guard.NewAddressModal: pubkey-first rendering, format hint, cannot-verify UX, taken state, skip, close blocked while registering, Escape.no-direct-in-uiguard test scan list.Notes
DIRECT://— the new address shows asAddress #N+truncateId(chainPubkey).null→ "available" (SDKisNametagAvailablefail-open); wallet-side we already treat a disconnected transport and resolver errors as "can't verify". A proper fix belongs in the SDK.