Skip to content

feat(wallet): prepare 8.6.0 release#777

Open
romchornyi wants to merge 61 commits into
masterfrom
feat/release-8.6.0
Open

feat(wallet): prepare 8.6.0 release#777
romchornyi wants to merge 61 commits into
masterfrom
feat/release-8.6.0

Conversation

@romchornyi

@romchornyi romchornyi commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Issue being fixed or feature implemented

This PR merges the 8.6.0 release branch into master.

It bundles the approved changes for the 8.6.0 iOS release into a single production-ready branch, including the new Maya conversion flow, shortcut bar improvements, PiggyCards translation updates, CrowdNode restrictions for this release, and the final release version/build updates.

What was done?

  • Merged the 8.6.0 release work into a single branch for final delivery.
  • Added the Maya conversion flow and related UI/screens.
  • Removed SwapKit-specific entry/model wiring so Maya remains the active supported conversion path for this release.
  • Updated Buy & Sell / Home shortcut experiences.
  • Included PiggyCards translation updates and broader release localization sync.
  • Applied CrowdNode release-scope restrictions/disablement changes included for 8.6.0.
  • Updated release metadata.

How Has This Been Tested?

  • Reviewed and merged the feature branches into feat/release-8.6.0.
  • Verified the release branch was synchronized with remote before preparing the final PR.
  • Previously validated parts of this release during feature work included:
  • Full end-to-end regression testing for the final aggregated release branch.

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

Summary by CodeRabbit

  • New Features

    • Added Maya crypto conversion flow (Dash → supported coins) with order preview and address validation.
    • Expanded Buy & Sell portal with a dedicated Maya service option.
    • Improved shortcuts and shortcut bar experience with updated SwiftUI presentation.
  • Bug Fixes

    • Reduced Dash amount decimal precision for clearer formatting.
    • Improved handling and messaging for pending/pacing swap states and stale input prevention.
    • Made Coinbase account fetching support zero-balance crypto accounts.
  • UI/UX Improvements

    • Refreshed and corrected app image assets (icons/illustrations) for better rendering consistency.
    • Adjusted feature visibility and menus based on signup/link status.

bfoss765 and others added 30 commits February 26, 2026 10:25
…sion to 8.6.0

- Replace storyboard-based Buy & Sell screen with SwiftUI view matching Figma designs
- Add Maya as a new service in Buy & Sell portal with dedicated Maya portal screen
- Group services into cards: Uphold+Coinbase, Topper with "Powered by Uphold" badge, Maya
- Fix navigation: back chevron works for both modal dismiss and push pop
- Present Buy & Sell portal full screen instead of sheet overlay
- Add shortcut bar customization with banner and selection sheet
- Add new shortcut icon SVG assets (ATM, Coinbase, CrowdNode, Topper, Uphold, etc.)
- Bump version to 8.6.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace invalid MAYA PBX object IDs with valid 24-char hex
- Fix integer division in Color initializer (176/255 → 176.0/255.0)
- Change infoButton from IUO to regular optional
- Add geoblock check to Coinbase shortcut entry point
- Add accessibility label and 44x44 hit target to banner dismiss button
- Add comment explaining why MayaPortalViewController wrapper is needed
  (UIHostingController hides UIKit nav bar when pushed directly)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add operator spacing around division in BuySellPortalView and replace
force-unwraps with safe optional chaining in BuySellPortalViewController.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
feat: Maya entry points, Buy & Sell portal redesign, v8.6.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implement coin selection screen for Maya Protocol integration. Users can
browse and search Maya-supported cryptocurrencies with live fiat prices
fetched from Midgard API, with halted chain indicators.

- Add MayaCryptoCurrency model with 19 hardcoded coins
- Add MayaEndpoint (Moya) + MayaAPIService for pools and inbound addresses
- Add SelectCoinView with search, loading, error, and halted toast states
- Add CoinRowView with icon, name, ticker, and fiat price
- Wire "Convert Dash" button in MayaPortalView to push coin selection
- Bundle 15 coin icon PNGs from crypto-icons inventory
- Add all files to both dashwallet and dashpay Xcode targets

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…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>
- Replace force-unwrapped URLs in MayaEndpoint with guard let + preconditionFailure
- Pin Maya to end of service list after sorting in ServiceDataProvider
- Use explicit label: parameter on Button to satisfy SwiftLint trailing closure rule
- Remove halted coin UI/logic (Requirement 8 not yet implemented): remove
  inbound_addresses API call, halted toast, disabled/greyed coin states
- Update MAYA.md: mark Req 1 as Implemented, update Req 2 status, fix coin count

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
feat: Maya select destination coin (Requirement 1)
- 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>
…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>
feat: Maya Enter Destination Address (Requirement 2)
…r Maya swaps

Add exchange address sources to the Maya Enter Address screen so users can
retrieve deposit addresses from their Uphold and Coinbase accounts.

- Add AddressSourceView, MayaExchangeAddressProvider, and address source
  state management to EnterAddressViewModel
- Uphold: fetch cards, create network-specific crypto addresses via API,
  with session caching and proper network key lookup
- Coinbase: direct account lookup by currency code with account-list
  fallback, address creation via POST, session caching
- Add login flows for both exchanges from the Enter Address screen
- Re-check authorization after failed fetches to show "Log In" instead of
  "Not available" when sessions expire

Fix Coinbase OAuth which migrated to new endpoints:
- Auth URL: login.coinbase.com/oauth2/auth (was coinbase.com/oauth/authorize)
- Token URL: login.coinbase.com/oauth2/token (was api.coinbase.com/oauth/token)
- Redirect URI: dashwallet://brokers/coinbase/connect (was authhub://oauth-callback)
- Token exchange encoding: application/x-www-form-urlencoded (was JSON)
- Remove deprecated account and meta[send_limit_*] OAuth parameters

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add chain-specific address validation with two levels:

1. Local regex validation (real-time) - validates format as user types:
   - BTC: Legacy Base58 (1.../3...) and Bech32 SegWit/Taproot (bc1q.../bc1p...)
   - ETH/ARB: 0x + 40 hex characters
   - KUJI: kujira1 + 38 bech32 characters
   - THOR: thor1 + 38 bech32 characters

2. Maya API validation (on Continue) - calls /mayachain/quote/swap to verify
   checksum and chain-level validity. Shows API error message on failure,
   or temp SUCCESS dialog on success.

UI updates to match Figma MO-32 design:
- Red border and light red background on invalid address field
- Error message displayed below the input field
- Continue button disabled for format-invalid addresses

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: add Uphold/Coinbase address sources for Maya and fix Coinbase OAuth
- Remove force unwraps in CoinbaseUserAccountData.balanceFormatted,
  using guard/let with raw string fallback matching fiatBalanceFormatted
- Return user-facing error on network failure in validateAddress instead
  of nil (which was incorrectly treated as valid)
- Check HTTP status code in createUpholdCard before decoding response

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…efore deallocation

AVCaptureMetadataOutput holds an unretained reference to its delegate.
When the scanner dismisses, stopSession() stops the capture session
asynchronously, but the Coordinator can be deallocated before the
session fully stops. If a QR code is detected in that window, the
callback fires on a dangling pointer, causing EXC_BAD_ACCESS.

