Skip to content

Prompt for Unicity ID when deriving a new address#436

Merged
KruGoL merged 4 commits into
mainfrom
feat/derive-address-nametag-prompt
Jul 14, 2026
Merged

Prompt for Unicity ID when deriving a new address#436
KruGoL merged 4 commits into
mainfrom
feat/derive-address-nametag-prompt

Conversation

@KruGoL

@KruGoL KruGoL commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

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

  • Slide-in wallet screen (same presentation as onboarding 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.
  • Debounced availability check runs only for IDs the SDK would actually accept, so a green check can never precede a VALIDATION_ERROR.
  • Availability that cannot be verified (transport missing / disconnected / resolver error) shows an amber "Can't verify availability" state — never a false "is available". Registration stays allowed: the relay enforces uniqueness at publish time.
  • Skip / backdrop / Escape leave the address usable and ID-less (the header "Register ID" button remains the fallback); closing is blocked while deriving/registering, with a beforeunload guard during the Nostr publish.

Flow safety (useNewAddressFlow)

  • The switch is only trusted once getCurrentAddressIndex() reports the new index (identity:changed event + polling, 30s budget, "network is slow" hint). The SDK assigns sphere.identity after 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).
  • Recovered-nametag completion additionally requires a chainPubkey match; a 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.
  • Phone numbers are canonicalized to E.164 (same normalization as the SDK) for availability checks, registration and display.
  • Next index is computed over 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.
  • Entry-point wiring test for both hosts; new files added to the no-direct-in-ui guard test scan list.
  • 350/350 unit tests, tsc/build/lint clean.

Notes

  • The flow never renders DIRECT:// — the new address shows as Address #N + truncateId(chainPubkey).
  • Residual SDK-level caveat: a connected relay whose kind-30078 query times out still resolves null → "available" (SDK isNametagAvailable fail-open); wallet-side we already treat a disconnected transport and resolver errors as "can't verify". A proper fix belongs in the SDK.

KruGoL added 4 commits July 14, 2026 20:14
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.
@KruGoL KruGoL merged commit aed1567 into main Jul 14, 2026
7 checks passed
@KruGoL KruGoL deleted the feat/derive-address-nametag-prompt branch July 14, 2026 18:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Prompt for Unicity ID when deriving a new address — check availability via Nostr, offer registration

2 participants