Skip to content

feat: Maya Enter Destination Address (Requirement 2)#756

Merged
bfoss765 merged 4 commits into
feat/mayafrom
feat/maya-enter-dest-address
Mar 10, 2026
Merged

feat: Maya Enter Destination Address (Requirement 2)#756
bfoss765 merged 4 commits into
feat/mayafrom
feat/maya-enter-dest-address

Conversation

@bfoss765
Copy link
Copy Markdown
Contributor

@bfoss765 bfoss765 commented Mar 6, 2026

Summary

  • Add Enter Destination Address screen for Maya swap flow (Requirement 2)
  • Users can type, paste from clipboard, or scan a QR code to enter the destination wallet address
  • Create a generic QR scanner (GenericQRScannerController) since the existing Dash-only scanner rejects non-Dash addresses
  • Wire navigation: Maya Portal → Select Coin → Enter Address → (TODO: Enter Amount)

New Files

  • EnterAddressViewModel.swift — ViewModel with address text, clipboard reading, validation
  • EnterAddressView.swift — SwiftUI screen with address field, QR button, clipboard paste section, Continue button
  • EnterAddressHostingController.swift — Thin UIKit wrapper with QR scanner presentation
  • GenericQRScannerController.swift — Reusable QR scanner returning raw strings (AVCaptureSession-based)

Modified Files

  • MayaPortalViewController.swift — Added navigateToEnterAddress(for:) wiring coin selection to address entry
  • project.pbxproj — Added new files to both dashwallet and dashpay targets

Test plan

  • Build succeeds for both dashwallet and dashpay schemes
  • Navigate: Buy & Sell → Maya → Convert Dash → Select BTC → Enter address screen appears
  • Title shows "Enter address"
  • Placeholder shows "BTC address" (changes per selected coin)
  • Typing an address enables the Continue button
  • Tapping QR icon opens camera scanner (test on device)
  • Clipboard row shows when clipboard has content, hidden when empty
  • Tapping clipboard row pastes content into address field
  • Tapping Continue with address triggers callback (log output for now)
  • Empty address keeps Continue button disabled

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Maya Protocol added: in-app crypto swap flow with coin selection and address entry
    • Search-enabled coin picker showing supported tokens and halted-state indicators
    • QR code scanner to scan/paste wallet addresses
    • Integrated Maya option in Buy & Sell portal and a Convert Dash action
    • New coin and Maya brand assets for UI icons and logos

…t 2)

Build the address entry screen where users enter/paste/scan the destination
wallet address for the selected cryptocurrency. Includes a generic QR scanner
since the existing Dash-only scanner rejects non-Dash addresses.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 6, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5dca7f89-2497-4d9b-8f3f-810849cbf445

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
📝 Walkthrough

Walkthrough

Adds a full Maya Protocol integration: asset catalog entries, new Maya data models and API endpoints, SwiftUI screens (SelectCoin, EnterAddress, MayaPortal), UIKit hosting controllers, QR scanner, and Buy & Sell portal wiring with navigation changes and project file updates.

Changes