Fix: nil out the metadata output delegate in both stopSession() and
deinit before the coordinator can be deallocated. Also remove the
unnecessary DispatchQueue.main.async hop in the delegate callback
(already dispatched on main), and replace force cast with guard.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
feat: validate destination address for Maya swaps (MO-32)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- MayaTransactionSuccessView: success state with icon, title, message
- MayaTransactionFailureView: failure state with retry, cancel, and support actions
- MayaFeeInfoSheet: bottom-sheet component explaining the Maya swap fee with a Learn more link and Got it dismiss button
- All three views registered in both dashwallet and dashpay Xcode targets
jeanpierreroma and others added 8 commits June 9, 2026 00:48
Why:
- keep users on Order Preview when a previous Maya swap is still confirming
- align Podfile.lock with the locally installed pod state used on this branch

What:
- add a dedicated pending-swap DashSpend error and surface it as a system alert in Order Preview
- prevent the previous-swap-pending case from navigating to the Maya failure screen
- refine AddressSourceView subtitle rendering and sync Podfile.lock to Protobuf 3.29.5

Validation:
- attempted xcodebuild for dashwallet
- build is currently blocked by CocoaPods/TodayExtension environment issues outside this code path
…lity

fix(wallet): hide unavailable account entry points
feat(ui): polish shortcut sheet and migrate shortcut bar UI
@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds Maya conversion services and SwiftUI screens, updates Coinbase OAuth and account retrieval, hardens send and swap transaction handling, refreshes Buy/Sell and shortcuts entry points, gates some CrowdNode surfaces, and adds or remaps many asset catalog image manifests.

Changes

Maya, Coinbase, and UI integration

