Skip to content

feat(dashpay): create-username form reflects contested-vote and marketplace reality - #965

Merged
QuantumExplorer merged 2 commits into
developfrom
claude/qa-iphone16-sim-launch-c02e04
Aug 10, 2026
Merged

feat(dashpay): create-username form reflects contested-vote and marketplace reality#965
QuantumExplorer merged 2 commits into
developfrom
claude/qa-iphone16-sim-launch-c02e04

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Aug 10, 2026

Copy link
Copy Markdown
Member

Problem

On the Create Username form, a contested-eligible name already mid-vote ("greg" during QA) showed a green Username available plus the generic "This name requires a masternode vote" — nothing said a vote was running, when it ends, or whether it can still be joined. A name listed for sale in the Username Marketplace showed a dead-end Username taken. The form also re-ran the DPNS availability check on every balance/readiness publish (constant while syncing), and re-teased Private registration to users who had just declined it on the readiness interstitial.

What changed

The availability check now runs the same contestPrecheck the marketplace search row uses (#955) and states each case:

Network state Form now shows
Vote running, join window open "voting ends around Today 20:33 — submitting joins the vote as a contender", Continue enabled
Join window closed (end − 45 min testnet / − 1 week mainnet, mirrors rs-platform-version allow_other_contenders_time) red "new contenders can no longer join", Continue disabled — a submit could only fail at broadcast
Vote ended, awaiting finalization "Vote ended … result is being finalized"
Own pending request unchanged "in voting" row — now also answered from the SDK contested-names cache when the bookmark is gone
Taken but listed for sale blue For sale callout with the asking price
Plainly taken the contradictory "requires a masternode vote" callout no longer shows

Correctness / load fixes underneath:

  • Label normalization: contestPrecheck and nameState normalize before querying — the vote-poll and marketplace indexes key on the normalized label, so "Greg" reported fresh while "greg" was mid-vote (verified against live testnet: the raw query returns 0 contenders, the normalized one returns the running contest). Also fixes the latent capitalized-input miss in the fix(dashpay): contested search row reflects vote state, not just document absence #955 search row.
  • One check per label: balance/credits/shielded-readiness publishes no longer cancel-and-restart the DPNS check; they only refresh the cost rules. A one-shot re-check fires at the contest's join-close and end boundaries, and each screen visit re-verifies once.
  • isContestedNameLocked removed — the precheck subsumes it (single vote-state query answers locked/active/fresh).
  • contenderJoinDeadline is added verbatim from feat(wallet): retry stuck asset-lock transfers from the tx detail sheet #949 so the merge dedupes.

Also: arriving at the form via the readiness interstitial (checklist or the explicit "Use transparent balance instead" escape) no longer re-teases Private registration — that question was just answered.

Verification

  • Clean dashpay simulator build.
  • Testnet smoke on the QA sim against live contests: "Greg" mid-vote showed the deadline copy; after the join window closed the form flipped to blocked; "Quantumtester2" (listed for sale at 1.4 DASH) showed the For sale callout; recheck churn during sync is gone.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Improved username registration checks for contested, finalized, locked, and marketplace-listed names.
    • Added contest contender counts and deadlines, with automatic validation updates as deadlines approach.
    • Added “For sale” details and sale prices for taken usernames.
    • Streamlined readiness flows by skipping previously answered shielded-funding guidance.
  • Bug Fixes
    • Improved username label matching and validation result reuse.
    • Added clearer messages when contender participation is no longer available.
  • Documentation
    • Added localized text for contest states, deadlines, participation, and marketplace listings.

…tplace reality

Typing a contested-eligible name that is already mid-vote showed a green
"Username available" plus a generic "requires a masternode vote" —
nothing said a vote was running, when it ends, or whether it can still
be joined. Typing a name listed for sale showed a dead-end "taken".

The availability check now runs the same contestPrecheck the search row
uses (#955) and the form states each case:

- vote running, join window open: "voting ends around <deadline> —
  submitting joins the vote as a contender" (deadline from the
  current-contests listing, "Today 20:33"-style formatting)
- join window closed (poll end − 45 min testnet / − 1 week mainnet,
  mirroring rs-platform-version allow_other_contenders_time): red
  "new contenders can no longer join" and Continue disabled — a submit
  could only fail at broadcast
- vote ended, awaiting finalization: "Vote ended … being finalized"
- own pending request: unchanged "in voting" row, now also answered
  from the SDK contested-names cache when the bookmark is gone
- taken but listed in the marketplace: "For sale" callout with the
  price instead of a bare "taken"
- plainly taken: the contradictory "requires a masternode vote"
  callout no longer shows — an owned name will never go to a vote

Correctness and load fixes underneath:

- contestPrecheck and nameState normalize the label before querying;
  the vote-poll and marketplace indexes key on the normalized form, so
  "Greg" reported fresh while "greg" was mid-vote (also fixes the
  latent capitalized-input miss in the #955 marketplace row)
- the DPNS availability check runs once per typed label instead of on
  every balance/credits/readiness publish (constant during sync); a
  one-shot re-check fires at the contest's join-close and end
  boundaries, and each screen visit re-verifies once
- isContestedNameLocked is removed — the precheck subsumes it

Also: arriving at the form through the readiness interstitial (its
shielded checklist or the explicit "Use transparent balance instead"
escape) no longer re-teases Private registration.

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

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@QuantumExplorer, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 30 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a9a5bbf7-74e3-4245-b5ea-950d805d91e9

📥 Commits

Reviewing files that changed from the base of the PR and between 0d08e57 and 1627871.

📒 Files selected for processing (2)
  • DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewController.swift
  • DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewModel.swift
📝 Walkthrough

Walkthrough

Username registration now normalizes marketplace labels, tracks contest deadlines and sale listings, caches validation results, refreshes at contest boundaries, and renders state-specific guidance. Readiness flows can suppress the shielded-registration hint.

Changes

Username registration flow

Layer / File(s) Summary
Contest deadlines and normalized lookups
DashWallet/Sources/Infrastructure/SwiftDashSDK/UsernameMarketplaceService.swift, DashWallet/Sources/Infrastructure/SwiftDashSDK/Identity/DWIdentityRegistrationCoordinator.swift
Marketplace lookups normalize labels and return contest end dates. Contender deadlines use network-specific intervals. The contested-name lock helper was removed.
Contest-aware validation and revalidation
DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewModel.swift
Validation caches same-label results, classifies contest states, performs sale lookups, publishes contest metadata, and schedules boundary revalidation.
Contest and marketplace guidance
DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewController.swift, DashWallet/en.lproj/Localizable.strings
The username form displays contest, deadline, finalized-result, and marketplace messages with localized text.
Readiness-flow hint suppression
DashWallet/Sources/UI/Home/HomeViewController+Shortcuts.swift, DashWallet/Sources/UI/Menu/Main/MainMenuViewController.swift, DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewController.swift
Readiness navigation passes a suppression flag so the username form can hide the shielded-registration hint.

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

Sequence Diagram(s)

sequenceDiagram
  participant ReadinessFlow
  participant CreateUsernameViewController
  participant CreateUsernameViewModel
  participant UsernameMarketplaceService
  participant DashSDK

  ReadinessFlow->>CreateUsernameViewController: open form with shielded hint suppressed
  CreateUsernameViewController->>CreateUsernameViewModel: validate username
  CreateUsernameViewModel->>UsernameMarketplaceService: request availability and contest data
  UsernameMarketplaceService->>DashSDK: query normalized contest state
  DashSDK-->>UsernameMarketplaceService: contest count and end date
  UsernameMarketplaceService-->>CreateUsernameViewModel: contest and marketplace results
  CreateUsernameViewModel-->>CreateUsernameViewController: publish validation state and deadlines
  CreateUsernameViewController-->>ReadinessFlow: render registration guidance
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: updating the Create Username form for contested-name voting and marketplace states.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/qa-iphone16-sim-launch-c02e04

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: 2

🧹 Nitpick comments (3)
DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewController.swift (2)

472-501: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Move contest-state classification into CreateUsernameViewModel.

showContestedWarning, activeContestHasEnded, and activeContestJoinClosed are domain-state decisions. Publish one contest-status value from CreateUsernameViewModel, then render that value here. This keeps the View lightweight and prevents display logic from drifting from submission gating.

As per coding guidelines, keep SwiftUI views lightweight and put business logic in @MainActor ObservableObject ViewModels.

🤖 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/CreateUsernameViewController.swift`
around lines 472 - 501, Move the contest-state classification out of the view by
adding a single published contest-status value to CreateUsernameViewModel, with
cases covering the current warning, ended, and join-closed states. Compute it
using the existing contest properties and deadline rules, then update
CreateUsernameViewController to render that value instead of maintaining
showContestedWarning, activeContestHasEnded, and activeContestJoinClosed.

Source: Coding guidelines


187-188: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Update the contested-warning comment.

A contested-eligible name that is plainly taken does not show this warning. State that exception in the comment.

As per coding guidelines, comments must describe behavior that the code actually implements.

🤖 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/CreateUsernameViewController.swift`
around lines 187 - 188, Update the comment associated with showContestedWarning
and contestedNameWarning to state that the warning applies to contested-eligible
names except when the name is plainly taken. Keep the implementation unchanged
and ensure the comment accurately describes the existing behavior.

Source: Coding guidelines

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

121-133: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Resolve these SwiftLint warnings.

Remove = nil from the optional declarations at lines 121–133 and 532–534. Change the activeContest pattern to case let .activeContest(contenders, endsAt) at line 559.

🤖 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 121 - 133, Resolve SwiftLint warnings in CreateUsernameViewModel by
removing redundant “= nil” initializers from the optional declarations
activeContestContenders, activeContestEndsAt, takenNameSalePriceDuffs, and the
optionals around lines 532–534. Update the activeContest switch pattern to use
case let .activeContest(contenders, endsAt) while preserving existing behavior.

Sources: Coding guidelines, Linters/SAST tools

🤖 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/en.lproj/Localizable.strings`:
- Around line 4285-4310: Propagate the nine username-voting localization keys
shown in en.lproj/Localizable.strings to all 42 non-English locale catalogs,
preserving each catalog’s existing translations or using the English values
where translations are unavailable. Ensure every updated Localizable.strings
file remains UTF-8 encoded without a BOM.

In
`@DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewController.swift`:
- Around line 314-323: Update the “Submit anyway” flow around performSubmit() to
recheck whether the active contest is still joinable before submitting. If the
join window has closed, dismiss the alert and refresh username availability
instead of calling performSubmit(); preserve the existing submission path while
the contest remains joinable.

---

Nitpick comments:
In
`@DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewController.swift`:
- Around line 472-501: Move the contest-state classification out of the view by
adding a single published contest-status value to CreateUsernameViewModel, with
cases covering the current warning, ended, and join-closed states. Compute it
using the existing contest properties and deadline rules, then update
CreateUsernameViewController to render that value instead of maintaining
showContestedWarning, activeContestHasEnded, and activeContestJoinClosed.
- Around line 187-188: Update the comment associated with showContestedWarning
and contestedNameWarning to state that the warning applies to contested-eligible
names except when the name is plainly taken. Keep the implementation unchanged
and ensure the comment accurately describes the existing behavior.

In
`@DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewModel.swift`:
- Around line 121-133: Resolve SwiftLint warnings in CreateUsernameViewModel by
removing redundant “= nil” initializers from the optional declarations
activeContestContenders, activeContestEndsAt, takenNameSalePriceDuffs, and the
optionals around lines 532–534. Update the activeContest switch pattern to use
case let .activeContest(contenders, endsAt) while preserving existing behavior.
🪄 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: 253107d1-f6b5-467a-8f4f-f829c80d3da2

📥 Commits

Reviewing files that changed from the base of the PR and between 766b680 and 0d08e57.

📒 Files selected for processing (7)
  • DashWallet/Sources/Infrastructure/SwiftDashSDK/Identity/DWIdentityRegistrationCoordinator.swift
  • DashWallet/Sources/Infrastructure/SwiftDashSDK/UsernameMarketplaceService.swift
  • DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewController.swift
  • DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewModel.swift
  • DashWallet/Sources/UI/Home/HomeViewController+Shortcuts.swift
  • DashWallet/Sources/UI/Menu/Main/MainMenuViewController.swift
  • DashWallet/en.lproj/Localizable.strings
💤 Files with no reviewable changes (1)
  • DashWallet/Sources/Infrastructure/SwiftDashSDK/Identity/DWIdentityRegistrationCoordinator.swift

Comment thread DashWallet/en.lproj/Localizable.strings
…-window submit guard

- Move activeContestHasEnded / activeContestJoinClosed /
  showContestedWarning from CreateUsernameView into the view model, so
  the protocol-derived join-window rule and callout policy live next to
  the submission gating (SwiftUI-first guideline).
- "Submit anyway" re-checks joinability at the moment of confirmation:
  the alert can sit open across the join-window boundary, and a
  submission the network would refuse is now replaced by a fresh
  availability answer instead of a broadcast failure.
- State the plainly-taken exception in the contested-warning comment.
- SwiftLint: drop redundant "= nil" optional initializers, use
  `case let .activeContest(...)` (pattern_matching_keywords).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@QuantumExplorer
QuantumExplorer merged commit b129b18 into develop Aug 10, 2026
2 checks passed
QuantumExplorer added a commit that referenced this pull request Aug 10, 2026
…merges (#967)

develop stopped compiling: #958's branch and #965 each carried the
identical contenderJoinDeadline(voteEnd:) helper, and their squash
merges landed both copies in UsernameMarketplaceService ("invalid
redeclaration"). Keep one.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
QuantumExplorer added a commit that referenced this pull request Aug 10, 2026
* fix(dashpay): remove duplicated contenderJoinDeadline after parallel merges

develop stopped compiling: #958's branch and #965 each carried the
identical contenderJoinDeadline(voteEnd:) helper, and their squash
merges landed both copies in UsernameMarketplaceService ("invalid
redeclaration"). Keep one.

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

* fix(ui): a Core self-send shows the amount it moved, not the net-change 0

A 1.2 DASH internal transfer between the wallet's own transparent
addresses rendered "0 Đ": the net-change model deliberately derives 0
for a move (every input and output is owned), and unlike the
to-Shielded / to-Platform / identity asset locks there was no recorded
amount to override it. Promote the Watch payload's reconstruction to
the phone display, fee-EXCLUSIVE to match .sent rows: the owned-output
total IS the moved amount of a self-send; owned inputs minus fee are
the fallback while the TXO join hasn't reconciled. CoinJoin mixing
rows keep their deliberate net semantics, and an unreconstructable
amount stays 0 rather than guessing.

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

* fix(ui): reconstruct only asset-lock moved amounts; self-sends keep net 0

QA falsified the owned-output reconstruction immediately: the "1.2
self-send" was actually an untracked asset lock, and owned outputs are
its ~825 change, not the moved amount (credit outputs live in the lock
payload, not in wallet TXOs). For an asset lock the locked amount IS
derivable: owned inputs − owned outputs − fee. Plain .moved self-sends
go back to the honest net-change 0 — destination-vs-change is not
derivable from owned totals.

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

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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