Cohort / File(s) Summary
Project Configuration
DashWallet.xcodeproj/project.pbxproj
Added PBX entries for new Maya source files and integrated them into groups and build phases.
Asset Catalog - Icons & Logos
DashWallet/Resources/AppAssets.xcassets/maya.logo.imageset/Contents.json, .../portal.maya.imageset/Contents.json, .../convert.crypto.imageset/Contents.json
Added SVG asset definitions for Maya branding and convert icon.
Asset Catalog - Coin Icons
DashWallet/Resources/AppAssets.xcassets/maya.coin.*.imageset/Contents.json (multiple files)
Added Contents.json manifests for ~15 Maya coin image sets (PNG/SVG metadata).
Maya Models & API
DashWallet/Sources/Models/Maya/MayaCryptoCurrency.swift, MayaPool.swift, MayaEndpoint.swift, MayaAPIService.swift
New coin catalog, pool model, Moya endpoint enum, and API service methods (fetchPools, fetchInboundAddresses).
Select Coin UI & VM
DashWallet/Sources/UI/Maya/SelectCoinView.swift, SelectCoinViewModel.swift, CoinRowView.swift, SelectCoinHostingController.swift
SwiftUI coin picker with search, halted handling, fiat conversion; ViewModel loads pools/inbound addresses; hosting controller for navigation.
Enter Address UI & VM
DashWallet/Sources/UI/Maya/EnterAddressView.swift, EnterAddressViewModel.swift, EnterAddressHostingController.swift
Address entry screen with clipboard reveal, paste, validation, QR scan integration; hosting controller exposes onAddressConfirmed callback.
QR Scanner
DashWallet/Sources/UI/Maya/GenericQRScannerController.swift
New AVFoundation-based QR scanner controller with callbacks for scanned value and cancel.
Maya Portal UI & Controller
DashWallet/Sources/UI/Maya/MayaPortalView.swift, MayaPortalViewController.swift
Maya landing SwiftUI view and UIViewController that hosts it and navigates to select-coin → enter-address flows.
Buy & Sell Integration
DashWallet/Sources/UI/Buy Sell/BuySellPortalView.swift, BuySellPortalViewController.swift, .../BuySellPortalModel.swift, ServiceDataProvider.swift
Added Maya service card, refactored portal controller to host SwiftUI, added Maya case to service model and ordering (Maya appended last).
Service EntryPoint
DashWallet/Sources/UI/Coinbase/ServiceOverview/ServiceEntryPointModel.swift
Added .maya handling with placeholder titles/icons.
Home & Shortcuts
DashWallet/Sources/UI/Home/HomeViewController+Shortcuts.swift, .../ShortcutCustomizeBannerView.swift
Present Buy/Sell modal fullScreen; adjusted dismiss button size and accessibility.
Documentation
MAYA.md
Added detailed Maya integration design, API spec, UI flow, and implementation plan.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant BuySell as BuySellPortalView
    participant MayaPortal as MayaPortalView
    participant SelectCoin as SelectCoinView
    participant SelectVM as SelectCoinViewModel
    participant API as MayaAPIService
    participant EnterAddr as EnterAddressView
    participant QR as GenericQRScannerController

    User->>BuySell: Tap Maya card
    BuySell->>MayaPortal: Present Maya portal
    User->>MayaPortal: Tap Convert Dash
    MayaPortal->>SelectCoin: Push SelectCoinView
    SelectCoin->>SelectVM: init/loadCoins()
    SelectVM->>API: fetchPools()
    API-->>SelectVM: pools
    SelectVM->>API: fetchInboundAddresses()
    API-->>SelectVM: inbound addresses
    SelectVM->>SelectVM: build CoinDisplayItem list (prices, halted)
    SelectVM-->>SelectCoin: coins ready
    User->>SelectCoin: Select coin
    SelectCoin->>EnterAddr: Push EnterAddressView
    alt QR flow
        User->>EnterAddr: Tap Scan QR
        EnterAddr->>QR: Present scanner
        QR-->>EnterAddr: onQRCodeScanned(address)
    else Paste flow
        User->>EnterAddr: Paste clipboard
    end
    EnterAddr->>EnterAddr: Validate address
    EnterAddr->>User: Continue (onAddressConfirmed)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • HashEngineering

Poem

🐰 I hopped to the portal where Maya coins gleam,
Search, scan, and paste — a seamless dream.
Coins list and convert, addresses in flight,
The rabbit applauds this new swap delight! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the primary change: adding the Enter Destination Address screen for the Maya swap flow, with explicit reference to Requirement 2.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/maya-enter-dest-address

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

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/Buy Sell/BuySellPortalViewController.swift (1)

244-267: ⚠️ Potential issue | 🟠 Major

Geoblock lookup can suspend forever when placemark resolution fails.

nextEmittedPlacemark() only resumes on a non-nil $currentPlacemark. In DWLocationManager.reverseGeocodeLocation, failure paths can leave currentPlacemark nil, so await isGeoblocked() can hang indefinitely after the Coinbase tap. Add a timeout or failure path so the flow always resolves.

Run this to inspect the continuation bridge next to DWLocationManager's placemark publishing. Expect the first snippet to wait for a non-nil placemark only, while the second shows publishing paths that do not guarantee one.

#!/bin/bash
set -euo pipefail

fd BuySellPortalViewController.swift DashWallet -x sed -n '244,267p' {}
printf '\n---\n'
fd DWLocationManager.swift DashWallet -x rg -n -C3 'currentPlacemark|reverseCurrentLocation|reverseGeocodeLocation|placemark' {}

As per coding guidelines, "Always use checked continuations with resumed flag to prevent double-resumption crashes when bridging Vision framework completion handlers to async/await."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Buy` Sell/BuySellPortalViewController.swift around
lines 244 - 267, The isGeoblocked() path can hang because nextEmittedPlacemark()
only resumes for a non-nil $currentPlacemark while
DWLocationManager.reverseGeocodeLocation may never publish a placemark; update
nextEmittedPlacemark() to enforce a bounded wait and a safe single-resume
pattern (e.g., use withCheckedContinuation or withCheckedThrowingContinuation
plus a resumed flag or timeout Task that resumes with a failure/default after X
seconds), ensure the AnyCancellable from
DWLocationManager.shared.$currentPlacemark is cancelled when the continuation is
resumed or timed out, and propagate/handle the timeout error back in
isGeoblocked() so the flow always returns (reference nextEmittedPlacemark(),
isGeoblocked(), DWLocationManager.shared.$currentPlacemark, and
reverseGeocodeLocation).
🧹 Nitpick comments (6)
DashWallet/Sources/UI/Maya/MayaPortalView.swift (1)

33-33: Consider extracting hardcoded color to a named asset.

The Maya background color Color(red: 0.08, green: 0.11, blue: 0.25) could be defined as a named color in the asset catalog for consistency and easier theming.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Maya/MayaPortalView.swift` at line 33, Replace the
hardcoded Color(red: 0.08, green: 0.11, blue: 0.25) used in MayaPortalView
(e.g., in the view body where .fill(...) is applied) with a named asset color;
add a new color asset (e.g., "MayaBackground") to the asset catalog and then use
Color("MayaBackground") (or a small Color extension if you prefer) in the .fill
call so the background color is centralized for theming and reuse.
DashWallet/Sources/UI/Maya/SelectCoinView.swift (1)