Layer / File(s) Summary
Asset catalog remap and additions
DashWallet/Resources/AppAssets.xcassets/Illustration/*, .../Menu/*, .../Navigation bar/*, .../Shortcuts/*, .../Toast/*, .../Other/*, .../maya.coin.*, .../portal.maya.imageset/Contents.json
Adds many new image-set manifests, remaps existing filenames, and updates menu, portal, navigation, toast, shortcut, and Maya coin assets.
Coinbase OAuth and account retrieval
DashWallet/Sources/Models/Coinbase/..., DashWallet/Sources/Application/Tools.swift
Moves Coinbase auth to login oauth2 endpoints and dashwallet redirects, adds zero-balance account retrieval paths, hardens balance parsing, and reduces Dash formatter precision to 5 fraction digits.
Maya API, metadata, and address sourcing
DashWallet/Sources/Models/Maya/*
Adds Maya endpoints, API service, DTOs, coin metadata, validators, constants, remote icon loading, pool parsing, and cached Uphold/Coinbase destination address retrieval.
Swap execution and guarded send flow
DashWallet/Sources/Models/Transactions/*, DashWallet/Sources/Models/Swap/SwapExecutionData.swift, DashWallet/Sources/Models/Explore Dash/Services/DashSpend/DashSpendError.swift
Adds Maya swap execution data and pending-state errors, detects spent inputs and unconfirmed swap transactions, and extends send flows with authentication, stale-input checks, swap transaction assembly, fee correction, and InstantSend gating.
Portal entry points and availability gating
DashWallet/Sources/UI/Buy Sell/*, DashWallet/Sources/UI/Coinbase/ServiceOverview/ServiceEntryPointModel.swift, DashWallet/Sources/UI/CrowdNode/*, DashWallet/Sources/UI/Explore Dash/ExploreMenuScreen.swift, DashWallet/Sources/UI/Explore Dash/.../POI*
Adds Maya to Buy/Sell portal models and navigation, keeps Maya ordered last in service lists, reduces CrowdNode onboarding/portal availability, gates Explore staking on CrowdNode account state, and adds POI preview scaffolding.
Home shortcuts SwiftUI migration
DashWallet/Sources/UI/Home/.../Shortcuts/*, .../Home Header View/HomeHeaderView.swift, .../HomeViewModel.swift, .../HomeViewController+Shortcuts.swift
Moves shortcut rendering and selection further into SwiftUI, updates header embedding and spacing, keeps shortcut availability tied to CrowdNode state, and refreshes shortcut sheet, banner, and preview code.
Maya convert amount, quote, and screen flow
DashWallet/Sources/UI/Maya/Convert/*
Adds the conversion amount model, picker and amount components, the Maya convert screen and hosting controller, and a quote-driven view model that sanitizes input, fetches rates and quotes, and prepares order preview state.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant MayaConvertView
  participant MayaConvertViewModel
  participant MayaAPIService
  participant MayaAPI

  User->>MayaConvertView: Enter amount or change currency
  MayaConvertView->>MayaConvertViewModel: Update input state
  MayaConvertViewModel->>MayaAPIService: fetchPools()
  MayaAPIService->>MayaAPI: GET pools
  MayaAPI-->>MayaAPIService: pool data
  MayaAPIService-->>MayaConvertViewModel: rates
  MayaConvertViewModel->>MayaAPIService: fetchQuote(dashSatoshis, toAsset, destination)
  MayaAPIService->>MayaAPI: GET quote
  MayaAPI-->>MayaAPIService: quote or error
  MayaAPIService-->>MayaConvertViewModel: parsed quote/error
  MayaConvertViewModel-->>MayaConvertView: receive amount / error / continue state
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • HashEngineering

Poem

A rabbit hopped through icons bright,
and wired new swap paths late at night.
With Maya roads and shortcuts neat,
plus guarded sends on careful feet.
🐇✨ The wallet garden grew complete.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/release-8.6.0

@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: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
DashWallet/Resources/AppAssets.xcassets/maya.coin.arb.imageset/Contents.json (1)

13-13: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove preserves-vector-representation from all Maya coin PNG assets.

All seven maya.coin.* asset catalogs set preserves-vector-representation: true but reference PNG files rather than SVG. This property is only meaningful for vector formats and creates misleading configuration. Remove it from all Maya cryptocurrency icon manifests.

🤖 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/Resources/AppAssets.xcassets/maya.coin.arb.imageset/Contents.json`
at line 13, Remove the "preserves-vector-representation" key from the
Contents.json manifests for all maya.coin.* image sets (e.g.,
maya.coin.arb.imageset) because these assets reference PNGs, not vectors; open
each maya.coin.* image set's Contents.json and delete the
"preserves-vector-representation" entry so the manifest only describes raster
images.

Source: Coding guidelines

DashWallet/Sources/UI/Buy Sell/BuySellPortalViewController.swift (1)

76-99: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid force unwrapping navigationController.

Line 82 force-unwraps navigationController, which could crash if the controller is not in a navigation stack (e.g., if presented modally in an unexpected way). Use safe unwrapping instead.

🛡️ Proposed fix
             let vc = IntegrationViewController.controller(model: CoinbaseEntryPointModel())
             vc.userSignedOutBlock = { [weak self] isNeedToShowSignOutError in
                 guard let self else { return }
                 
-                self.navigationController!.popToViewController(self, animated: true)
+                guard let navigationController = self.navigationController else { return }
+                navigationController.popToViewController(self, animated: true)
                 
                 if isNeedToShowSignOutError {
🤖 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/Buy` Sell/BuySellPortalViewController.swift around
lines 76 - 99, The navigateToCoinbase method currently force-unwraps
navigationController in the vc.userSignedOutBlock which can crash; update the
block to safely unwrap the navigation controller (e.g., guard let nav =
navigationController else { return }) and use that local nav to call
popToViewController and pushViewController (also ensure other uses of
navigationController? in this method use safe optional chaining or the same
guard before pushing ServiceOverviewViewController) so no force unwrapping
occurs; update references inside vc.userSignedOutBlock and the push logic to use
the safely unwrapped navigation controller.

Source: Linters/SAST tools

🧹 Nitpick comments (15)
DashWallet/Resources/AppAssets.xcassets/Navigation bar/navigationbar-plus.imageset/Contents.json (1)

23-25: ⚡ Quick win

Verify removal of template-rendering-intent is intentional.

The previous version included template-rendering-intent in the properties, which has been removed. Navigation bar icons are typically generic UI elements that should inherit the system tint color via template-rendering-intent: "template". Without this property, the icon may not tint correctly with the app's theme.

Recommended addition
 "properties" : {
-  "localizable" : true
+  "localizable" : true,
+  "template-rendering-intent" : "template"
 }
🤖 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/Resources/AppAssets.xcassets/Navigation`
bar/navigationbar-plus.imageset/Contents.json around lines 23 - 25, The
Contents.json for the navigationbar-plus imageset removed the
"template-rendering-intent" property which prevents the navigation icon from
being tinted by the system; restore "template-rendering-intent": "template"
inside the "properties" object of the navigationbar-plus.imageset Contents.json
(alongside "localizable": true) so the asset uses template rendering and
inherits tint colors.

Source: Learnings

DashWallet/Resources/AppAssets.xcassets/info-icon.imageset/Contents.json (1)

1-23: ⚡ Quick win

Consider adding template-rendering-intent for tinting.

The info-icon is a generic UI icon that would benefit from system tinting. Add a properties section with template-rendering-intent: "template" to allow the icon to inherit the system tint color.

♻️ Suggested properties addition
   ],
   "info" : {
     "author" : "xcode",
     "version" : 1
+  },
+  "properties" : {
+    "template-rendering-intent" : "template"
   }
 }
🤖 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/Resources/AppAssets.xcassets/info-icon.imageset/Contents.json`
around lines 1 - 23, The asset catalog entry for "info-icon" needs a properties
block to enable system tinting; open the info-icon.imageset Contents.json and
add a top-level "properties" object containing "template-rendering-intent":
"template" (so the asset will render as a template image and inherit tint),
leaving the existing "images" and "info" entries intact.

Source: Learnings

DashWallet/Sources/Models/Transactions/SendCoinsService.swift (1)

163-164: ⚡ Quick win

Gate swap diagnostics behind #if DEBUG and apply emoji log prefixes.

These logs run on production send paths and currently skip the repository’s debug/log-format conventions.

As per coding guidelines, “Wrap debug print statements in #if DEBUG blocks…” and “Use emoji debug markers for easy log filtering…”.

Also applies to: 236-242, 273-278, 283-293, 302-306

🤖 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/Models/Transactions/SendCoinsService.swift` around lines
163 - 164, Wrap the diagnostic DSLogger.log calls in SendCoinsService (e.g., the
memo/byteCount logs and the other logs referenced) inside `#if` DEBUG ... `#endif`
blocks and prepend an emoji prefix (like "🔧" or "🧪") to each log message to
follow repository conventions; locate usages of DSLogger.log in methods such as
sendMayaSwap and surrounding blocks (the calls around memoByteCount and the
later DSLogger.log calls) and change them to conditional debug-only logging with
the emoji prefix while leaving production behavior unchanged.

Source: Coding guidelines

DashWallet/Sources/UI/Maya/Convert/Components/EnterAmountView.swift (1)

40-50: ⚡ Quick win

Cache NumberFormatter to avoid repeated allocations.

Creating a new NumberFormatter on each access of the symbol computed property is inefficient. Since NumberFormatter initialization is relatively expensive and this property is accessed during view rendering (line 90), consider using a static cached formatter or moving the formatter creation outside the computed property.

⚡ Suggested optimization
+private let currencySymbolFormatter: NumberFormatter = {
+    let formatter = NumberFormatter()
+    formatter.numberStyle = .currency
+    return formatter
+}()
+
 enum CurrencyOption: Hashable {
     case fiat(String)
     case dash
     case coin(String)
 
     // ... existing code ...
     
     var symbol: String? {
         switch self {
         case .fiat(let code):
-            let formatter = NumberFormatter()
-            formatter.numberStyle = .currency
-            formatter.currencyCode = code
-            return formatter.currencySymbol
+            currencySymbolFormatter.currencyCode = code
+            return currencySymbolFormatter.currencySymbol
         case .dash: return nil
         case .coin(let code): return code
         }
     }
 }
🤖 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/Maya/Convert/Components/EnterAmountView.swift` around
lines 40 - 50, The symbol computed property creates a new NumberFormatter on
every access (in var symbol for case .fiat(let code)); cache a formatter instead
to avoid repeated allocations by adding a static cached NumberFormatter (or a
static [String: NumberFormatter] keyed by currency code) in the same
type/extension and reuse it inside symbol: set the cached formatter's
currencyCode = code (or fetch the cached per-code formatter) and return its
currencySymbol for .fiat, leaving .dash and .coin unchanged; update references
to use the cached formatter to improve performance during view rendering.
DashWallet/Sources/UI/Maya/Convert/MayaConvertHostingController.swift (1)

34-36: 💤 Low value

Mark unimplemented initializer as unavailable.

SwiftLint suggests marking the init(coder:) initializer as unavailable to make the intent explicit and prevent accidental use.

📝 Suggested annotation
-    required init?(coder: NSCoder) {
+    `@available`(*, unavailable)
+    required init?(coder: NSCoder) {
         fatalError("init(coder:) has not been implemented")
     }
🤖 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/Maya/Convert/MayaConvertHostingController.swift` around
lines 34 - 36, Mark the unimplemented initializer as explicitly unavailable:
annotate the required init?(coder: NSCoder) in MayaConvertHostingController with
`@available`(*, unavailable) so it cannot be used accidentally (keep the signature
required init?(coder: NSCoder) and the existing fatalError or empty body). This
makes the intent explicit to SwiftLint and prevents instantiation via
storyboard/NIB.

Source: Linters/SAST tools

DashWallet/Sources/UI/Maya/Convert/Components/MayaAmountView.swift (1)

66-67: 💤 Low value

Remove commented code.

The commented lines .scaleToFitWidth() and .layoutPriority(1) should be removed if they are no longer needed, as they add unnecessary clutter to the codebase.

🧹 Cleanup suggestion
             amountView
                 .foregroundStyle(Color.primaryText)
-//                .scaleToFitWidth()
-//                .layoutPriority(1)
 
             if showCurrencyButton {
🤖 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/Maya/Convert/Components/MayaAmountView.swift` around
lines 66 - 67, Remove the two commented-out legacy layout modifiers
(.scaleToFitWidth() and .layoutPriority(1)) from the MayaAmountView SwiftUI view
in MayaAmountView.swift; locate the commented lines within the view body (e.g.,
inside the struct or its body property) and delete them to clean up the file and
remove unnecessary clutter.
DashWallet/Sources/UI/Maya/Convert/MayaConvertViewModel.swift (2)

316-321: 💤 Low value

Consider providing user feedback on network failures.

Network errors are silently swallowed here, setting errorMessage = nil. While API-level errors (line 311-312) do get shown to the user, network failures leave users with no feedback about why the quote didn't appear.

Consider either showing a generic "Unable to fetch quote, please try again" message, or at minimum adding debug logging per the coding guidelines.

🛠️ Suggested improvement
         } catch {
             guard quoteRequestID == snapshot.id else { return }
             latestQuote = nil
-            errorMessage = nil
+            `#if` DEBUG
+            DSLogger.log("🌐 Quote fetch failed: \(error.localizedDescription)")
+            `#endif`
+            errorMessage = NSLocalizedString("Unable to get a quote", comment: "Maya")
             receiveAmount = 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/Maya/Convert/MayaConvertViewModel.swift` around lines
316 - 321, The catch block in MayaConvertViewModel (inside the async quote fetch
where quoteRequestID and snapshot.id are compared) currently swallows all
network errors by clearing latestQuote/errorMessage/receiveAmount; update it to
set a user-facing generic errorMessage (e.g., "Unable to fetch quote, please try
again") when quoteRequestID == snapshot.id, keep latestQuote/receiveAmount
cleared as before, and add a debug log of the caught error (using the project's
logging utility) so network failures are visible in logs for debugging.

Source: Coding guidelines


395-406: 💤 Low value

trimTrailingZeros can return empty string for input "0".

If trimTrailingZeros("0") is called, it removes the trailing "0" leaving an empty string. Currently this isn't reachable because receiveAmount always formats with decimals, but the function could silently break if used elsewhere.

Consider adding a safeguard:

🛠️ Suggested safeguard
     static func trimTrailingZeros(_ s: String) -> String {
         var result = s
         while result.hasSuffix("0") { result.removeLast() }
         if result.hasSuffix(".") { result.removeLast() }
+        return result.isEmpty ? "0" : result
-        return result
     }
🤖 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/Maya/Convert/MayaConvertViewModel.swift` around lines
395 - 406, The trimTrailingZeros function can return an empty string for input
"0"; update MayaInputFormatter.trimTrailingZeros to guard against that by
returning "0" when the trimmed result is empty (i.e., after removing trailing
zeros and a trailing dot, if result.isEmpty then return "0"). Keep
MayaInputFormatter.receiveAmount behavior unchanged but rely on the safe
trimTrailingZeros to never produce an empty string.
DashWallet/Sources/UI/Buy Sell/BuySellPortalViewController.swift (1)

168-171: 💤 Low value

Consider using static instead of class in final class.

Since BuySellPortalViewController is marked final, the factory method can use static func instead of class func for clarity and slight performance benefit.

♻️ Proposed refactor
 `@objc`
-class func controller() -> BuySellPortalViewController {
+static func controller() -> BuySellPortalViewController {
     BuySellPortalViewController()
 }
🤖 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/Buy` Sell/BuySellPortalViewController.swift around
lines 168 - 171, The factory method controller() on the final class
BuySellPortalViewController is declared as class func but should be static for
clarity and minor performance improvement; change the declaration from class
func controller() -> BuySellPortalViewController to static func controller() ->
BuySellPortalViewController while keeping the implementation body the same.

Source: Linters/SAST tools

DashWallet/Sources/UI/Home/Views/HomeViewModel.swift (1)

742-748: ⚖️ Poor tradeoff

Consider consolidating the CrowdNode filtering logic.

The CrowdNode state check here duplicates the logic in ShortcutActionType.customizableActions (ShortcutAction.swift lines 55-58). If one is updated without the other, users could select a CrowdNode shortcut in the picker but have it filtered out during reload, or vice versa.

Consider extracting this into a shared helper or computed property on ShortcutActionType:

extension ShortcutActionType {
    static func shouldShow(_ type: ShortcutActionType) -> Bool {
        if type == .crowdNode {
            let state = CrowdNode.shared.signUpState
            return state == .finished || state == .linkedOnline
        }
        return true
    }
}

Then use it in both locations:

// In customizableActions
if ShortcutActionType.shouldShow(.crowdNode) {
    actions.append(.crowdNode)
}

// In reloadShortcuts
.filter { ShortcutActionType.shouldShow($0.type) }

This ensures consistency and makes the business rule explicit in one place.

🤖 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/Home/Views/HomeViewModel.swift` around lines 742 - 748,
Duplicate CrowdNode visibility logic exists in the HomeViewModel reload filter
and in ShortcutActionType.customizableActions; extract the rule into a single
helper (e.g., add a static method or computed property on ShortcutActionType
like shouldShow(_:)) that checks if type == .crowdNode then inspects
CrowdNode.shared.signUpState (returning true only for .finished or
.linkedOnline) otherwise returns true, then update
ShortcutActionType.customizableActions to call that helper when deciding to
append .crowdNode and update the HomeViewModel filter block (the closure shown)
to use .filter { ShortcutActionType.shouldShow($0.type) } so both places share
the same logic.
DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutItemView.swift (2)

24-24: ⚡ Quick win

Remove unused alpha property.

The alpha property is declared but never used in the view body or anywhere else in the struct. This is dead code that should be removed.

🧹 Proposed fix
     let title: String
     let iconName: String
-    var alpha: CGFloat = 1.0
🤖 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/Home/Views/Shortcuts/ShortcutItemView.swift` at line
24, Remove the dead property declaration "var alpha: CGFloat = 1.0" from the
ShortcutItemView struct; locate the unused "alpha" property in ShortcutItemView
(the var named alpha) and delete it so the struct no longer contains an unused
stored property.

75-77: ⚡ Quick win

Remove duplicate "Disabled" preview.

The "Disabled" preview is identical to the "Enabled" preview—both render the same shortcutItemGrid() with no differences. Either apply a meaningful distinction (e.g., disabled appearance, different state) or remove this duplicate preview.

🧹 Proposed fix
 `#Preview`("Enabled") {
     shortcutItemGrid()
 }
-
-
-#Preview("Disabled") {
-    shortcutItemGrid()
-}
🤖 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/Home/Views/Shortcuts/ShortcutItemView.swift` around
lines 75 - 77, The "Disabled" SwiftUI preview that calls shortcutItemGrid() is a
duplicate of the "Enabled" preview; either remove the duplicate
Preview("Disabled") block or modify it to show a true disabled state (e.g., call
shortcutItemGrid().disabled(true) or supply a disabled/configured init/state to
the view) so the preview differs; update the Preview("Disabled") declaration and
its call to shortcutItemGrid() accordingly.
DashWallet/Sources/Models/Coinbase/Auth/CBAuth.swift (1)

150-151: ⚡ Quick win

Consider extracting the OAuth host to a constant.

The OAuth host "login.coinbase.com" is hardcoded here and also used in CoinbaseAPIEndpoint.baseURL (line 169). Extract it to Coinbase+Constants.swift alongside other OAuth constants (callbackURLScheme, redirectUri, etc.) for easier maintenance and consistency.

📝 Proposed refactor

In Coinbase+Constants.swift:

+    static let oauthHost = "login.coinbase.com"
     static let callbackURLScheme = "dashwallet"

Then use it here and in CoinbaseAPIEndpoint.swift:

     urlComponents.scheme = "https"
-    urlComponents.host = "login.coinbase.com"
+    urlComponents.host = Coinbase.oauthHost
     urlComponents.path = "/oauth2/auth"
🤖 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/Models/Coinbase/Auth/CBAuth.swift` around lines 150 - 151,
Hardcoded OAuth host "login.coinbase.com" is duplicated; extract it to a shared
constant (e.g., coinbaseOAuthHost) in Coinbase+Constants (alongside
callbackURLScheme/redirectUri) and replace occurrences in CBAuth (where
urlComponents.host is set) and in CoinbaseAPIEndpoint.baseURL to reference that
constant for consistency and easier maintenance.
DashWallet/Sources/Models/Maya/MayaAPIService.swift (1)

75-75: 💤 Low value

Consider adding emoji marker to debug log for easier filtering.

Per coding guidelines, debug logs should use emoji markers (e.g., 🌐 for network-related logs). This helps with log filtering during debugging.

📝 Suggested change
-            DSLogger.log("Maya: Address validation request failed: \(error)")
+            DSLogger.log("🌐 Maya: Address validation request failed: \(error)")
🤖 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/Models/Maya/MayaAPIService.swift` at line 75, The
DSLogger.log call that records failed address validation ("Maya: Address
validation request failed: \(error)") should include the recommended emoji
marker for network logs for easier filtering; update the log invocation in the
Maya API error path (the DSLogger.log call inside the address validation request
failure branch, likely within the method handling address validation in
MayaAPIService) to prepend an appropriate emoji like "🌐" to the message (e.g.,
"🌐 Maya: Address validation request failed: ...") so all network-related debug
logs are consistently marked.

Source: Coding guidelines

DashWallet/Sources/Models/Maya/MayaCoinIconLoader.swift (1)

62-64: 💤 Low value

Consider percent-encoding the coin code for robustness.

While coin codes are typically alphanumeric, adding percent encoding would make the URL construction more robust against unexpected characters.

🔧 Suggested enhancement
-        guard let url = URL(string: Self.baseURL + "\(code.lowercased()).png") else {
+        guard let encoded = code.lowercased().addingPercentEncoding(withAllowedCharacters: .urlPathAllowed),
+              let url = URL(string: Self.baseURL + "\(encoded).png") else {
             return 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/Models/Maya/MayaCoinIconLoader.swift` around lines 62 -
64, The URL construction using Self.baseURL + "\(code.lowercased()).png" should
percent-encode the coin code to handle unexpected characters; before building
the URL, call something like let encoded =
code.lowercased().addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)
and use encoded in place of code.lowercased(), returning nil if encoding fails;
update the code in MayaCoinIconLoader (the place that references Self.baseURL
and code.lowercased()) accordingly.
🤖 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/Resources/AppAssets.xcassets/maya.coin.link.imageset/Contents.json`:
- Around line 12-14: The Contents.json properties block incorrectly sets
"preserves-vector-representation" : true for PNG raster assets; open each
maya.coin.{link,pepe,rune,tgt,usdc,usdt,wbtc,wsteth}.imageset/Contents.json and
either remove the "preserves-vector-representation" property or change it to
false in the "properties" object so the "preserves-vector-representation" key is
not true for images whose filenames are maya.coin.<coin>.png.

In `@DashWallet/Resources/AppAssets.xcassets/Navigation`
bar/navigationbar-info.imageset/Contents.json:
- Around line 4-15: Update the incorrect scale suffixes in the imageset: replace
"controls-info@x1.png", "controls-info@x2.png", and "controls-info@x3.png" with
the iOS-standard "controls-info@1x.png", "controls-info@2x.png", and
"controls-info@3x.png" inside the Navigation bar/navigationbar-info.imageset
Contents.json and also rename the actual PNG files in the same imageset folder
to match those corrected filenames so the scale entries and filenames are
identical.

In
`@DashWallet/Resources/AppAssets.xcassets/Shortcuts/shortcut-bar-send-contact.imageset/Contents.json`:
- Around line 4-14: The imageset folder name shortcut-bar-send-contact.imageset
does not match the contained file names (shortcut-bar-send-account.png / `@2x` /
`@3x`); either rename the files to shortcut-bar-send-contact(.png / `@2x` / `@3x`) or
rename the folder to shortcut-bar-send-account.imageset to make names
consistent, and update any references to the asset in code/IB to the chosen
canonical name (shortcut-bar-send-contact or shortcut-bar-send-account) to avoid
runtime asset lookup issues.

In
`@DashWallet/Sources/Models/Coinbase/Infrastructure/API/CoinbaseAPIEndpoint.swift`:
- Around line 170-172: The .path case currently builds a full URL with
force-unwrapped URL(string:) in baseURL and leaves the path property empty,
risking crashes and breaking the TargetType contract; change baseURL to return
only the host (e.g., "https://api.coinbase.com") for all cases, move the route
string into the path computed property for the .path(let string) case (returning
the decoded/encoded route), and eliminate force-unwrapping by constructing any
full-URL overrides with safe URL(initializers) or URLComponents and handling the
optional (e.g., throw or return a safe fallback) inside the CoinbaseAPIEndpoint
implementation so no URL(string: ...) is force-unwrapped.

In `@DashWallet/Sources/Models/Transactions/SendCoinsService.swift`:
- Around line 130-134: The current authenticate() returns only Bool so callers
in SendCoinsService (uses of authenticate() in the send flow) conflate
user-cancel and hard failure into "Authentication cancelled"; change
authenticate() to surface distinct results (either throw AuthenticationError
with cases like .cancelled and .failed, or return Result<Void,
AuthenticationError>/enum) and update all callers in SendCoinsService (the
authenticate() call sites) to switch on the new result: throw
DashSpendError.paymentProcessingError("Authentication cancelled") only for
.cancelled and throw a different DashSpendError.paymentProcessingError with the
underlying error message for .failed (or rethrow the error) so cancellation vs
real failure are preserved.
- Around line 254-265: The current loop only checks per-input spend status via
account.isInputSpent and can still allow starting a new swap while a previous
swap is unconfirmed; add a wallet-level check using the new unconfirmed-swap API
before selecting/iterating inputs (e.g., call wallet.hasPreviousSwapPending() or
the provided wallet-level unconfirmed-swap method) and if it returns true throw
DashSpendError.previousSwapPending to enforce the new previousSwapPending
semantics alongside the existing per-input guard.

In `@DashWallet/Sources/UI/Buy` Sell/Model/ServiceDataProvider.swift:
- Around line 63-75: The updateServices() method duplicates mayaItems because
mayaItems is appended when building sortedItems (sortedItems = ... + mayaItems)
and then appended again in the handler call (handler?(sortedItems + mayaItems));
fix by returning the collection only once—either remove the + mayaItems when
creating sortedItems and keep handler?(sortedItems + mayaItems), or simpler:
keep sortedItems as currently built and change the handler call to
handler?(sortedItems) so mayaItems are appended only once; update references to
items, nonMayaItems, mayaItems, sortedItems and handler accordingly.

In `@DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutsView.swift`:
- Line 249: The comment on the frame height in ShortcutsView.swift is outdated:
it reads "68 cell + 8×2 vertical padding" but verticalPadding was changed to 4.0
(variable verticalPadding), so either change the height from 92 to 76 (68 + 4×2)
to match the new padding or update the comment to reflect why 92 is correct
(e.g., different base cell height or other paddings). Locate the .frame(width:
375, height: 92) line and either adjust the numeric height to 76 to match
verticalPadding or revise the inline comment to accurately explain the 92
calculation.

In `@DashWallet/Sources/UI/Maya/EnterAddress/Components/AddressSourceView.swift`:
- Around line 37-43: The switch in the computed property var title (in the
AddressSource/AddressSourceView enum) returns a hardcoded "Clipboard" string;
change that to a localized string. Replace the "Clipboard" literal with a
localization lookup (e.g. NSLocalizedString("clipboard", comment: "Address
source: Clipboard") or your app's L10n/Localization helper) so the case
.clipboard returns the localized value while leaving .uphold and .coinbase
as-is.

---

Outside diff comments:
In
`@DashWallet/Resources/AppAssets.xcassets/maya.coin.arb.imageset/Contents.json`:
- Line 13: Remove the "preserves-vector-representation" key from the
Contents.json manifests for all maya.coin.* image sets (e.g.,
maya.coin.arb.imageset) because these assets reference PNGs, not vectors; open
each maya.coin.* image set's Contents.json and delete the
"preserves-vector-representation" entry so the manifest only describes raster
images.

In `@DashWallet/Sources/UI/Buy` Sell/BuySellPortalViewController.swift:
- Around line 76-99: The navigateToCoinbase method currently force-unwraps
navigationController in the vc.userSignedOutBlock which can crash; update the
block to safely unwrap the navigation controller (e.g., guard let nav =
navigationController else { return }) and use that local nav to call
popToViewController and pushViewController (also ensure other uses of
navigationController? in this method use safe optional chaining or the same
guard before pushing ServiceOverviewViewController) so no force unwrapping
occurs; update references inside vc.userSignedOutBlock and the push logic to use
the safely unwrapped navigation controller.

---

Nitpick comments:
In `@DashWallet/Resources/AppAssets.xcassets/info-icon.imageset/Contents.json`:
- Around line 1-23: The asset catalog entry for "info-icon" needs a properties
block to enable system tinting; open the info-icon.imageset Contents.json and
add a top-level "properties" object containing "template-rendering-intent":
"template" (so the asset will render as a template image and inherit tint),
leaving the existing "images" and "info" entries intact.

In `@DashWallet/Resources/AppAssets.xcassets/Navigation`
bar/navigationbar-plus.imageset/Contents.json:
- Around line 23-25: The Contents.json for the navigationbar-plus imageset
removed the "template-rendering-intent" property which prevents the navigation
icon from being tinted by the system; restore "template-rendering-intent":
"template" inside the "properties" object of the navigationbar-plus.imageset
Contents.json (alongside "localizable": true) so the asset uses template
rendering and inherits tint colors.

In `@DashWallet/Sources/Models/Coinbase/Auth/CBAuth.swift`:
- Around line 150-151: Hardcoded OAuth host "login.coinbase.com" is duplicated;
extract it to a shared constant (e.g., coinbaseOAuthHost) in Coinbase+Constants
(alongside callbackURLScheme/redirectUri) and replace occurrences in CBAuth
(where urlComponents.host is set) and in CoinbaseAPIEndpoint.baseURL to
reference that constant for consistency and easier maintenance.

In `@DashWallet/Sources/Models/Maya/MayaAPIService.swift`:
- Line 75: The DSLogger.log call that records failed address validation ("Maya:
Address validation request failed: \(error)") should include the recommended
emoji marker for network logs for easier filtering; update the log invocation in
the Maya API error path (the DSLogger.log call inside the address validation
request failure branch, likely within the method handling address validation in
MayaAPIService) to prepend an appropriate emoji like "🌐" to the message (e.g.,
"🌐 Maya: Address validation request failed: ...") so all network-related debug
logs are consistently marked.

In `@DashWallet/Sources/Models/Maya/MayaCoinIconLoader.swift`:
- Around line 62-64: The URL construction using Self.baseURL +
"\(code.lowercased()).png" should percent-encode the coin code to handle
unexpected characters; before building the URL, call something like let encoded
= code.lowercased().addingPercentEncoding(withAllowedCharacters:
.urlPathAllowed) and use encoded in place of code.lowercased(), returning nil if
encoding fails; update the code in MayaCoinIconLoader (the place that references
Self.baseURL and code.lowercased()) accordingly.

In `@DashWallet/Sources/Models/Transactions/SendCoinsService.swift`:
- Around line 163-164: Wrap the diagnostic DSLogger.log calls in
SendCoinsService (e.g., the memo/byteCount logs and the other logs referenced)
inside `#if` DEBUG ... `#endif` blocks and prepend an emoji prefix (like "🔧" or
"🧪") to each log message to follow repository conventions; locate usages of
DSLogger.log in methods such as sendMayaSwap and surrounding blocks (the calls
around memoByteCount and the later DSLogger.log calls) and change them to
conditional debug-only logging with the emoji prefix while leaving production
behavior unchanged.

In `@DashWallet/Sources/UI/Buy` Sell/BuySellPortalViewController.swift:
- Around line 168-171: The factory method controller() on the final class
BuySellPortalViewController is declared as class func but should be static for
clarity and minor performance improvement; change the declaration from class
func controller() -> BuySellPortalViewController to static func controller() ->
BuySellPortalViewController while keeping the implementation body the same.

In `@DashWallet/Sources/UI/Home/Views/HomeViewModel.swift`:
- Around line 742-748: Duplicate CrowdNode visibility logic exists in the
HomeViewModel reload filter and in ShortcutActionType.customizableActions;
extract the rule into a single helper (e.g., add a static method or computed
property on ShortcutActionType like shouldShow(_:)) that checks if type ==
.crowdNode then inspects CrowdNode.shared.signUpState (returning true only for
.finished or .linkedOnline) otherwise returns true, then update
ShortcutActionType.customizableActions to call that helper when deciding to
append .crowdNode and update the HomeViewModel filter block (the closure shown)
to use .filter { ShortcutActionType.shouldShow($0.type) } so both places share
the same logic.

In `@DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutItemView.swift`:
- Line 24: Remove the dead property declaration "var alpha: CGFloat = 1.0" from
the ShortcutItemView struct; locate the unused "alpha" property in
ShortcutItemView (the var named alpha) and delete it so the struct no longer
contains an unused stored property.
- Around line 75-77: The "Disabled" SwiftUI preview that calls
shortcutItemGrid() is a duplicate of the "Enabled" preview; either remove the
duplicate Preview("Disabled") block or modify it to show a true disabled state
(e.g., call shortcutItemGrid().disabled(true) or supply a disabled/configured
init/state to the view) so the preview differs; update the Preview("Disabled")
declaration and its call to shortcutItemGrid() accordingly.

In `@DashWallet/Sources/UI/Maya/Convert/Components/EnterAmountView.swift`:
- Around line 40-50: The symbol computed property creates a new NumberFormatter
on every access (in var symbol for case .fiat(let code)); cache a formatter
instead to avoid repeated allocations by adding a static cached NumberFormatter
(or a static [String: NumberFormatter] keyed by currency code) in the same
type/extension and reuse it inside symbol: set the cached formatter's
currencyCode = code (or fetch the cached per-code formatter) and return its
currencySymbol for .fiat, leaving .dash and .coin unchanged; update references
to use the cached formatter to improve performance during view rendering.

In `@DashWallet/Sources/UI/Maya/Convert/Components/MayaAmountView.swift`:
- Around line 66-67: Remove the two commented-out legacy layout modifiers
(.scaleToFitWidth() and .layoutPriority(1)) from the MayaAmountView SwiftUI view
in MayaAmountView.swift; locate the commented lines within the view body (e.g.,
inside the struct or its body property) and delete them to clean up the file and
remove unnecessary clutter.

In `@DashWallet/Sources/UI/Maya/Convert/MayaConvertHostingController.swift`:
- Around line 34-36: Mark the unimplemented initializer as explicitly
unavailable: annotate the required init?(coder: NSCoder) in
MayaConvertHostingController with `@available`(*, unavailable) so it cannot be
used accidentally (keep the signature required init?(coder: NSCoder) and the
existing fatalError or empty body). This makes the intent explicit to SwiftLint
and prevents instantiation via storyboard/NIB.

In `@DashWallet/Sources/UI/Maya/Convert/MayaConvertViewModel.swift`:
- Around line 316-321: The catch block in MayaConvertViewModel (inside the async
quote fetch where quoteRequestID and snapshot.id are compared) currently
swallows all network errors by clearing latestQuote/errorMessage/receiveAmount;
update it to set a user-facing generic errorMessage (e.g., "Unable to fetch
quote, please try again") when quoteRequestID == snapshot.id, keep
latestQuote/receiveAmount cleared as before, and add a debug log of the caught
error (using the project's logging utility) so network failures are visible in
logs for debugging.
- Around line 395-406: The trimTrailingZeros function can return an empty string
for input "0"; update MayaInputFormatter.trimTrailingZeros to guard against that
by returning "0" when the trimmed result is empty (i.e., after removing trailing
zeros and a trailing dot, if result.isEmpty then return "0"). Keep
MayaInputFormatter.receiveAmount behavior unchanged but rely on the safe
trimTrailingZeros to never produce an empty string.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

Comment thread DashWallet/Sources/Models/Coinbase/Infrastructure/API/CoinbaseAPIEndpoint.swift Outdated
Comment thread DashWallet/Sources/Models/Transactions/SendCoinsService.swift
Comment thread DashWallet/Sources/Models/Transactions/SendCoinsService.swift
Comment thread DashWallet/Sources/UI/Buy Sell/Model/ServiceDataProvider.swift
Comment thread DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutsView.swift
Why:
- reduce English fallbacks in the Maya conversion flow across supported locales

What:
- update Maya portal and conversion-related strings in 20 localization files
- refresh labels such as Convert Dash, Contact Maya Support, Receive amount, Loading, and Log In

Validation:
- build/tests were not run because the staged diff only changes .strings files

@HashEngineering HashEngineering left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks fine. the individual PR's were reviewed previously

@HashEngineering HashEngineering left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

Why:
- prevent the Maya service card from being appended twice in the Buy & Sell service list

What:
- return the already-sorted service items without concatenating mayaItems a second time

Validation:
- build/tests were not run because the staged diff is a one-line logic fix
@romchornyi

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Why:
- address follow-up issues around swap submission safety and asset catalog consistency

What:
- add a wallet-level guard to block starting a new swap while a previous swap is still unconfirmed
- align navigationbar-info asset filenames with their expected 1x/2x/3x naming in the image set manifest

Validation:
- build/tests were not run in this session before committing

@HashEngineering HashEngineering left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@romchornyi romchornyi dismissed HashEngineering’s stale review June 12, 2026 08:14

The merge-base changed after approval.

Why:
- Backport Maya convert fixes and related UI polish onto the release branch without pulling in risky localization churn.

What:
- Fix coin-input gross-up and Max behavior on the Maya convert screen.
- Polish Maya select coin, order preview, portal, loading spinner, and shortcut item UI.

Validation:
- Not run.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
DashWallet/Sources/Models/Transactions/MayaSwapPendingGate.swift (1)

83-83: 💤 Low value

Add emoji prefix to log statement per coding guidelines.

Coding guidelines recommend using emoji debug markers with DSLogger for easier log filtering (e.g., 🔍 for search, 🔄 for sync operations). Consider using an appropriate emoji prefix here.

-DSLogger.log("MayaSwapPendingGate: IS-lock received for \(expected) — gate released")
+DSLogger.log("🔄 MayaSwapPendingGate: IS-lock received for \(expected) — gate released")
🤖 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/Models/Transactions/MayaSwapPendingGate.swift` at line 83,
The DSLogger.log statement that logs the IS-lock received message for the gate
release is missing an emoji prefix required by coding guidelines. Add an
appropriate emoji prefix (such as 🔄 for sync operations or ✅ for completion) to
the beginning of the log message string in the DSLogger.log call to align with
the project's debug logging conventions and enable easier log filtering.

Source: Coding guidelines

🤖 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/Models/Transactions/MayaSwapPendingGate.swift`:
- Around line 54-68: The observer property assignment in the register method
occurs after lock.unlock() is called, creating a race condition where
clearLocked() could be called from the notification handler before observer is
assigned. Move the entire observer registration code (the
NotificationCenter.default.addObserver call) inside the lock block, before
lock.unlock() is called, so that all mutations to the observer property are
atomic and protected by the same lock that clearLocked() expects to be held when
it accesses observer.

In `@DashWallet/Sources/UI/Maya/Convert/MayaConvertViewModel.swift`:
- Around line 261-267: In the applyQuoteError method, the ternary operator
evaluating the apiError condition currently returns the same localized string
for both the true and false branches (lines 264-267). This is a logic error—the
condition checks whether the apiError contains "not enough asset to pay for
fees" to provide a specific message, but the else branch should display a
different, more generic error message instead of duplicating the same one.
Update the else branch of the ternary operator to return a generic error message
(for example, the original apiError value or a generic "Conversion failed" type
message) rather than the identical "Amount too small to cover fees" string.

---

Nitpick comments:
In `@DashWallet/Sources/Models/Transactions/MayaSwapPendingGate.swift`:
- Line 83: The DSLogger.log statement that logs the IS-lock received message for
the gate release is missing an emoji prefix required by coding guidelines. Add
an appropriate emoji prefix (such as 🔄 for sync operations or ✅ for completion)
to the beginning of the log message string in the DSLogger.log call to align
with the project's debug logging conventions and enable easier log filtering.
🪄 Autofix (Beta)

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

Run ID: 46f8434e-47b2-463e-bf45-9598134344e1

📥 Commits

Reviewing files that changed from the base of the PR and between 4d825ba and c7ce94f.

⛔ Files ignored due to path filters (3)
  • DashWallet/Resources/AppAssets.xcassets/Navigation bar/navigationbar-info.imageset/controls-info@1x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Navigation bar/navigationbar-info.imageset/controls-info@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Navigation bar/navigationbar-info.imageset/controls-info@3x.png is excluded by !**/*.png
