feat(dashpay): create-username form reflects contested-vote and marketplace reality - #965
Conversation
…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>
|
Warning Review limit reached
Next review available in: 30 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughUsername 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. ChangesUsername registration flow
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
DashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewController.swift (2)
472-501: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftMove contest-state classification into
CreateUsernameViewModel.
showContestedWarning,activeContestHasEnded, andactiveContestJoinClosedare domain-state decisions. Publish one contest-status value fromCreateUsernameViewModel, 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
@MainActorObservableObject 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 winUpdate 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 winResolve these SwiftLint warnings.
Remove
= nilfrom the optional declarations at lines 121–133 and 532–534. Change theactiveContestpattern tocase 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
📒 Files selected for processing (7)
DashWallet/Sources/Infrastructure/SwiftDashSDK/Identity/DWIdentityRegistrationCoordinator.swiftDashWallet/Sources/Infrastructure/SwiftDashSDK/UsernameMarketplaceService.swiftDashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewController.swiftDashWallet/Sources/UI/DashPay/Setup/CreateUsername/CreateUsernameViewModel.swiftDashWallet/Sources/UI/Home/HomeViewController+Shortcuts.swiftDashWallet/Sources/UI/Menu/Main/MainMenuViewController.swiftDashWallet/en.lproj/Localizable.strings
💤 Files with no reviewable changes (1)
- DashWallet/Sources/Infrastructure/SwiftDashSDK/Identity/DWIdentityRegistrationCoordinator.swift
…-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>
* 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>
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
contestPrecheckthe marketplace search row uses (#955) and states each case:allow_other_contenders_time)Correctness / load fixes underneath:
contestPrecheckandnameStatenormalize 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.isContestedNameLockedremoved — the precheck subsumes it (single vote-state query answers locked/active/fresh).contenderJoinDeadlineis 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
dashpaysimulator build.🤖 Generated with Claude Code
Summary by CodeRabbit