118-130: Same trailing closure pattern as noted in EnterAddressView.

SwiftLint flags the trailing closure syntax on line 122. Consider using explicit label: parameter for consistency with linting rules.

♻️ Proposed fix
-            Button(action: {
+            Button(action: {
                 Task {
                     await viewModel.loadCoins()
                 }
-            }) {
+            }, label: {
                 Text(NSLocalizedString("Retry", comment: ""))
                     .font(.system(size: 14, weight: .semibold))
                     .foregroundColor(.white)
                     .padding(.horizontal, 24)
                     .padding(.vertical, 10)
                     .background(Color.dashBlue)
                     .cornerRadius(8)
-            }
+            })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Maya/SelectCoinView.swift` around lines 118 - 130, The
Button currently uses a trailing closure for its label which triggers SwiftLint;
change the Button to use the explicit label parameter form so the action and
label are both passed as named parameters (keep the existing action that calls
Task { await viewModel.loadCoins() } and the existing label content with
Text/formatting), referencing the Button declaration and viewModel.loadCoins()
call so the change is applied to that exact component.
DashWallet/Sources/UI/Maya/EnterAddressView.swift (2)

71-77: Address SwiftLint trailing closure warning.

SwiftLint flags using trailing closure syntax when passing multiple closure arguments. Consider using explicit label: parameter for consistency.

♻️ Proposed fix
-            Button(action: { onScanQR?() }) {
+            Button(action: { onScanQR?() }, label: {
                 Image("scan-qr.accessory.icon")
                     .resizable()
                     .aspectRatio(contentMode: .fit)
                     .frame(width: 20, height: 20)
                     .foregroundColor(.secondaryText)
-            }
+            })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Maya/EnterAddressView.swift` around lines 71 - 77,
SwiftLint warns about using trailing-closure syntax when multiple closure
parameters are present in the Button initializer; update the Button call in
EnterAddressView (the Button(action: { onScanQR?() }) that currently uses a
trailing closure for the label) to use the explicit label: parameter (i.e.,
Button(action: { onScanQR?() }, label: { /* Image(...) */ })) so the action and
label closures are passed explicitly and the warning is resolved.

50-52: Consider using SwiftUI's @FocusState for keyboard dismissal.

The current approach using UIApplication.shared.sendAction works but is a UIKit-based solution. SwiftUI's @FocusState provides a more idiomatic approach for managing keyboard focus.

♻️ Alternative approach using `@FocusState`
// Add to struct properties:
`@FocusState` private var isAddressFieldFocused: Bool

// Update TextField:
TextField(viewModel.placeholderText, text: $viewModel.addressText)
    .focused($isAddressFieldFocused)
    // ...