📒 Files selected for processing (9)
  • DashWallet.xcodeproj/project.pbxproj
  • DashWallet/Resources/AppAssets.xcassets/Navigation bar/navigationbar-info.imageset/Contents.json
  • DashWallet/Sources/Models/Explore Dash/Services/DashSpend/DashSpendError.swift
  • DashWallet/Sources/Models/Transactions/MayaSwapPendingGate.swift
  • DashWallet/Sources/Models/Transactions/SendCoinsService.swift
  • DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutItemView.swift
  • DashWallet/Sources/UI/Maya/Convert/Components/EnterAmountView.swift
  • DashWallet/Sources/UI/Maya/Convert/MayaConvertView.swift
  • DashWallet/Sources/UI/Maya/Convert/MayaConvertViewModel.swift
✅ Files skipped from review due to trivial changes (1)
  • DashWallet/Resources/AppAssets.xcassets/Navigation bar/navigationbar-info.imageset/Contents.json
🚧 Files skipped from review as they are similar to previous changes (5)
  • DashWallet/Sources/UI/Maya/Convert/Components/EnterAmountView.swift
  • DashWallet/Sources/Models/Explore Dash/Services/DashSpend/DashSpendError.swift
  • DashWallet/Sources/UI/Home/Views/Shortcuts/ShortcutItemView.swift
  • DashWallet/Sources/Models/Transactions/SendCoinsService.swift
  • DashWallet/Sources/UI/Maya/Convert/MayaConvertView.swift

