Skip to content

feat(dashpay): prompt for a temporary username alongside a contested submission - #989

Merged
QuantumExplorer merged 3 commits into
developfrom
claude/dashpay-temp-username-contest-289441
Aug 11, 2026
Merged

feat(dashpay): prompt for a temporary username alongside a contested submission#989
QuantumExplorer merged 3 commits into
developfrom
claude/dashpay-temp-username-contest-289441

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Aug 11, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

A contested DPNS name is only preregistered — it belongs to nobody until masternode voting resolves (~90 min testnet, ~2 weeks mainnet), so a first-time DashPay signup that picks one leaves the user with no reachable username for the entire wait. This PR prompts the user, at the moment they confirm a contested submission, to also register a non-contested temporary username they keep permanently — making it clear that when the contest closes in their favor they will be reachable at both usernames.

What was done?

  • Contested confirmation alert → sheet (CreateUsernameViewController.swift): the "Submit anyway" alert is now ContestedNameConfirmationSheet. It keeps the existing vote-wait / locked-Dash warning (both wording variants, and the last-moment join-window re-check), and adds a temporary-username field seeded with a guaranteed non-contested suggestion (<name>2 — any digit 2–9 takes a label out of the contested character set). Primary action "Submit both usernames"; skipping is an explicit secondary action; swipe-down cancels.

  • Live validation for the companion label (CreateUsernameViewModel.swift): same length/character rules as the main field, a must-not-be-contested gate (a contested companion would silently open a second vote), and a debounced DPNS availability check, surfaced through the existing ValidationCheck row styling.

  • One authorized flow (DWIdentityRegistrationCoordinator.swift): startCreateUsername gains an optional temporaryUsername. The companion registers right after the contested submission's bookkeeping — same identity, same signer, no second PIN prompt — with a pre-flight guard rejecting invalid pairings before any money moves. Companion failure is deliberately non-fatal (the contested submission already succeeded) and surfaces as a partial-outcome note in the "Username submitted" alert.

  • Mirror semantics: on completion the temporary name becomes the DWGlobalOptions active username while the contested label stays deferred as before. finalizeWon only backfills an empty mirror, so a vote win adds the contested name to the identity without displacing the temporary one — both end up live. The voting banner keeps working since it keys off the pending-contest bookmark, not the mirror.

  • Bridge threading (DWIdentityRegistrationBridge.swift): pendingTemporaryUsername follows the preferredFundingSource lifecycle (written by the form before submit, preserved across .failed so retry keeps it, cleared on .completed). The invitation-claim path passes the parameter directly.

  • Nine new user-facing strings added to en.lproj/Localizable.strings.

  • Post-submission surface (UsernameRequestStatusScreen.swift, d41f38ac1): the prompt originally existed only inside the signup flow, so an identity whose first username was already in a vote (submitted on an older build, or with the prompt skipped) was never offered a reachable name. The request-status screen now carries an "Add a temporary username" section — same shared validated field (TemporaryUsernameFieldModel, extracted so the two surfaces can't drift), registering to the existing identity via UsernameMarketplaceService.register(label:). Shown only while the vote is unresolved and the identity owns no other username; #if DASHPAY-gated because the screen also compiles in the dashwallet target.

  • Copy correction: the confirmation previously claimed "Your Dash will be locked until voting completes" — false: the contest fee prefunds the vote poll's specialized balance, whose leftover is swept into the network's processing pools at poll end (rs-drive-abci clean_up_after_contested_resources_vote_polls_end); nothing returns to the contender. Both variants now say the fee is spent at submission and not returned.

How Has This Been Tested?

  • Clean build of the dashpay scheme (arm64 iOS Simulator), no new warnings in touched files.
  • Not smoke-tested end-to-end on testnet: exercising the sheet requires an identity-less wallet holding ≥0.25 tDASH, which the standing QA simulators (which hold identities) can't provide without destroying their state. QA should verify: (1) the sheet appears on Continue for a contested name with the suggestion pre-validated, (2) after submitting both, the profile shows the temporary username immediately while the home banner still shows the voting state, (3) a vote win adds the contested name without replacing the temporary one.
  • The unit-test target is broken pre-existing (per CLAUDE.md), so no new tests were added.

Breaking Changes

None.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have made corresponding changes to the documentation

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added support for choosing an optional temporary username when a preferred username is contested.
    • Temporary usernames are validated for availability and eligibility before registration.
    • Users can accept suggestions, enter their own name, skip the option, or cancel.
    • Registration results now show whether the temporary username was registered successfully.
  • Bug Fixes
    • Preserved temporary username details when retrying failed registrations.
    • Cleared invalid or outdated temporary usernames before submission.

…submission

A contested DPNS name is only preregistered — it belongs to nobody until
masternode voting resolves (~90 min testnet, ~2 weeks mainnet), so a
first-time DashPay signup that picks one has no reachable username for
the whole wait. The contested confirmation alert is now a sheet that
prompts the user to register a non-contested companion ("temporary")
username to the same identity in the same authorized flow: one PIN
prompt, same signer, registered right after the contested submission.
The sheet seeds a guaranteed non-contested suggestion (any digit 2-9
takes a label out of the contested character set), validates it with
the same local rules plus a debounced DPNS availability check, and
makes skipping an explicit secondary action. The copy states the name
stays theirs permanently and that a vote win makes them reachable at
both usernames.

The companion registers via the coordinator's new optional
temporaryUsername parameter (threaded through the bridge as
pendingTemporaryUsername with the preferredFundingSource lifecycle, so
a retry keeps it; the invitation path passes it directly). Its failure
is deliberately non-fatal — the contested submission has already
succeeded — and surfaces as a partial-outcome note in the voting
alert. On completion the companion becomes the DWGlobalOptions mirror
username while the contested label stays deferred; finalizeWon only
backfills an empty mirror, so a later vote win adds the second name
instead of displacing the first.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The username setup flow now supports an optional temporary username for contested registrations. The UI validates and submits both names. The bridge and coordinator preserve state across retries and report temporary registration outcomes.

Changes

Temporary Username Registration

Layer / File(s) Summary
Registration state and execution
DashWallet/Sources/Infrastructure/SwiftDashSDK/Identity/DWIdentityRegistrationBridge.swift, DashWallet/Sources/Infrastructure/SwiftDashSDK/Identity/DWIdentityRegistrationCoordinator.swift
The bridge sanitizes, forwards, preserves, and clears the temporary username. The coordinator validates, registers, mirrors, reports, and resets temporary username state.
Companion username validation and submission
DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewModel.swift
The view model validates companion usernames, checks DPNS availability, generates suggestions, and forwards the value through invitation and bridge registration flows.
Contested username confirmation UI
DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewController.swift, DashWallet/en.lproj/Localizable.strings
The confirmation sheet collects, validates, submits, skips, and reports temporary username results. English localization covers the new workflow and errors.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CreateUsernameViewController
  participant CreateUsernameViewModel
  participant DWIdentityRegistrationBridge
  participant DWIdentityRegistrationCoordinator
  participant IdentitySigner

  CreateUsernameViewController->>CreateUsernameViewModel: Submit primary and temporary usernames
  CreateUsernameViewModel->>DWIdentityRegistrationBridge: Store temporary username
  CreateUsernameViewModel->>DWIdentityRegistrationBridge: Start registration
  DWIdentityRegistrationBridge->>DWIdentityRegistrationCoordinator: Forward temporary username
  DWIdentityRegistrationCoordinator->>IdentitySigner: Register contested primary username
  DWIdentityRegistrationCoordinator->>IdentitySigner: Register temporary username
  IdentitySigner-->>DWIdentityRegistrationCoordinator: Return registration outcome
  DWIdentityRegistrationCoordinator-->>CreateUsernameViewController: Report temporary registration result
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: prompting for a temporary username during contested DPNS name submission.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/dashpay-temp-username-contest-289441

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewModel.swift (1)

514-525: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

registrationOutcome uses the single-slot pendingLabel and can drop the contested outcome.

DWContestedNameStatusService.pendingLabel returns only the OLDEST in-flight contested label for the active network; pendingLabels is the multi-label store. If the wallet already holds an older unresolved contested submission, pendingLabel returns that older label, labelsMatch fails, and this function returns .success.

The consequence is now larger than before: the form shows the "Username registered" alert for a name that is only submitted for voting, and both registeredTemporaryUsername and temporaryUsernameError are discarded, so the user never learns whether the companion name landed.

DWIdentityRegistrationCoordinator.handlePhaseChange decides the same question with DWContestedNameStatusService.shared.isPendingLabel(username) (Line 1328). Use the same predicate here so the two consumers agree.

🐛 Proposed fix
     private func registrationOutcome(for username: String) -> UsernameRegistrationOutcome {
-        guard let pending = DWContestedNameStatusService.shared.pendingLabel,
-              DWContestedNameStatusService.labelsMatch(pending, username) else {
+        guard DWContestedNameStatusService.shared.isPendingLabel(username) else {
             return .success
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewModel.swift`
around lines 514 - 525, Update registrationOutcome(for:) to determine pending
status with DWContestedNameStatusService.shared.isPendingLabel(username),
matching the predicate used by
DWIdentityRegistrationCoordinator.handlePhaseChange. Preserve the existing
.success and .submittedForVoting outcomes, including the coordinator’s temporary
username fields.
🧹 Nitpick comments (6)
DashWallet/Sources/Infrastructure/SwiftDashSDK/Identity/DWIdentityRegistrationBridge.swift (2)

189-197: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Log the temporary username in retry as well.

retry captures temporaryUsername and forwards it to the coordinator, but the log line omits it. startCreateUsername logs it on Line 168. The retry path is the one where the preserved value matters most, so the asymmetry hides the state that is actually used.

♻️ Proposed fix
-        Self.logger.info("🪪 IDENT-BRIDGE :: retry username=\(username, privacy: .public) funding=\(source.logLabel, privacy: .public)")
+        Self.logger.info("🪪 IDENT-BRIDGE :: retry username=\(username, privacy: .public) funding=\(source.logLabel, privacy: .public) temporary=\(temporaryUsername ?? "none", privacy: .public)")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@DashWallet/Sources/Infrastructure/SwiftDashSDK/Identity/DWIdentityRegistrationBridge.swift`
around lines 189 - 197, Update the retry log in the identity registration bridge
to include the captured temporaryUsername value, matching the diagnostic
information logged by startCreateUsername while preserving the existing username
and funding fields.

136-143: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the redundant @objc attribute.

The class is annotated @objcMembers, so @objc on this stored property is redundant. SwiftLint reports redundant_objc_attribute on Line 143.

♻️ Proposed fix
-    `@objc` public var pendingTemporaryUsername: String?
+    public var pendingTemporaryUsername: String?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@DashWallet/Sources/Infrastructure/SwiftDashSDK/Identity/DWIdentityRegistrationBridge.swift`
around lines 136 - 143, Remove the redundant `@objc` attribute from the
pendingTemporaryUsername property; the enclosing `@objcMembers` class already
exposes it to Objective-C.

Source: Linters/SAST tools

DashWallet/Sources/Infrastructure/SwiftDashSDK/Identity/DWIdentityRegistrationCoordinator.swift (2)

848-865: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The isContestedSubmission condition is unreachable as a false branch.

The guard at Lines 461-468 already rejects a non-nil temporaryUsername when username is not contested, so temporaryUsername != nil implies isContestedSubmission == true at Line 848. The extra term is harmless, but it suggests a second, independent rule that does not exist. Keep it only if you intend it as a defensive assertion, and say so in the comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@DashWallet/Sources/Infrastructure/SwiftDashSDK/Identity/DWIdentityRegistrationCoordinator.swift`
around lines 848 - 865, Simplify the conditional in the temporary DPNS
registration block around temporaryUsernameError so it checks only for a non-nil
temporaryUsername; the earlier validation already guarantees contested status.
Remove the redundant isContestedSubmission term, or explicitly document it as an
intentional defensive assertion if retaining it.

448-455: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

SwiftLint reports a cyclomatic_complexity error on startCreateUsername.

The rule is violated at Line 449 with a complexity of 36 against a limit of 10, and the tool reports it at error severity. The new temporary-username guard and Step 3.6 branch add to an already large function. If the lint run gates the build, this fails it.

Extract the funding-source branch (Lines 657-722) and the contested post-registration work (Lines 812-865) into private methods to bring the count down.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@DashWallet/Sources/Infrastructure/SwiftDashSDK/Identity/DWIdentityRegistrationCoordinator.swift`
around lines 448 - 455, Reduce cyclomatic complexity in startCreateUsername by
extracting the funding-source branch into a private helper and the contested
post-registration work into another private helper. Preserve the existing
control flow, parameters, return behavior, and error handling while updating
startCreateUsername to delegate to those helpers; keep the temporary-username
guard and Step 3.6 behavior unchanged.

Source: Linters/SAST tools

DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewModel.swift (2)

527-543: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The truncation branch is unreachable for a contested label.

DWContestedNameStatusService.isContestedLabel only returns true for labels of 19 characters or fewer, and prepareTemporaryUsernameSuggestion runs from the contested confirmation sheet. DW_MAX_USERNAME_LENGTH is 23, so suggestion.count >= DW_MAX_USERNAME_LENGTH at Line 539 cannot be true on that path. Keep the guard as defensive code, but state that intent in the comment so a reader does not look for the case that triggers it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewModel.swift`
around lines 527 - 543, Clarify the inline comment on the truncation branch in
prepareTemporaryUsernameSuggestion to state that it is defensive/unreachable for
contested labels because the contested-name flow limits their length, while
retaining the existing truncation behavior.

545-586: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared local username rules.

Lines 555-563 repeat the length, illegal-character, and hyphen-placement checks from validateUsername (Lines 646-648). Lines 569-586 repeat the debounce, availability call, and stale-result guard from checkIfBlocked (Lines 744-746 and 841-843). Two copies of the same rules will drift when one side changes, and the two fields must stay consistent because both feed the same DPNS registration.

Extract one private helper that returns the local verdict for a trimmed label, and call it from both validators.

♻️ Sketch of the shared helper
+    /// Local-only DPNS label rules shared by the main field and the
+    /// companion field. Returns nil when the label passes every rule.
+    private func localRuleFailure(for label: String) -> TemporaryUsernameCheck? {
+        guard label.count >= DW_MIN_USERNAME_LENGTH && label.count <= DW_MAX_USERNAME_LENGTH else {
+            return .invalidLength
+        }
+        guard label.rangeOfCharacter(from: illegalChars) == nil,
+              label.first != "-", label.last != "-" else {
+            return .invalidCharacters
+        }
+        return nil
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewModel.swift`
around lines 545 - 586, Extract the duplicated local validation rules from
validateTemporaryUsername and validateUsername into one private helper that
accepts a trimmed label and returns the shared local verdict, including empty,
length, character, hyphen, and contested-name outcomes. Update both validators
to use this helper and preserve their existing debounce, availability, and
stale-result handling for labels that pass local validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@DashWallet/Sources/Infrastructure/SwiftDashSDK/Identity/DWIdentityRegistrationBridge.swift`:
- Around line 296-299: Update the phase-handling logic around the registration
flow so pendingTemporaryUsername is cleared when registration is cancelled and
when processing legacy plain submissions that did not intentionally set it.
Preserve the property only through an intentional retry, and retain the existing
completed-phase cleanup in the relevant identity registration method.

In
`@DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewController.swift`:
- Around line 961-982: Update the temporary-username initialization flow around
prepareTemporaryUsernameSuggestion and validateTemporaryUsername so the seeded
value is passed through local validation synchronously when the sheet appears,
immediately setting temporaryUsernameCheck to .checking and showing the
validation row. Preserve the existing throttled/debounced availability request
for the eventual server result.
- Around line 1004-1007: Update the temporary username suggestion flow around
prepareTemporaryUsernameSuggestion so it re-seeds when the current main username
differs from the username used for the last seeding, rather than returning
solely because temporaryUsername is non-empty. Track the last-seeded main
username in CreateUsernameViewModel and preserve the existing cached suggestion
only when it was generated for the current main username.

---

Outside diff comments:
In
`@DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewModel.swift`:
- Around line 514-525: Update registrationOutcome(for:) to determine pending
status with DWContestedNameStatusService.shared.isPendingLabel(username),
matching the predicate used by
DWIdentityRegistrationCoordinator.handlePhaseChange. Preserve the existing
.success and .submittedForVoting outcomes, including the coordinator’s temporary
username fields.

---

Nitpick comments:
In
`@DashWallet/Sources/Infrastructure/SwiftDashSDK/Identity/DWIdentityRegistrationBridge.swift`:
- Around line 189-197: Update the retry log in the identity registration bridge
to include the captured temporaryUsername value, matching the diagnostic
information logged by startCreateUsername while preserving the existing username
and funding fields.
- Around line 136-143: Remove the redundant `@objc` attribute from the
pendingTemporaryUsername property; the enclosing `@objcMembers` class already
exposes it to Objective-C.

In
`@DashWallet/Sources/Infrastructure/SwiftDashSDK/Identity/DWIdentityRegistrationCoordinator.swift`:
- Around line 848-865: Simplify the conditional in the temporary DPNS
registration block around temporaryUsernameError so it checks only for a non-nil
temporaryUsername; the earlier validation already guarantees contested status.
Remove the redundant isContestedSubmission term, or explicitly document it as an
intentional defensive assertion if retaining it.
- Around line 448-455: Reduce cyclomatic complexity in startCreateUsername by
extracting the funding-source branch into a private helper and the contested
post-registration work into another private helper. Preserve the existing
control flow, parameters, return behavior, and error handling while updating
startCreateUsername to delegate to those helpers; keep the temporary-username
guard and Step 3.6 behavior unchanged.

In
`@DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewModel.swift`:
- Around line 527-543: Clarify the inline comment on the truncation branch in
prepareTemporaryUsernameSuggestion to state that it is defensive/unreachable for
contested labels because the contested-name flow limits their length, while
retaining the existing truncation behavior.
- Around line 545-586: Extract the duplicated local validation rules from
validateTemporaryUsername and validateUsername into one private helper that
accepts a trimmed label and returns the shared local verdict, including empty,
length, character, hyphen, and contested-name outcomes. Update both validators
to use this helper and preserve their existing debounce, availability, and
stale-result handling for labels that pass local validation.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5eb57659-8db8-4971-aea1-4df471aadfe1

📥 Commits

Reviewing files that changed from the base of the PR and between 916ac4a and 1499b99.

📒 Files selected for processing (5)
  • DashWallet/Sources/Infrastructure/SwiftDashSDK/Identity/DWIdentityRegistrationBridge.swift
  • DashWallet/Sources/Infrastructure/SwiftDashSDK/Identity/DWIdentityRegistrationCoordinator.swift
  • DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewController.swift
  • DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewModel.swift
  • DashWallet/en.lproj/Localizable.strings

QuantumExplorer and others added 2 commits August 12, 2026 03:21
- Sanitize the bridge's pendingTemporaryUsername at read time: a
  PIN-cancelled attempt never reaches the .completed cleanup and the
  legacy DWDashPayModel.createUsername: entry point never writes the
  property, so a stale companion could fail an unrelated submission at
  the coordinator's pre-flight pairing guard. Values that don't pair
  validly with the submitted label are dropped and cleared (logged);
  an intentional retry of the same contested attempt still keeps it.
- Validate the seeded companion suggestion synchronously so the sheet's
  rule row and primary button don't sit blank/disabled through the
  pipeline's 500 ms throttle; a temporaryCheckLabel cache (mirroring
  availabilityCheckLabel) keeps the later same-label emission from
  restarting the availability check.
- Re-seed the companion suggestion when the main username changed since
  the last seeding, instead of carrying a name derived from an
  abandoned contested label into the next confirmation sheet.
- registrationOutcome now checks membership across all pending
  contested entries (isPendingLabel) instead of the single-slot oldest
  pendingLabel, so an older unresolved contest can't make a new
  contested submission read as a completed registration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sion; correct the fee copy

Two gaps from testing the flow against a wallet already waiting on a
vote:

- The temporary-username prompt only existed inside the signup flow. An
  identity whose FIRST username went to a vote — submitted on an older
  build, or with the prompt skipped — had no surface offering it a
  reachable name. UsernameRequestStatusScreen now carries an "Add a
  temporary username" section (DASHPAY-gated; the screen also compiles
  in the dashwallet target): explainer, validated field with the seeded
  suggestion, and a Register action through
  UsernameMarketplaceService.register(label:) against the existing
  identity (own PIN gate; PIN cancel is a silent no-op; failures alert).
  Shown only while the vote is unresolved and the identity owns no
  other username; flips to a confirmation on success.

- The contested confirmation claimed "Your Dash will be locked until
  voting completes." That is false: the contest fee prefunds the vote
  poll's specialized balance and its leftover is swept into the
  network's processing pools at poll end (rs-drive-abci
  clean_up_after_contested_resources_vote_polls_end) — nothing is
  returned to the contender, win or lose. Both message variants now say
  the fee is spent at submission and not returned.

To keep the two surfaces from drifting, the companion-name validation
moved out of CreateUsernameViewModel into a shared
TemporaryUsernameFieldModel (input pipeline, local rules, non-contested
gate, debounced availability, suggestion seeding) with a shared
TemporaryUsernameField view; the confirmation sheet and the status
screen both bind to it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@QuantumExplorer
QuantumExplorer merged commit d218877 into develop Aug 11, 2026
3 checks passed
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.

1 participant