// Update tap gesture:
.onTapGesture {
    isAddressFieldFocused = false
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Maya/EnterAddressView.swift` around lines 50 - 52,
Replace the UIKit-based keyboard dismissal in EnterAddressView by adding a
SwiftUI `@FocusState` property (e.g., private var isAddressFieldFocused: Bool) to
the view struct, attach .focused($isAddressFieldFocused) to the
TextField(viewModel.placeholderText, text: $viewModel.addressText) and change
the .onTapGesture handler to set isAddressFieldFocused = false instead of
calling UIApplication.shared.sendAction; remove the UIKit call so focus is
managed idiomatically by SwiftUI.
DashWallet/Sources/UI/Maya/EnterAddressViewModel.swift (1)

34-36: Consider adding coin-specific address validation.

The current isAddressValid only checks for non-empty input. Since Maya supports multiple cryptocurrencies with different address formats, you might want to add basic format validation per coin (e.g., BTC addresses start with specific prefixes, ETH addresses are 42 chars starting with 0x).

This could prevent users from proceeding with obviously invalid addresses before the API call.

Would you like me to help design a basic address validation approach for common cryptocurrencies?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Maya/EnterAddressViewModel.swift` around lines 34 - 36,
Replace the simplistic non-empty check in isAddressValid with coin-specific
format validation: trim addressText and route it to a new helper (e.g.,
validateAddress(for:coin:)) that switches on the view model's coin identifier
(use your existing coin property such as selectedCoin/coinType) and applies
lightweight rules (BTC: allowed prefixes and base58/Bech32 patterns, ETH:
startsWith "0x" and length 42, etc.); update isAddressValid to return the result
of that helper so UI gating uses coin-aware validation while keeping
trimming/empty checks.
DashWallet/Sources/UI/Maya/SelectCoinViewModel.swift (1)

54-55: Inject the network and FX dependencies into the ViewModel.

MayaAPIService.shared and CurrencyExchanger.shared bake global state into the ViewModel, which makes loading/error cases hard to unit-test. An initializer that accepts protocol-backed services would keep this MVVM surface testable.

As per coding guidelines, "Use protocol-based design with extensive use of protocols for dependency injection." and "Use service layer with dedicated services for major functionality (CoinJoin, networking, database)."

Also applies to: 115-119

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Maya/SelectCoinViewModel.swift` around lines 54 - 55,
SelectCoinViewModel currently references global singletons
(MayaAPIService.shared and CurrencyExchanger.shared) which prevents unit
testing; add protocol abstractions (e.g., MayaAPIServiceProtocol and
CurrencyExchangerProtocol) and change SelectCoinViewModel to accept these as
init dependencies (with default parameters that wrap the current shared
singletons for backward compatibility), replace direct calls to
MayaAPIService.shared.fetchPools()/fetchInboundAddresses() and
CurrencyExchanger.shared usage with the injected protocol instances, and update
other similar usages (the other async lets around lines 115-119) to use the
injected network service; update tests to inject mock implementations of the
protocols.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@DashWallet/Sources/UI/Buy` Sell/Model/ServiceDataProvider.swift:
- Around line 43-44: The comment is wrong because updateServices() sorts the
items array and can move the .maya entry; to ensure Maya remains last, modify
updateServices(): remove or exclude the .maya item from the array before
performing the current sort (the block using usageCount and isInUse), perform
the sort on the remaining items, then re-append the .maya item
(items.append(.init(service: .maya, dataProvider: nil))) after sorting;
reference the items array, the .maya service enum case, and the updateServices()
sorting logic so the change targets the correct place.

In `@DashWallet/Sources/UI/Maya/EnterAddressHostingController.swift`:
- Around line 52-55: Remove the plain-text address logging in the onContinue
closure of EnterAddressHostingController: do not call DSLogger.log with the full
destination address; either remove the log entirely or only log a redacted
address (e.g. show first/last chars) and/or wrap logging behind `#if` DEBUG.
Update the block that currently calls DSLogger.log("Maya: Address confirmed for
\(self.coin.code): \(address)") so that sensitive payment data is not emitted,
and keep the onAddressConfirmed(self.coin, address) behavior unchanged.

In `@DashWallet/Sources/UI/Maya/GenericQRScannerController.swift`:
- Around line 216-217: The DSLogger.log call in GenericQRScannerController
exposes raw QR payloads (sensitive addresses/URIs); change the logging so raw
values are never emitted in release builds — either remove the
DSLogger.log("Maya QR Scanner: Scanned value: ...") call entirely or guard it
with a debug-only check (e.g., only log under DEBUG or use a
redaction/safe-logging helper) and ensure onQRCodeScanned?(stringValue) still
fires; update references to DSLogger.log and the QR scanning handler in
GenericQRScannerController accordingly.
- Around line 43-60: Replace direct AVCaptureSession start/stop calls in
viewWillAppear(_:) and viewWillDisappear(_:) and the immediate session start in
setupCamera() with DWCaptureSessionManager usage so authorization is checked
(using DWCaptureSessionManager's authorization handling) and all start/stop
operations are dispatched on its dedicated serial queue to avoid concurrent
startRunning() races; locate calls to
session.startRunning()/session.stopRunning() in viewWillAppear(_:),
viewWillDisappear(_:), and setupCamera() and route them through
DWCaptureSessionManager APIs. Also wrap any raw QR payload logging in the QR
handling code (where the scanned payload is logged) with `#if` DEBUG ... `#endif` to
avoid emitting sensitive data in release builds.

In `@DashWallet/Sources/UI/Maya/SelectCoinViewModel.swift`:
- Around line 70-88: The loop that builds picker items currently only ensures a
pool exists (using poolsByAsset[coin.mayaAsset]) but ignores
MayaPool.isAvailable, so staged/non-available pools still appear; update the
loop in the SelectCoinViewModel where you iterate
MayaCryptoCurrency.supportedCoins and before creating the CoinDisplayItem (the
items.append(...) call), check pool.isAvailable and continue/skip if false
(i.e., only create CoinDisplayItem when pool.isAvailable is true), ensuring
unusable assets are excluded from the picker.

In `@MAYA.md`:
- Around line 52-61: Update the tracker row for "Enter Destination Address"
(Requirement 2 / table row 2, Figma Node 24007-6732) to reflect this PR: set the
Branch to the current PR branch name and change Status from "Not Started" to "In
Progress" (or "Done" if applicable); ensure the table entry for the feature
title "Enter Destination Address" is edited accordingly so the
single-source-of-truth table matches the implemented enter-address screen in
this PR.

---

Outside diff comments:
In `@DashWallet/Sources/UI/Buy` Sell/BuySellPortalViewController.swift:
- Around line 244-267: The isGeoblocked() path can hang because
nextEmittedPlacemark() only resumes for a non-nil $currentPlacemark while
DWLocationManager.reverseGeocodeLocation may never publish a placemark; update
nextEmittedPlacemark() to enforce a bounded wait and a safe single-resume
pattern (e.g., use withCheckedContinuation or withCheckedThrowingContinuation
plus a resumed flag or timeout Task that resumes with a failure/default after X
seconds), ensure the AnyCancellable from
DWLocationManager.shared.$currentPlacemark is cancelled when the continuation is
resumed or timed out, and propagate/handle the timeout error back in
isGeoblocked() so the flow always returns (reference nextEmittedPlacemark(),
isGeoblocked(), DWLocationManager.shared.$currentPlacemark, and
reverseGeocodeLocation).

---

Nitpick comments:
In `@DashWallet/Sources/UI/Maya/EnterAddressView.swift`:
- Around line 71-77: SwiftLint warns about using trailing-closure syntax when
multiple closure parameters are present in the Button initializer; update the
Button call in EnterAddressView (the Button(action: { onScanQR?() }) that
currently uses a trailing closure for the label) to use the explicit label:
parameter (i.e., Button(action: { onScanQR?() }, label: { /* Image(...) */ }))
so the action and label closures are passed explicitly and the warning is
resolved.
- Around line 50-52: Replace the UIKit-based keyboard dismissal in
EnterAddressView by adding a SwiftUI `@FocusState` property (e.g., private var
isAddressFieldFocused: Bool) to the view struct, attach
.focused($isAddressFieldFocused) to the TextField(viewModel.placeholderText,
text: $viewModel.addressText) and change the .onTapGesture handler to set
isAddressFieldFocused = false instead of calling
UIApplication.shared.sendAction; remove the UIKit call so focus is managed
idiomatically by SwiftUI.

In `@DashWallet/Sources/UI/Maya/EnterAddressViewModel.swift`:
- Around line 34-36: Replace the simplistic non-empty check in isAddressValid
with coin-specific format validation: trim addressText and route it to a new
helper (e.g., validateAddress(for:coin:)) that switches on the view model's coin
identifier (use your existing coin property such as selectedCoin/coinType) and
applies lightweight rules (BTC: allowed prefixes and base58/Bech32 patterns,
ETH: startsWith "0x" and length 42, etc.); update isAddressValid to return the
result of that helper so UI gating uses coin-aware validation while keeping
trimming/empty checks.

In `@DashWallet/Sources/UI/Maya/MayaPortalView.swift`:
- Line 33: Replace the hardcoded Color(red: 0.08, green: 0.11, blue: 0.25) used
in MayaPortalView (e.g., in the view body where .fill(...) is applied) with a
named asset color; add a new color asset (e.g., "MayaBackground") to the asset
catalog and then use Color("MayaBackground") (or a small Color extension if you
prefer) in the .fill call so the background color is centralized for theming and
reuse.

In `@DashWallet/Sources/UI/Maya/SelectCoinView.swift`:
- Around line 118-130: The Button currently uses a trailing closure for its
label which triggers SwiftLint; change the Button to use the explicit label
parameter form so the action and label are both passed as named parameters (keep
the existing action that calls Task { await viewModel.loadCoins() } and the
existing label content with Text/formatting), referencing the Button declaration
and viewModel.loadCoins() call so the change is applied to that exact component.

In `@DashWallet/Sources/UI/Maya/SelectCoinViewModel.swift`:
- Around line 54-55: SelectCoinViewModel currently references global singletons
(MayaAPIService.shared and CurrencyExchanger.shared) which prevents unit
testing; add protocol abstractions (e.g., MayaAPIServiceProtocol and
CurrencyExchangerProtocol) and change SelectCoinViewModel to accept these as
init dependencies (with default parameters that wrap the current shared
singletons for backward compatibility), replace direct calls to
MayaAPIService.shared.fetchPools()/fetchInboundAddresses() and
CurrencyExchanger.shared usage with the injected protocol instances, and update
other similar usages (the other async lets around lines 115-119) to use the
injected network service; update tests to inject mock implementations of the
protocols.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7e4698b6-83d6-4b2c-8276-bbc4a0395542

📥 Commits

Reviewing files that changed from the base of the PR and between 44625a4 and ee61597.

⛔ Files ignored due to path filters (18)
  • DashWallet/Resources/AppAssets.xcassets/convert.crypto.imageset/convert.crypto.svg is excluded by !**/*.svg
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.arb.imageset/maya.coin.arb.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.btc.imageset/maya.coin.btc.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.dai.imageset/maya.coin.dai.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.eth.imageset/maya.coin.eth.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.gld.imageset/maya.coin.gld.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.kuji.imageset/maya.coin.kuji.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.leo.imageset/maya.coin.leo.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.link.imageset/maya.coin.link.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.pepe.imageset/maya.coin.pepe.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.rune.imageset/maya.coin.rune.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.tgt.imageset/maya.coin.tgt.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.usdc.imageset/maya.coin.usdc.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.usdt.imageset/maya.coin.usdt.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.wbtc.imageset/maya.coin.wbtc.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.wsteth.imageset/maya.coin.wsteth.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/maya.logo.imageset/maya.logo.svg is excluded by !**/*.svg
  • DashWallet/Resources/AppAssets.xcassets/portal.maya.imageset/portal.maya.svg is excluded by !**/*.svg
📒 Files selected for processing (41)
  • DashWallet.xcodeproj/project.pbxproj
  • DashWallet/Resources/AppAssets.xcassets/convert.crypto.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.arb.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.btc.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.dai.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.eth.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.gld.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.kuji.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.leo.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.link.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.pepe.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.rune.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.tgt.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.usdc.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.usdt.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.wbtc.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/maya.coin.wsteth.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/maya.logo.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/portal.maya.imageset/Contents.json
  • DashWallet/Sources/Models/Maya/MayaAPIService.swift
  • DashWallet/Sources/Models/Maya/MayaCryptoCurrency.swift
  • DashWallet/Sources/Models/Maya/MayaEndpoint.swift
  • DashWallet/Sources/Models/Maya/MayaPool.swift
  • DashWallet/Sources/UI/Buy Sell/BuySellPortalView.swift
  • DashWallet/Sources/UI/Buy Sell/BuySellPortalViewController.swift
  • DashWallet/Sources/UI/Buy Sell/Model/BuySellPortalModel.swift
  • DashWallet/Sources/UI/Buy Sell/Model/ServiceDataProvider.swift
  • DashWallet/Sources/UI/Coinbase/ServiceOverview/ServiceEntryPointModel.swift
  • DashWallet/Sources/UI/Home/HomeViewController+Shortcuts.swift
  • DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutCustomizeBannerView.swift
  • DashWallet/Sources/UI/Maya/CoinRowView.swift
  • DashWallet/Sources/UI/Maya/EnterAddressHostingController.swift
  • DashWallet/Sources/UI/Maya/EnterAddressView.swift
  • DashWallet/Sources/UI/Maya/EnterAddressViewModel.swift
  • DashWallet/Sources/UI/Maya/GenericQRScannerController.swift
  • DashWallet/Sources/UI/Maya/MayaPortalView.swift
  • DashWallet/Sources/UI/Maya/MayaPortalViewController.swift
  • DashWallet/Sources/UI/Maya/SelectCoinHostingController.swift
  • DashWallet/Sources/UI/Maya/SelectCoinView.swift
  • DashWallet/Sources/UI/Maya/SelectCoinViewModel.swift
  • MAYA.md

Comment thread DashWallet/Sources/UI/Buy Sell/Model/ServiceDataProvider.swift
Comment thread DashWallet/Sources/UI/Maya/EnterAddressHostingController.swift
Comment thread DashWallet/Sources/UI/Maya/GenericQRScannerController.swift Outdated
Comment thread DashWallet/Sources/UI/Maya/GenericQRScannerController.swift Outdated
Comment thread DashWallet/Sources/UI/Maya/SelectCoinViewModel.swift
Comment thread MAYA.md
- Fix Maya sort order in ServiceDataProvider to keep Maya last
- Wrap sensitive address/QR logging in #if DEBUG
- Check pool.isAvailable before showing coins in SelectCoinViewModel
- Implement two-step clipboard reveal matching Send screen pattern
- Fix clipboard paste flash by matching hosting controller background
  color and animating the transition
- Update MAYA.md Requirement 2 status to In Progress

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (3)
DashWallet/Sources/UI/Maya/GenericQRScannerController.swift (2)

216-219: ⚠️ Potential issue | 🟠 Major

Don't emit the raw QR payload.

Even in DEBUG, the scanned string can be a wallet address or payment URI. Logging it verbatim leaks sensitive transaction data into shared test/device logs; redact it or remove the payload from the message.

🔒 Minimal fix
         `#if` DEBUG
-        DSLogger.log("Maya QR Scanner: Scanned value: \(stringValue)")
+        let preview = "\(stringValue.prefix(6))...\(stringValue.suffix(4))"
+        DSLogger.log("🔍 Maya QR Scanner: Scanned value: \(preview)")
         `#endif`
         onQRCodeScanned?(stringValue)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Maya/GenericQRScannerController.swift` around lines 216
- 219, The DEBUG log in GenericQRScannerController.swift emits the raw scanned
QR payload (stringValue) which may contain sensitive payment data; update the
DEBUG logging inside the scan handler to avoid logging stringValue
directly—either remove the payload from the DSLogger.log call or replace it with
a redacted/placeholder message like "Maya QR Scanner: Scanned value: <redacted>"
while keeping the onQRCodeScanned?(stringValue) call intact so functionality is
unchanged.

43-103: ⚠️ Potential issue | 🟠 Major

Request camera access first and serialize session start/stop.

setupCamera() already dispatches startRunning(), and viewWillAppear(_:) can queue a second startRunning() before the first flips isRunning. Users with denied/restricted camera access also just land on the scanner chrome with no explicit recovery path. Gate setup on .video authorization and move every startRunning() / stopRunning() call onto a dedicated serial queue.

🔐 One way to tighten this up
+    private let sessionQueue = DispatchQueue(label: "org.dashwallet.maya.qr-session")
+
     override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
-
-        if let session = captureSession, !session.isRunning {
-            DispatchQueue.global(qos: .userInitiated).async {
-                session.startRunning()
-            }
-        }
+        guard AVCaptureDevice.authorizationStatus(for: .video) == .authorized,
+              let session = captureSession,
+              !session.isRunning else { return }
+        sessionQueue.async { session.startRunning() }
     }
 
     override func viewWillDisappear(_ animated: Bool) {
         super.viewWillDisappear(animated)
 
         if let session = captureSession, session.isRunning {
-            DispatchQueue.global(qos: .userInitiated).async {
-                session.stopRunning()
-            }
+            sessionQueue.async { session.stopRunning() }
         }
     }

Then branch setupCamera() through AVCaptureDevice.authorizationStatus(for: .video) / requestAccess(for: .video) before creating the session or preview.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Maya/GenericQRScannerController.swift` around lines 43
- 103, The scanner currently may call startRunning/stopRunning concurrently and
proceeds without requesting camera permission; update setupCamera(),
viewWillAppear(_:), and viewWillDisappear(_:) to first check
AVCaptureDevice.authorizationStatus(for: .video) and call requestAccess(for:
.video) when needed, only creating the AVCaptureSession and previewLayer after
authorization is granted (show a clear camera-denied alert with recovery actions
when denied/restricted), and move all session.startRunning() and
session.stopRunning() calls onto a dedicated serial DispatchQueue (e.g., a
private DispatchQueue used by this controller) so start/stop are serialized and
you avoid racing on captureSession.isRunning; ensure captureSession and
previewLayer are only set after successful setup and that
viewWillAppear/viewWillDisappear schedule start/stop on that serial queue rather
than directly on global queues.
DashWallet/Sources/UI/Maya/EnterAddressHostingController.swift (1)

52-57: ⚠️ Potential issue | 🟠 Major

Redact the confirmed address before logging.

#if DEBUG narrows the audience, but this still writes the full destination address into device/QA logs on every successful continue. Please either drop the payload or log only a short preview.

🔒 Minimal fix
             onContinue: { [weak self] address in
                 guard let self else { return }
                 `#if` DEBUG
-                DSLogger.log("Maya: Address confirmed for \(self.coin.code): \(address)")
+                let preview = "\(address.prefix(6))...\(address.suffix(4))"
+                DSLogger.log("🎯 Maya: Address confirmed for \(self.coin.code): \(preview)")
                 `#endif`
                 self.onAddressConfirmed?(self.coin, address)
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@DashWallet/Sources/UI/Maya/EnterAddressHostingController.swift` around lines
52 - 57, The debug log currently prints the full destination address inside the
onContinue closure of EnterAddressHostingController; change the DSLogger.log
call so it does not include the full address—either remove the address payload
or log a redacted preview (e.g., first 4 and last 4 chars or a fixed-length
mask) while retaining the coin code; update the DSLogger.log invocation in the
onContinue closure accordingly and leave self.onAddressConfirmed?(self.coin,
address) unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@DashWallet/Sources/UI/Maya/EnterAddressView.swift`:
- Around line 78-84: The QR scan Button (the one calling onScanQR?() and showing
Image("scan-qr.accessory.icon")) needs an explicit accessibility label and a
larger hit target; update the Button to include .accessibilityLabel("Scan QR
code") (or similar localized string) and enlarge its tappable area to at least
44×44 by adding padding or using .frame(minWidth: 44, minHeight: 44) together
with .contentShape(Rectangle()) so the image remains visually 20×20 but the hit
area meets accessibility guidelines.

In `@DashWallet/Sources/UI/Maya/EnterAddressViewModel.swift`:
- Around line 49-71: Update clipboard handling and normalize pasted addresses:
change checkClipboard() to detect URL items as well (use
UIPasteboard.general.hasStrings || UIPasteboard.general.hasURLs) and update
revealClipboard() to prefer UIPasteboard.general.url?.absoluteString when
available; create a single helper (e.g., normalizeAddress(_:),
extractAddressFromURI(_:), or similar) that mirrors
PiggyCardsRepository.parsePaymentURI() behavior to strip wallet URI schemes and
query params (handle scheme:address?params like bitcoin:addr?amount= or
ethereum:addr?...), then call that normalizer from pasteFromClipboard() and
setAddress(_:) so revealedClipboardContent and addressText store only the
extracted/normalized address instead of raw URIs.

---

Duplicate comments:
In `@DashWallet/Sources/UI/Maya/EnterAddressHostingController.swift`:
- Around line 52-57: The debug log currently prints the full destination address
inside the onContinue closure of EnterAddressHostingController; change the
DSLogger.log call so it does not include the full address—either remove the
address payload or log a redacted preview (e.g., first 4 and last 4 chars or a
fixed-length mask) while retaining the coin code; update the DSLogger.log
invocation in the onContinue closure accordingly and leave
self.onAddressConfirmed?(self.coin, address) unchanged.

In `@DashWallet/Sources/UI/Maya/GenericQRScannerController.swift`:
- Around line 216-219: The DEBUG log in GenericQRScannerController.swift emits
the raw scanned QR payload (stringValue) which may contain sensitive payment
data; update the DEBUG logging inside the scan handler to avoid logging
stringValue directly—either remove the payload from the DSLogger.log call or
replace it with a redacted/placeholder message like "Maya QR Scanner: Scanned
value: <redacted>" while keeping the onQRCodeScanned?(stringValue) call intact
so functionality is unchanged.
- Around line 43-103: The scanner currently may call startRunning/stopRunning
concurrently and proceeds without requesting camera permission; update
setupCamera(), viewWillAppear(_:), and viewWillDisappear(_:) to first check
AVCaptureDevice.authorizationStatus(for: .video) and call requestAccess(for:
.video) when needed, only creating the AVCaptureSession and previewLayer after
authorization is granted (show a clear camera-denied alert with recovery actions
when denied/restricted), and move all session.startRunning() and
session.stopRunning() calls onto a dedicated serial DispatchQueue (e.g., a
private DispatchQueue used by this controller) so start/stop are serialized and
you avoid racing on captureSession.isRunning; ensure captureSession and
previewLayer are only set after successful setup and that
viewWillAppear/viewWillDisappear schedule start/stop on that serial queue rather
than directly on global queues.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d8012e80-f04e-4b26-84fe-c1e1ae515f06

📥 Commits

Reviewing files that changed from the base of the PR and between ee61597 and 1377ad6.

📒 Files selected for processing (8)
  • DashWallet/Sources/UI/Buy Sell/Model/ServiceDataProvider.swift
  • DashWallet/Sources/UI/Maya/EnterAddressHostingController.swift
  • DashWallet/Sources/UI/Maya/EnterAddressView.swift
  • DashWallet/Sources/UI/Maya/EnterAddressViewModel.swift
  • DashWallet/Sources/UI/Maya/GenericQRScannerController.swift
  • DashWallet/Sources/UI/Maya/MayaPortalViewController.swift
  • DashWallet/Sources/UI/Maya/SelectCoinViewModel.swift
  • MAYA.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • DashWallet/Sources/UI/Buy Sell/Model/ServiceDataProvider.swift

Comment thread DashWallet/Sources/UI/Maya/EnterAddressView.swift
Comment thread DashWallet/Sources/UI/Maya/EnterAddressViewModel.swift
Comment thread DashWallet/Sources/UI/Maya/GenericQRScannerController.swift Outdated
@bfoss765 bfoss765 changed the base branch from master to feat/maya March 10, 2026 12:44
bfoss765 and others added 2 commits March 10, 2026 09:06
…to URI normalization

Refactor GenericQRScannerController from 221-line UIKit to thin wrapper
hosting new SwiftUI GenericQRScannerView + QRCaptureView UIViewRepresentable.
Add 44x44pt hit target and accessibility label to QR scan button.
Detect clipboard URLs and normalize crypto URIs (bitcoin:, ethereum:, etc.)
by stripping scheme, chain ID, and query params in paste/scan paths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@HashEngineering HashEngineering left a comment

Choose a reason for hiding this comment

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

LGTM

@bfoss765 bfoss765 merged commit 6735154 into feat/maya Mar 10, 2026
2 checks passed
@bfoss765 bfoss765 deleted the feat/maya-enter-dest-address branch March 10, 2026 14:52
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.

2 participants