Comment thread DashWallet/Sources/Models/Transactions/MayaSwapPendingGate.swift
Comment on lines +261 to +267
private func applyQuoteError(_ apiError: String) {
latestQuote = nil
receiveAmount = nil
errorMessage = apiError.contains("not enough asset to pay for fees")
? NSLocalizedString("Amount too small to cover fees", comment: "Maya")
: NSLocalizedString("Amount too small to cover fees", comment: "Maya")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Both ternary branches produce identical error messages.

Lines 264-267 return the same localized string regardless of whether the API error contains "not enough asset to pay for fees". This appears to be a copy-paste error—the else branch should likely display a different, more generic error message.

Suggested fix
     private func applyQuoteError(_ apiError: String) {
         latestQuote = nil
         receiveAmount = nil
         errorMessage = apiError.contains("not enough asset to pay for fees")
             ? NSLocalizedString("Amount too small to cover fees", comment: "Maya")
-            : NSLocalizedString("Amount too small to cover fees", comment: "Maya")
+            : NSLocalizedString("Unable to get quote", comment: "Maya")
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private func applyQuoteError(_ apiError: String) {
latestQuote = nil
receiveAmount = nil
errorMessage = apiError.contains("not enough asset to pay for fees")
? NSLocalizedString("Amount too small to cover fees", comment: "Maya")
: NSLocalizedString("Amount too small to cover fees", comment: "Maya")
}
private func applyQuoteError(_ apiError: String) {
latestQuote = nil
receiveAmount = nil
errorMessage = apiError.contains("not enough asset to pay for fees")
? NSLocalizedString("Amount too small to cover fees", comment: "Maya")
: NSLocalizedString("Unable to get quote", comment: "Maya")
}
🤖 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/Maya/Convert/MayaConvertViewModel.swift` around lines
261 - 267, In the applyQuoteError method, the ternary operator evaluating the
apiError condition currently returns the same localized string for both the true
and false branches (lines 264-267). This is a logic error—the condition checks
whether the apiError contains "not enough asset to pay for fees" to provide a
specific message, but the else branch should display a different, more generic
error message instead of duplicating the same one. Update the else branch of the
ternary operator to return a generic error message (for example, the original
apiError value or a generic "Conversion failed" type message) rather than the
identical "Amount too small to cover fees" string.

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

🧹 Nitpick comments (1)
DashWallet/Sources/Models/Maya/MayaCoinIconLoader.swift (1)

22-29: 💤 Low value

Doc comment is outdated after adding SwapKit CDN source.

The comment at lines 24-25 states icons are fetched from jsupa/crypto-icons, but the primary source is now the SwapKit CDN with jsupa as fallback. Consider updating to reflect the dual-source approach.

📝 Suggested doc update
 /// Loads remote coin icons with a two-level cache: memory (`NSCache`) + disk (`URLCache`).
 ///
-/// Icons are fetched from the jsupa/crypto-icons repository using the lowercased coin code,
-/// matching the Android implementation. Disk-cached entries survive app restarts and are
+/// Icons are fetched primarily from the SwapKit token-list CDN using the full asset identifier,
+/// with jsupa/crypto-icons as a fallback using the lowercased coin code. Disk-cached entries survive app restarts and are
 /// served without re-downloading until the OS purges the cache.
🤖 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/Models/Maya/MayaCoinIconLoader.swift` around lines 22 -
29, The documentation comment for the MayaCoinIconLoader class incorrectly
states that icons are fetched from the jsupa/crypto-icons repository. Update the
doc comment to accurately reflect the current dual-source approach where the
SwapKit CDN is the primary source for fetching coin icons and jsupa/crypto-icons
is used as a fallback. This ensures the documentation accurately describes the
actual implementation behavior.
🤖 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.

Nitpick comments:
In `@DashWallet/Sources/Models/Maya/MayaCoinIconLoader.swift`:
- Around line 22-29: The documentation comment for the MayaCoinIconLoader class
incorrectly states that icons are fetched from the jsupa/crypto-icons
repository. Update the doc comment to accurately reflect the current dual-source
approach where the SwapKit CDN is the primary source for fetching coin icons and
jsupa/crypto-icons is used as a fallback. This ensures the documentation
accurately describes the actual implementation behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 358844d9-0392-49be-b747-c5e41fe58b1e

📥 Commits

Reviewing files that changed from the base of the PR and between c7ce94f and 68bbdf4.

📒 Files selected for processing (7)
  • DashWallet.xcodeproj/project.pbxproj
  • DashWallet/Sources/Models/Maya/MayaCoinIconLoader.swift
  • DashWallet/Sources/Models/Transactions/MayaSwapPendingGate.swift
  • DashWallet/Sources/UI/Maya/Convert/Components/EnterAmountView.swift
  • DashWallet/Sources/UI/Maya/Convert/Components/MayaAmountView.swift
  • DashWallet/Sources/UI/Maya/Convert/MayaConvertView.swift
  • DashWallet/Sources/UI/Maya/Convert/MayaConvertViewModel.swift
🚧 Files skipped from review as they are similar to previous changes (4)
  • DashWallet/Sources/UI/Maya/Convert/Components/EnterAmountView.swift
  • DashWallet/Sources/UI/Maya/Convert/Components/MayaAmountView.swift
  • DashWallet/Sources/Models/Transactions/MayaSwapPendingGate.swift
  • DashWallet/Sources/UI/Maya/Convert/MayaConvertView.swift

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.

4 participants