feat(dashpay): identity public keys page, Enable DashPay flow with intro & FAQ - #941
Conversation
The identity detail's "Public keys" row was a bare count. It now pushes a Public Keys page listing every persisted DPP key: key id, purpose, security level, key type, read-only/disabled badges, the copyable key material, contract bounds, and the DIP-9 derivation path when known. Keys project into immutable row models at reload time (same no-live-@model contract as the identity rows). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Identities registered before the contact-key policy (or restored with an incomplete key set) can't receive contact requests: DIP-15 ECDH needs an enabled ECDSA ENCRYPTION/DECRYPTION pair on both sides. The Contacts tab now shows an "Enable DashPay" banner when the main identity's persisted keys lack the pair. Tapping opens a confirm sheet explaining that two keys will be added so others can send contact requests, with the fee-schedule-derived cost estimate in DASH and local currency (identity_update min fee + 2x identity_key_in_creation_cost, paid from the identity's credit balance). Confirming runs the PIN gate and the existing DWIdentityKeyUpgrader IdentityUpdate (no-op if Platform already has both keys). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
DIP-15 ECDH is required on both sides, so until the identity has its contact-key pair the add-contact toolbar button and empty-state CTA are hidden — the Enable DashPay banner is the call to action — and the banner subtitle says "can't send or receive". Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Vertical fee card with wrap-enabled value (the fiat estimate truncated in the 360pt detent), taller detent, non-clipping body text, and the body now ends "This is a one time event." Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Until the identity has its contact-key pair, the tab reads "DashPay" instead of "Contacts" and pitches the experience: usernames over addresses, end-to-end-encrypted contact requests, per-contact history, and a coming-soon note for Shielded DashPay and pay-to-non-contact. A Learn More sheet answers FAQ items (what DashPay is, why enabling is needed, privacy, cost, roadmap). The Enable CTA opens the existing fee-confirm sheet; the banner it replaces is gone. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The intro card and FAQ now say it plainly: payment addresses travel in an encrypted payload and are never published, but contact requests themselves are currently public (anyone can see two identities are connected). Private contact requests join Shielded DashPay and pay-to-non-contact in the coming-soon list. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… caveat Payments to contacts go to unpublished encrypted-exchanged addresses, so they aren't trivially linkable to the username — but they're still transparent-chain payments and chain analysis may leak information; Shielded DashPay (coming soon) closes that gap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
"Private payment addresses are exchanged between you and your contacts. Only you and your contact know the recipient and sender of payments between yourselves." The shielded/chain-analysis nuance stays in the FAQ's privacy answer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…vices Three new privacy-comparison answers: Bitcoin (open address sharing and reuse vs private per-contact rotating addresses; chain analysis applies to both), Ethereum accounts (one reusable address with a public history, ENS pointing straight at it, vs names resolving to identities), and Unstoppable-Domains-style name services (name -> fixed public address vs a username that never publicly resolves to a payment address). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Item.id was a fresh UUID per body evaluation, so any unrelated publish rebuilt the ForEach and collapsed open DisclosureGroups mid-read. The question text is the stable identity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
"When will contact requests become private?" — in development, expected around September-October 2026; explains today's public-request / encrypted-payload split and what changes when they ship. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per owner wording: once private contact requests ship, you will have the choice of having your friendship be public or private. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A successful enable now presents "DashPay enabled" with a "Send your first contact request" CTA that chains into AddContactScreen (via onDismiss, so the two sheets don't collide), plus a Not-now dismiss. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The post-send confirmation was a 1.5s thin capsule at the sheet bottom - easy to miss entirely. Success now shows a centered card with a green checkmark and "Contact request sent to <username>" for 2.5s; the row spinner continues to cover the in-flight window. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Warning Review limit reached
Next review available in: 4 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 (4)
📝 WalkthroughWalkthroughDashPay contacts now support key readiness checks, PIN-gated enablement, onboarding, FAQs, fee confirmation, and first-contact requests. Contact requests show recipient-specific success feedback. Identity details now expose persisted public-key metadata and key material. ChangesDashPay enablement and contacts
Identity public-key details
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant ContactsScreen
participant SwiftDashSDKContactsService
participant IdentityKeyUpgrader
participant EnableDashPaySuccessSheet
participant AddContactScreen
ContactsScreen->>SwiftDashSDKContactsService: submit PIN-gated enablement
SwiftDashSDKContactsService->>IdentityKeyUpgrader: ensure DashPay keys
IdentityKeyUpgrader-->>SwiftDashSDKContactsService: return upgrade result
SwiftDashSDKContactsService-->>ContactsScreen: report enablement outcome
ContactsScreen->>EnableDashPaySuccessSheet: present success options
EnableDashPaySuccessSheet->>AddContactScreen: open optional first-contact request
Suggested reviewers: 🚥 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: 4
🧹 Nitpick comments (1)
DashWallet/Sources/UI/DashPay/Contacts/SwiftUI/AddContactScreen.swift (1)
39-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove the redundant optional initializer.
SwiftLint reports
implicit_optional_initializationon Line 41. Declare the state as@State private var sentToUsername: String?; it already initializes tonil.🤖 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/Contacts/SwiftUI/AddContactScreen.swift` around lines 39 - 41, Remove the explicit nil initializer from the sentToUsername state declaration, leaving its optional type to provide the default nil initialization.Source: 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/Sources/Infrastructure/SwiftDashSDK/Contacts/SwiftDashSDKContactsService.swift`:
- Around line 772-780: Derive the enable-DashPay fee estimate from the current
missing-key count returned by mainIdentityNeedsDashPayKeys(), or explicitly
present it as a maximum. Update ContactsScreen.swift at lines 57-65 to display
the matching estimate, and at lines 717-742 to select singular or plural copy
for one versus two missing keys. Update Localizable.strings at lines 2302-2303
and 2332-2333 to remove fixed two-key wording and support partial upgrades or
neutral maximum-estimate wording.
In `@DashWallet/Sources/UI/DashPay/Contacts/SwiftUI/AddContactScreen.swift`:
- Around line 337-339: Update the success-card state flow around
`sentToUsername` so the delayed cleanup only clears the message when it still
belongs to the same recipient. Assign the captured `username` value when showing
the success state, then guard the later nil assignment against the current
recipient (or cancel the prior dismissal task) so an earlier request cannot
clear a newer success message.
In `@DashWallet/Sources/UI/DashPay/Contacts/SwiftUI/ContactsScreen.swift`:
- Around line 217-218: Update
DashWallet/Sources/UI/DashPay/Contacts/SwiftUI/ContactsScreen.swift at lines
217-218 and 246-248 to use adaptive sheet detents instead of fixed heights. Wrap
the success-sheet VStack at lines 641-689 and confirmation-sheet VStack at lines
703-777 in ScrollView so all content and actions remain reachable at large
Dynamic Type sizes.
In `@DashWallet/Sources/UI/Menu/Security/Wallets/IdentitiesScreen.swift`:
- Line 703: Update both Button declarations in IdentitiesScreen.swift at lines
703-703 and 773-773 to use the labeled label: closure form instead of trailing
label closures, preserving the existing back-button and copy-button actions and
views.
---
Nitpick comments:
In `@DashWallet/Sources/UI/DashPay/Contacts/SwiftUI/AddContactScreen.swift`:
- Around line 39-41: Remove the explicit nil initializer from the sentToUsername
state declaration, leaving its optional type to provide the default nil
initialization.
🪄 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: d5f56640-5b62-4673-a52f-0522e005fe19
📒 Files selected for processing (6)
DashWallet/Sources/Infrastructure/SwiftDashSDK/Contacts/SwiftDashSDKContactsService.swiftDashWallet/Sources/UI/DashPay/Contacts/SwiftUI/AddContactScreen.swiftDashWallet/Sources/UI/DashPay/Contacts/SwiftUI/ContactsScreen.swiftDashWallet/Sources/UI/Menu/Security/Wallets/IdentitiesScreen.swiftDashWallet/Sources/UI/Menu/Security/Wallets/IdentitiesViewModel.swiftDashWallet/en.lproj/Localizable.strings
- Fee estimate and copy sized to the keys actually missing: missingDashPayKeyCount() (0-2) replaces the boolean check, the schedule estimate takes the count, and the confirm/FAQ copy no longer hardcodes "two keys". - Enable/success sheets scroll and use adaptive detents so every action stays reachable at large Dynamic Type sizes. - A send-success card is only cleared by its own recipient's timer, so back-to-back requests can't hide a newer card early. - SwiftLint: labeled label: closures on the new multi-closure Buttons; drop the redundant optional initializer on sentToUsername. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Issue being fixed or feature implemented
Identity keys were invisible and unfixable from the UI:
6836db798) lack the DIP-15 ENCRYPTION/DECRYPTION pair, so they can neither send nor receive contact requests — and nothing in the UI said so or offered a fix. The lazyDWIdentityKeyUpgraderrepair only fired buried inside a contact action.What was done?
Identity detail → Public Keys page — the "Public keys" row now pushes a page listing every persisted DPP key: key id, purpose, security level, key type, read-only/disabled badges, copyable key material, contract bounds, and the DIP-9 derivation path when known. Keys project into immutable row models at reload time (same no-live-
@Modelcontract as the identity rows).Enable DashPay flow —
SwiftDashSDKContactsServicegainsmainIdentityNeedsDashPayKeys()(local-store check for an enabled ECDSA ENCRYPTION + DECRYPTION pair on the main identity) and a PIN-gatedenableDashPay()that reuses the existingDWIdentityKeyUpgraderIdentityUpdate (which re-checks Platform's authoritative key set first, so it's a no-op when the keys already exist). The confirm sheet shows a fee estimate derived from the platform fee schedule (identity_updatemin fee + 2 ×identity_key_in_creation_costfrom rs-platform-versionstate_transition_min_feesv1 ≈ 0.000131 DASH) in DASH and the user's local currency, labeled as an estimate and noted as paid from the identity's credit balance.Pre-enable DashPay intro — until the identity has its key pair, the tab reads "DashPay" instead of "Contacts" and pitches the experience: usernames over addresses, private payments (owner-approved copy), per-contact history, and a coming-soon card (private contact requests, Shielded DashPay, pay-to-non-contact). Add-contact affordances are hidden until enabled — the Enable CTA is the single call to action.
Learn More FAQ — expandable Q&As: what DashPay is, why enabling is needed, an honest privacy answer (payments not trivially linkable but not shielded; chain analysis caveat; contact requests currently public), the private-contact-requests timeline (expected September–October 2026, with public/private friendship choice), privacy comparisons against Bitcoin, Ethereum accounts, and Unstoppable-Domains-style name services, cost, and roadmap. FAQ rows use the question text as stable identity so open answers survive body re-evaluations (a per-rebuild
UUID()id was collapsing them mid-read).Success feedback — a successful enable presents a "DashPay enabled" sheet whose "Send your first contact request" CTA chains into AddContactScreen (via
onDismiss, so the sheets don't collide). A successful contact request now shows a centered card with a green checkmark and "Contact request sent to " for 2.5s instead of the missable capsule.How Has This Been Tested?
Manual, on mainnet against a real pre-policy identity (registered 2026-07-13 with only 4 keys — the exact cohort this targets):
Clean
dashpaybuild at every step. (The unit-test target is pre-existing broken; not run.)Breaking Changes
None.
Checklist:
For repository code-owners and collaborators only
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
UI Improvements