Skip to content

feat(dashpay): identity public keys page, Enable DashPay flow with intro & FAQ - #941

Merged
QuantumExplorer merged 15 commits into
developfrom
feat/identity-public-keys-screen
Aug 8, 2026
Merged

feat(dashpay): identity public keys page, Enable DashPay flow with intro & FAQ#941
QuantumExplorer merged 15 commits into
developfrom
feat/identity-public-keys-screen

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Aug 8, 2026

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

Identity keys were invisible and unfixable from the UI:

  • The identity detail's "Public keys" row was a bare count — no way to see what keys an identity actually holds.
  • Identities registered before the contact-key policy (2026-07-24, 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 lazy DWIdentityKeyUpgrader repair only fired buried inside a contact action.
  • The Contacts tab assumed a working DashPay identity, and the post-send confirmation was a 1.5s bottom capsule that was easy to miss entirely.

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-@Model contract as the identity rows).

Enable DashPay flowSwiftDashSDKContactsService gains mainIdentityNeedsDashPayKeys() (local-store check for an enabled ECDSA ENCRYPTION + DECRYPTION pair on the main identity) and a PIN-gated enableDashPay() that reuses the existing DWIdentityKeyUpgrader IdentityUpdate (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_update min fee + 2 × identity_key_in_creation_cost from rs-platform-version state_transition_min_fees v1 ≈ 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):

  • Intro/"DashPay" takeover rendered with the FAQ and confirm sheet (fee estimate in DASH + THB); add-contact affordances hidden.
  • Enabling ran the PIN gate and broadcast the IdentityUpdate; the identity's persisted key set went from 4 keys to 6 (key 4 ENCRYPTION, key 5 DECRYPTION verified in the store), and the tab flipped to the normal Contacts experience.
  • A real contact request to another identity went out (row → Contact Request Pending); an identity without DashPay keys correctly showed "Can't receive contact requests".
  • Public Keys page verified against the store (4 keys pre-enable, 6 post-enable), with purposes/levels/types, copyable hex, and derivation paths.
  • FAQ items stay open while background sync publishes.

Clean dashpay build at every step. (The unit-test target is pre-existing broken; not run.)

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 DashPay onboarding and enablement, including fee estimates, PIN confirmation, FAQs, privacy information, and optional first-contact requests.
    • Added a detailed identity public-keys screen with key status, purpose, security level, contract bindings, derivation paths, and copy-to-clipboard support.
    • Added localized labels and guidance for DashPay, contact requests, payment history, and identity keys.
  • UI Improvements

    • Replaced contact-request toast notifications with an animated success card showing the recipient’s username.
    • Contacts remain hidden until DashPay is enabled, creating a guided introduction experience.

QuantumExplorer and others added 14 commits August 8, 2026 22:46
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>
@coderabbitai

coderabbitai Bot commented Aug 8, 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: 4 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: 6b57c8b4-6307-43df-b710-db837a4b7b6b

📥 Commits

Reviewing files that changed from the base of the PR and between 77d1a6a and 9d08426.

📒 Files selected for processing (4)
  • DashWallet/Sources/Infrastructure/SwiftDashSDK/Contacts/SwiftDashSDKContactsService.swift
  • DashWallet/Sources/UI/DashPay/Contacts/SwiftUI/AddContactScreen.swift
  • DashWallet/Sources/UI/DashPay/Contacts/SwiftUI/ContactsScreen.swift
  • DashWallet/Sources/UI/Menu/Security/Wallets/IdentitiesScreen.swift
📝 Walkthrough

Walkthrough

DashPay 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.

Changes

DashPay enablement and contacts

Layer / File(s) Summary
DashPay key readiness and upgrade
DashWallet/Sources/Infrastructure/SwiftDashSDK/Contacts/SwiftDashSDKContactsService.swift
The contacts service checks required ECDSA keys, estimates the enablement fee, and returns the key-upgrader broadcast result.
DashPay onboarding and enablement UI
DashWallet/Sources/UI/DashPay/Contacts/SwiftUI/ContactsScreen.swift, DashWallet/en.lproj/Localizable.strings
The contacts screen adds introduction, FAQ, fee confirmation, enablement, success, and first-contact flows with localized text.
Contact-request success feedback
DashWallet/Sources/UI/DashPay/Contacts/SwiftUI/AddContactScreen.swift
The generic toast becomes an animated recipient-specific success card that remains visible for 2.5 seconds.

Identity public-key details

Layer / File(s) Summary
Identity public-key projection
DashWallet/Sources/UI/Menu/Security/Wallets/IdentitiesViewModel.swift, DashWallet/en.lproj/Localizable.strings
Identity rows now store sorted public-key projections with metadata, formatted key material, contract bindings, derivation paths, and localized labels.
Identity public-key navigation and presentation
DashWallet/Sources/UI/Menu/Security/Wallets/IdentitiesScreen.swift
The public-key count row opens a screen with key cards, status badges, attributes, and clipboard feedback.

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
Loading

Suggested reviewers: jeanpierreroma, romchornyi, llbartekll

🚥 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 identifies the two main changes: the identity public keys page and the DashPay enablement flow with introduction and FAQ.
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 feat/identity-public-keys-screen

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

🧹 Nitpick comments (1)
DashWallet/Sources/UI/DashPay/Contacts/SwiftUI/AddContactScreen.swift (1)

39-41: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the redundant optional initializer.

SwiftLint reports implicit_optional_initialization on Line 41. Declare the state as @State private var sentToUsername: String?; it already initializes to 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/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

📥 Commits

Reviewing files that changed from the base of the PR and between 7664bae and 77d1a6a.

📒 Files selected for processing (6)
  • DashWallet/Sources/Infrastructure/SwiftDashSDK/Contacts/SwiftDashSDKContactsService.swift
  • DashWallet/Sources/UI/DashPay/Contacts/SwiftUI/AddContactScreen.swift
  • DashWallet/Sources/UI/DashPay/Contacts/SwiftUI/ContactsScreen.swift
  • DashWallet/Sources/UI/Menu/Security/Wallets/IdentitiesScreen.swift
  • DashWallet/Sources/UI/Menu/Security/Wallets/IdentitiesViewModel.swift
  • DashWallet/en.lproj/Localizable.strings

Comment thread DashWallet/Sources/UI/DashPay/Contacts/SwiftUI/AddContactScreen.swift Outdated
Comment thread DashWallet/Sources/UI/DashPay/Contacts/SwiftUI/ContactsScreen.swift Outdated
Comment thread DashWallet/Sources/UI/Menu/Security/Wallets/IdentitiesScreen.swift Outdated
- 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>
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