Skip to content

feat(wallet): remove CrowdNode - #1268

Open
romchornyi wants to merge 10 commits into
developfrom
feat/remove-crowdnode
Open

romchornyi wants to merge 10 commits into
developfrom
feat/remove-crowdnode

Conversation

@romchornyi

@romchornyi romchornyi commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Issue being fixed or feature implemented

Jira MO-1115: remove the CrowdNode feature from the app — no CrowdNode screens, validations or messaging, and no search for CrowdNode transactions in the wallet's history.

This goes further than Android today, which keeps a withdraw-only mode (dash-wallet #1490, #1497). Both apps delete CrowdNode's stored settings after the upgrade.

What was done?

Removed

  • Models/CrowdNode and UI/CrowdNode (storyboard included), the CrowdNode notification producer, and their assets.
  • Home: the CrowdNode shortcut, the balance-reminder sheet and the "CrowdNode · Account" group (it was never filled, so past CrowdNode transactions were already plain rows and stay so).
  • Explore: the Staking item and the reminder banner, in both the SwiftUI and the UIKit screen.
  • Validations: the "Looks like you are emptying your Dash Wallet" warning on Send, Buy credits and Coinbase transfer.
  • Send: the selected-input path in WalletSendService / SwiftDashSDKTransactionSender, used only for CrowdNode signal transactions. Its fee model stays as estimatedFee, because the Max fee reserve uses it.
  • Notifications: the crowdnode topic and the staking route. A tap on an old notification just opens the app.
  • 132 catalog keys used only by the removed code, in all 43 locales; no translation of a remaining key changed. The Coinbase deposit error now reuses the translated "Couldn't complete the transfer".

Moved, because other features use them

  • TransactionObserver / ObservedTransaction (transaction notifications, Receive) → Models/Transactions/Observation, trimmed to what those callers read.
  • GroupedTransactionsScreen (CoinJoin groups) → UI/Tx/Details.

Upgrade handling for what older builds left behind

  • Shortcut raw values persist in DWGlobalOptions.shortcuts, so every case after the removed one is pinned (coinbase = 19 … nodes = 25) and 18 is retired. Restoring a saved bar moved into one pure function, ShortcutActionType.restoredBar: it applies the existing availability fallbacks, then fills a saved 18 with the first action the bar doesn't show yet (Spend, the faucet on testnet, then Explore, Receive…), so the bar keeps its size and the retired slot adds no duplicate. Duplicates the existing fallbacks can produce on their own are a separate ticket.
  • Delivered notifications on the crowdnode thread or with the legacy CrowdNode id are removed on every activation. A migration (MarkRetiredNotificationTopicsSeen) marks that topic's unseen events seen, so a background notification posted before the first activation doesn't count them in the badge; the rows stay for dedup.
  • RetiredCrowdNodeDefaults removes CrowdNode's 17 UserDefaults keys — global, bare legacy and <key>_<walletIdHex> for every wallet; one key starts with a Cyrillic "с", so the list is copied byte for byte. A per-wallet name is removed only when a lowercase hex wallet id follows the underscore, as the old CrowdNode code wrote it, so another setting that merely starts the same way stays. It is called from handleDidBecomeActive and retries on each activation until done; it skips an activation without protected data (before the first unlock the defaults read back empty). Removing a wallet deletes that wallet's keys, and the full wipe deletes them all; the wipe's UserDefaults resets moved from performWipe into resetUserDefaultsStoresForWipe() so a test can call them. crowdnode.io cookies and website data stay, as in Android's CrowdNode removal (dash-wallet #1664): nothing in the app opens the site any more.

Decisions — recorded in the decision log as ADR-0006 (remove CrowdNode entirely, as Android does), ADR-0007 (pin shortcut raw values and refill the retired slot), ADR-0008 (remove CrowdNode defaults by exact key list on activation) and ADR-0009 (retire the CrowdNode topic by marking its events seen), all Proposed. Numbers 0003–0005 are taken by #1311.

How Has This Been Tested?

  • dashpay scheme builds for the simulator against platform 1721ca7a8b (.platform-ref).
  • DashWalletTests on an iPhone 17 simulator (iOS 26.5): 1536 tests, 0 failures, 1 skipped (after merging develop). New: ShortcutActionTypeTests (pinned raw values; restoredBar from raw saved values, including slots that fall back to Spend/Receive), RetiredCrowdNodeDefaultsTests (every key form, the Cyrillic key, foreign keys untouched, a non-hex suffix kept, once-only, no protected data → nothing recorded), WiperRemovesCrowdNodeKeysTests (wallet removal and the full wipe), MaxSendFeeReserveTests (the Max fee reserve is unchanged), CoinbaseDepositFailedErrorTests (the translated transfer error), migration and tray tests in NotifiedEventStoreTests / NotificationLifecycleTests, and the transaction-notification tests for the former deposit amount.
  • By hand, on the simulator with a branch build, after updating a mainnet wallet that has CrowdNode history: no CrowdNode entry point, banner, sheet or text on Home, the shortcut bar settings, Explore, the menu, Send, Receive or Settings; the CrowdNode transactions show as ordinary rows with the same balance; the bar's CrowdNode slot shows Spend in the same place; the connection log has no CrowdNode host.
  • scripts/test_core_drain.py (wiper harness): 27 passed.
  • scripts/a11y_audit.py --check: no new findings; the baseline only loses entries of deleted files.
  • scripts/ci/check_test_target_membership.py: ok.

Breaking Changes

None for the app's API. Users who still hold funds at CrowdNode can no longer withdraw from the app; they withdraw on crowdnode.io.

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
  • ADR (ADR-0001): ADR-0006, ADR-0007, ADR-0008, ADR-0009 added (Proposed)
  • Nothing in this PR (files, commit messages, branch name, description, comments, attachments, linked issues) holds customer data, unfixed vulnerability detail, secrets, or step-by-step attack detail against keys, PIN or encryption; such content is left out and handled privately with the maintainers (ADR-0001, rule 2)

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

🤖 Generated with Claude Code

romchornyi and others added 4 commits October 30, 2026 17:27
Remove the CrowdNode integration: its screens, Home shortcut and balance
reminder, the Explore staking entry, the leftover-balance warnings on
Send, Buy credits and Coinbase transfer, its notifications, the API
client and the CrowdNode transaction matching. Past CrowdNode
transactions show as ordinary rows.

Shared pieces that lived in the CrowdNode folders move out:
TransactionObserver and ObservedTransaction (transaction notifications,
Receive) to Models/Transactions/Observation, GroupedTransactionsScreen
(CoinJoin groups) to UI/Tx/Details. The selected-input send path, used
only for CrowdNode signal transactions, goes; its fee model stays as
estimatedFee for the Max reserve.

Upgrades are handled for data older builds left behind:
- shortcut raw values are pinned; a saved retired slot becomes the first
  action not already on the bar
- delivered notifications on the retired thread or id are removed on
  activation, and a migration marks the retired topic's events seen
- CrowdNode's UserDefaults and crowdnode.io website data are removed
  once, after protected data is available

The Coinbase deposit error reuses the translated "Couldn't complete the
transfer". 132 keys used only by the removed code leave the catalogs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- fill a retired shortcut slot after the availability fallbacks, so it
  never adds a button the bar already shows
- record the crowdnode.io website data as removed only once WebKit
  reports it done, and match the domain on a label boundary
- run the cleanup after launch rather than inside it
- drop the crowdnode PR-title scope and a stale comment

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ShortcutActionType.restoredBar applies the availability fallbacks and
  fills a retired slot last, against the bar as shown; HomeViewModel
  calls it instead of mapping inline
- crowdnode.io website data is removed on the first activation, never
  during a background launch, and matched in any letter case
- the crowdnode PR-title scope stays: cleanup code still carries it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Call RetiredCrowdNodeDefaults from AppDelegate's handleDidBecomeActive
instead of launch plus a self-removing observer, so the defaults pass
and the website-data pass both retry on every activation until done.
Comments now say where the retired shortcut slot is mapped and that the
availability fallbacks don't check for duplicates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 30, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 201 files, which is 101 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 64fc93a2-e008-4ee1-aa96-96260e5c8e6c
Commits

Reviewing files that changed from the base of the PR and between 3201192 and 2047727.

Files ignored due to path filters: 75

  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.account.link.imageset/icon.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.account.link.imageset/icon@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.account.link.imageset/icon@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.account.new.imageset/icon.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.account.new.imageset/icon@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.account.new.imageset/icon@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.apy.imageset/icon-apy.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.apy.imageset/icon-apy@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.apy.imageset/icon-apy@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.bullet.point.imageset/icon.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.bullet.point.imageset/icon@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.bullet.point.imageset/icon@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.deposit.imageset/deposit.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.deposit.imageset/deposit@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.deposit.imageset/deposit@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.error.imageset/error.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.error.imageset/error@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.error.imageset/error@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.first.deposit.imageset/icon.first.minimum.deposit.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.first.deposit.imageset/icon.first.minimum.deposit@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.first.deposit.imageset/icon.first.minimum.deposit@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.history.imageset/history.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.history.imageset/history@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.history.imageset/history@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.info.imageset/Icon.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.info.imageset/Icon@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.info.imageset/Icon@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.joining.pool.imageset/icon.join.the.pool.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.joining.pool.imageset/icon.join.the.pool@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.joining.pool.imageset/icon.join.the.pool@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.leaning.pool.imageset/icon.leaving.the.pool.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.leaning.pool.imageset/icon.leaving.the.pool@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.leaning.pool.imageset/icon.leaving.the.pool@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.lightning.imageset/icon.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.lightning.imageset/icon@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.lightning.imageset/icon@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.logo.imageset/logo.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.logo.imageset/logo@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.logo.imageset/logo@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.online.imageset/online.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.online.imageset/online@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.online.imageset/online@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.payout.imageset/payout.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.payout.imageset/payout@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.payout.imageset/payout@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.receiving.rewards.imageset/icon.receiving.rewards.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.receiving.rewards.imageset/icon.receiving.rewards@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.receiving.rewards.imageset/icon.receiving.rewards@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.recovery.imageset/recovery.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.recovery.imageset/recovery@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.recovery.imageset/recovery@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.staking.imageset/staking.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.staking.imageset/staking@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.staking.imageset/staking@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.support.imageset/support.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.support.imageset/support@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.support.imageset/support@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.warning.imageset/icon.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.warning.imageset/icon@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.warning.imageset/icon@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.withdraw.imageset/withdraw.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.withdraw.imageset/withdraw@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.withdraw.imageset/withdraw@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.explore.dash.staking.imageset/icon-staking.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.explore.dash.staking.imageset/icon-staking@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.explore.dash.staking.imageset/icon-staking@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-staking.imageset/image-menu-staking.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-staking.imageset/image-menu-staking@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-staking.imageset/image-menu-staking@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Shortcuts/shortcut-bar-crowdnode.imageset/shortcut-bar-crowdnode.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Shortcuts/shortcut-bar-crowdnode.imageset/shortcut-bar-crowdnode@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Shortcuts/shortcut-bar-crowdnode.imageset/shortcut-bar-crowdnode@3x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Transactions/tx.item.cn.icon.imageset/cn-tx.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Transactions/tx.item.cn.icon.imageset/cn-tx@2x.png is excluded by !**/*.png
  • DashWallet/Resources/AppAssets.xcassets/Transactions/tx.item.cn.icon.imageset/cn-tx@3x.png is excluded by !**/*.png
Files selected for processing: 201

  • .claude/agents/EXTERNAL-INTEGRATIONS.md
  • .claude/agents/PROJECT-FUNCTIONS.md
  • CLAUDE.md
  • DASHSYNC_KEY_MIGRATION.md
  • DashWallet.xcodeproj/project.pbxproj
  • DashWallet/AppDelegate.m
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.account.link.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.account.new.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.apy.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.bullet.point.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.deposit.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.error.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.first.deposit.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.history.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.info.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.joining.pool.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.leaning.pool.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.lightning.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.logo.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.online.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.payout.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.receiving.rewards.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.recovery.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.staking.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.support.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.warning.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.crowdnode.withdraw.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/CrowdNode/image.explore.dash.staking.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Menu/image-menu-staking.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Shortcuts/shortcut-bar-crowdnode.imageset/Contents.json
  • DashWallet/Resources/AppAssets.xcassets/Transactions/tx.item.cn.icon.imageset/Contents.json
  • DashWallet/Sources/Infrastructure/Database/DatabaseConnection.swift
  • DashWallet/Sources/Infrastructure/Notifications/Core/DeepLinkRoute.swift
  • DashWallet/Sources/Infrastructure/Notifications/Core/NotificationTopic.swift
  • DashWallet/Sources/Infrastructure/Notifications/NotificationLifecycle.swift
  • DashWallet/Sources/Infrastructure/Notifications/NotificationRouter.swift
  • DashWallet/Sources/Infrastructure/Notifications/NotificationsBootstrap.swift
  • DashWallet/Sources/Infrastructure/Notifications/NotifiedEventStore.swift
  • DashWallet/Sources/Infrastructure/Notifications/Producers/CrowdNodeNotificationProducer.swift
  • DashWallet/Sources/Infrastructure/Notifications/Producers/TransactionNotificationProducer.swift
  • DashWallet/Sources/Infrastructure/RetiredCrowdNodeDefaults.swift
  • DashWallet/Sources/Infrastructure/SwiftDashSDK/AssetLockInputPolicy.swift
  • DashWallet/Sources/Infrastructure/SwiftDashSDK/Contacts/SwiftDashSDKContactsService.swift
  • DashWallet/Sources/Infrastructure/SwiftDashSDK/SwiftDashSDKTransactionSender.swift
  • DashWallet/Sources/Infrastructure/SwiftDashSDK/SwiftDashSDKWalletRuntime.swift
  • DashWallet/Sources/Infrastructure/SwiftDashSDK/SwiftDashSDKWalletState.swift
  • DashWallet/Sources/Infrastructure/SwiftDashSDK/SwiftDashSDKWalletWiper.swift
  • DashWallet/Sources/Infrastructure/SwiftDashSDK/WalletEnvironment.swift
  • DashWallet/Sources/Models/Coinbase/Coinbase+Error.swift
  • DashWallet/Sources/Models/CrowdNode/API/CrowdNodeEndpoint.swift
  • DashWallet/Sources/Models/CrowdNode/API/DTOs/AddressStatus.swift
  • DashWallet/Sources/Models/CrowdNode/API/DTOs/CrowdNodeBalance.swift
  • DashWallet/Sources/Models/CrowdNode/API/DTOs/FeeInfo.swift
  • DashWallet/Sources/Models/CrowdNode/API/DTOs/IsAddressInUse.swift
  • DashWallet/Sources/Models/CrowdNode/API/DTOs/IsDefaultEmail.swift
  • DashWallet/Sources/Models/CrowdNode/API/DTOs/MessageStatus.swift
  • DashWallet/Sources/Models/CrowdNode/API/DTOs/WithdrawalLimit.swift
  • DashWallet/Sources/Models/CrowdNode/CrowdNode+Constants.swift
  • DashWallet/Sources/Models/CrowdNode/CrowdNode+UserDefaults.swift
  • DashWallet/Sources/Models/CrowdNode/CrowdNode.swift
  • DashWallet/Sources/Models/CrowdNode/CrowdNodeBalanceReminder.swift
  • DashWallet/Sources/Models/CrowdNode/MasternodeAPYCalculator.swift
  • DashWallet/Sources/Models/CrowdNode/Model/ApiCode.swift
  • DashWallet/Sources/Models/CrowdNode/Model/CrowdNodeError.swift
  • DashWallet/Sources/Models/CrowdNode/Services/CrowdNodeWebService.swift
  • DashWallet/Sources/Models/CrowdNode/Services/DarkCoinMessageFraming.swift
  • DashWallet/Sources/Models/CrowdNode/TxFilters/CoinsToAddressTxFilter.swift
  • DashWallet/Sources/Models/CrowdNode/TxFilters/CrowdNodeAPIConfirmationTx.swift
  • DashWallet/Sources/Models/CrowdNode/TxFilters/CrowdNodeDepositTx.swift
  • DashWallet/Sources/Models/CrowdNode/TxFilters/CrowdNodeErrorResponse.swift
  • DashWallet/Sources/Models/CrowdNode/TxFilters/CrowdNodeRequest.swift
  • DashWallet/Sources/Models/CrowdNode/TxFilters/CrowdNodeResponse.swift
  • DashWallet/Sources/Models/CrowdNode/TxFilters/CrowdNodeTopUpTx.swift
  • DashWallet/Sources/Models/CrowdNode/TxFilters/CrowdNodeWithdrawalReceivedTx.swift
  • DashWallet/Sources/Models/CrowdNode/TxFilters/FullCrowdNodeSignUpTxSet.swift
  • DashWallet/Sources/Models/CrowdNode/TxFilters/SingleInputAddressSelector.swift
  • DashWallet/Sources/Models/CrowdNode/TxFilters/TransactionWrapper.swift
  • DashWallet/Sources/Models/CrowdNode/TxFilters/TxWithinTimePeriod.swift
  • DashWallet/Sources/Models/Transactions/Model/Transaction.swift
  • DashWallet/Sources/Models/Transactions/Observation/TransactionFilter.swift
  • DashWallet/Sources/Models/Transactions/Observation/TransactionObserver.swift
  • DashWallet/Sources/Models/Transactions/SendCoinsService.swift
  • DashWallet/Sources/Models/Transactions/WalletSendService.swift
  • DashWallet/Sources/Models/Tx Metadata/ServiceName.swift
  • DashWallet/Sources/UI/Coinbase/Transfer Amount/TransferAmountHostingController.swift
  • DashWallet/Sources/UI/Coinbase/Transfer Amount/TransferAmountViewModel.swift
  • DashWallet/Sources/UI/CrowdNode/BalanceReminder/CrowdNodeBalanceReminderBanner.swift
  • DashWallet/Sources/UI/CrowdNode/BalanceReminder/CrowdNodeBalanceReminderSheet.swift
  • DashWallet/Sources/UI/CrowdNode/BalanceReminder/CrowdNodeWithdrawalRouter.swift
  • DashWallet/Sources/UI/CrowdNode/CrowdNode.storyboard
  • DashWallet/Sources/UI/CrowdNode/CrowdNodeModel.swift
  • DashWallet/Sources/UI/CrowdNode/Extensions/UINavigationController+CrowdNode.swift
  • DashWallet/Sources/UI/CrowdNode/Extensions/WKWebView+CrowdNode.swift
  • DashWallet/Sources/UI/CrowdNode/Getting Started/GettingStartedViewController.swift
  • DashWallet/Sources/UI/CrowdNode/Getting Started/WelcomeToCrowdNodeViewController.swift
  • DashWallet/Sources/UI/CrowdNode/New Account/AccountCreatingController.swift
  • DashWallet/Sources/UI/CrowdNode/New Account/NewAccountViewController.swift
  • DashWallet/Sources/UI/CrowdNode/Online/ConfirmationTransactionQRController.swift
  • DashWallet/Sources/UI/CrowdNode/Online/CrowdNodeWebViewController.swift
  • DashWallet/Sources/UI/CrowdNode/Online/OnlineAccountConfirmationController.swift
  • DashWallet/Sources/UI/CrowdNode/Online/OnlineAccountDetailsController.swift
  • DashWallet/Sources/UI/CrowdNode/Online/OnlineAccountEmailController.swift
  • DashWallet/Sources/UI/CrowdNode/Online/OnlineAccountInfoController.swift
  • DashWallet/Sources/UI/CrowdNode/Portal/CrowdNodePortalViewController.swift
  • DashWallet/Sources/UI/CrowdNode/Portal/CrowdNodeTransferViewController.swift
  • DashWallet/Sources/UI/CrowdNode/Portal/Model/CrowdNodePortalItem.swift
  • DashWallet/Sources/UI/CrowdNode/Portal/Model/CrowdNodeTransferModel.swift
  • DashWallet/Sources/UI/CrowdNode/Portal/StakingInfoDialogController.swift
  • DashWallet/Sources/UI/CrowdNode/Portal/Views/CrowdNodeCell.swift
  • DashWallet/Sources/UI/CrowdNode/Portal/Views/FromLabel.swift
  • DashWallet/Sources/UI/CrowdNode/Portal/Views/KeyboardHeader.swift
  • DashWallet/Sources/UI/CrowdNode/Portal/Views/MinimumDepositBanner.swift
  • DashWallet/Sources/UI/CrowdNode/Portal/WithdrawalLimitsController.swift
  • DashWallet/Sources/UI/CrowdNode/Views/CrowdNodeAPYView.swift
  • DashWallet/Sources/UI/DashPay/Credits/BuyCreditsViewController.swift
  • DashWallet/Sources/UI/Explore Dash/ExploreMenuScreen.swift
  • DashWallet/Sources/UI/Explore Dash/ExploreViewController.swift
  • DashWallet/Sources/UI/Explore Dash/Views/DashSpend/GiftCardDetails/GiftCardDetailsView.swift
  • DashWallet/Sources/UI/Explore Dash/Views/ExploreContentsView.swift
  • DashWallet/Sources/UI/Home/HomeViewController+Shortcuts.swift
  • DashWallet/Sources/UI/Home/HomeViewController.swift
  • DashWallet/Sources/UI/Home/Models/DWHomeModel.m
  • DashWallet/Sources/UI/Home/Protocols/DWHomeProtocol.h
  • DashWallet/Sources/UI/Home/Views/HomeView.swift
  • DashWallet/Sources/UI/Home/Views/HomeViewModel.swift
  • DashWallet/Sources/UI/Home/Views/Shortcuts/Models/ShortcutAction.swift
  • DashWallet/Sources/UI/Home/Views/TransactionListDataItem.swift
  • DashWallet/Sources/UI/Onboarding/Stubs/DWHomeModelStub.m
  • DashWallet/Sources/UI/Payment Controller/Enter Amount/ProvideAmountViewController.swift
  • DashWallet/Sources/UI/Payments/Amount/Model/Send/SendAmountModel.swift
  • DashWallet/Sources/UI/Payments/Amount/SendAmountViewController.swift
  • DashWallet/Sources/UI/SwiftUI Components/Dialogs/ModalDialog.swift
  • DashWallet/Sources/UI/Tx/Details/GroupedTransactionsScreen.swift
  • DashWallet/Sources/UI/Views/Navigation/BaseNavigationController.swift
  • DashWallet/ar.lproj/Localizable.strings
  • DashWallet/bg.lproj/Localizable.strings
  • DashWallet/ca.lproj/Localizable.strings
  • DashWallet/cs.lproj/Localizable.strings
  • DashWallet/da.lproj/Localizable.strings
  • DashWallet/dashwallet-Bridging-Header.h
  • DashWallet/de.lproj/Localizable.strings
  • DashWallet/el.lproj/Localizable.strings
  • DashWallet/en.lproj/Localizable.strings
  • DashWallet/eo.lproj/Localizable.strings
  • DashWallet/es.lproj/Localizable.strings
  • DashWallet/et.lproj/Localizable.strings
  • DashWallet/fa.lproj/Localizable.strings
  • DashWallet/fi.lproj/Localizable.strings
  • DashWallet/fil.lproj/Localizable.strings
  • DashWallet/fr.lproj/Localizable.strings
  • DashWallet/hr.lproj/Localizable.strings
  • DashWallet/hu.lproj/Localizable.strings
  • DashWallet/id.lproj/Localizable.strings
  • DashWallet/it.lproj/Localizable.strings
  • DashWallet/ja.lproj/Localizable.strings
  • DashWallet/ko.lproj/Localizable.strings
  • DashWallet/mk.lproj/Localizable.strings
  • DashWallet/ms.lproj/Localizable.strings
  • DashWallet/nb.lproj/Localizable.strings
  • DashWallet/nl.lproj/Localizable.strings
  • DashWallet/pl.lproj/Localizable.strings
  • DashWallet/pt.lproj/Localizable.strings
  • DashWallet/ro.lproj/Localizable.strings
  • DashWallet/ru.lproj/Localizable.strings
  • DashWallet/sk.lproj/Localizable.strings
  • DashWallet/sl.lproj/Localizable.strings
  • DashWallet/sl_SI.lproj/Localizable.strings
  • DashWallet/sq.lproj/Localizable.strings
  • DashWallet/sr.lproj/Localizable.strings
  • DashWallet/sv.lproj/Localizable.strings
  • DashWallet/th.lproj/Localizable.strings
  • DashWallet/tr.lproj/Localizable.strings
  • DashWallet/uk.lproj/Localizable.strings
  • DashWallet/vi.lproj/Localizable.strings
  • DashWallet/zh-Hans.lproj/Localizable.strings
  • DashWallet/zh-Hant-TW.lproj/Localizable.strings
  • DashWallet/zh.lproj/Localizable.strings
  • DashWallet/zh_TW.lproj/Localizable.strings
  • DashWalletTests/AssetLockInputPolicyTests.swift
  • DashWalletTests/CoinbaseDepositFailedErrorTests.swift
  • DashWalletTests/CoinbaseTransferAmountTests.swift
  • DashWalletTests/CrowdNodeOwnershipTests.swift
  • DashWalletTests/MaxSendFeeReserveTests.swift
  • DashWalletTests/Notifications/CrowdNodeNotificationProducerTests.swift
  • DashWalletTests/Notifications/NotificationDispatcherTests.swift
  • DashWalletTests/Notifications/NotificationLifecycleTests.swift
  • DashWalletTests/Notifications/NotificationTopicTests.swift
  • DashWalletTests/Notifications/NotifiedEventStoreTests.swift
  • DashWalletTests/Notifications/TransactionNotificationProducerTests.swift
  • DashWalletTests/RetiredCrowdNodeDefaultsTests.swift
  • DashWalletTests/ShortcutActionTypeTests.swift
  • DashWalletTests/TransactionDirectionTests.swift
  • DashWalletTests/WiperRemovesCrowdNodeKeysTests.swift
  • docs/adr/0006-remove-crowdnode-entirely-as-android-does.md
  • docs/adr/0007-pin-shortcut-raw-values-and-refill-the-retired-slot.md
  • docs/adr/0008-remove-crowdnode-defaults-by-exact-key-list-on-activation.md
  • docs/adr/0009-retire-the-crowdnode-topic-by-marking-events-seen.md
  • docs/adr/README.md
  • scripts/a11y_baseline.json
  • scripts/core_drain_recovery_harness.py

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

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@thepastaclaw

thepastaclaw commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

✅ Final review complete — no blockers (commit 2047727) · triage: critical

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

Final validation — Phase 1 + Phase 2

The CrowdNode removal is internally consistent and the supplied exact-head CI checks are successful. One blocking lifecycle gap remains: activation removes delivered legacy notifications but leaves older pending CrowdNode requests scheduled by the previous one-second notification path, allowing a retired message to appear after activation.

🔴 1 blocking

1 finding(s) not shown inline (GitHub refused the PR diff as too large)

🔴 Blocking: Activation does not cancel pending legacy CrowdNode notifications
DashWallet/Sources/Infrastructure/Notifications/NotificationLifecycle.swift:142-151

clearTray only queries delivered notifications and calls removeDeliveredNotifications. Older builds scheduled the legacy CrowdNode request with a one-second trigger, so reopening or upgrading the app before that trigger fires leaves the request pending; it can then deliver the retired CrowdNode message after activation. The existing UserNotificationCenterClient already exposes removePendingNotificationRequests, and the activation reconciliation should cancel the known legacy identifiers (CrowNode/Now) in addition to removing delivered requests. Add regression coverage for a pending legacy request.

source: gpt-6.1-sol (phase2-reviewer: general)

Review provenance

Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: ffi-engineer); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: critical by gpt-6.1-sol (effort low) — This is a large, intricate cross-cutting change that modifies wallet send and transaction-building paths in SwiftDashSDKTransactionSender and WalletSendService, including coin-selection-related behavior and fee handling, which affects funds movement.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity quota lookup failed), glm-5.3-flash (not used above high effort; tier asks max)
  • Single stage: Phase 1 and Phase 2 reviewed this head side by side, with no blocker gate between them (triage tier)
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — ffi-engineer (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort xhigh); agent phase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `DashWallet/Sources/Infrastructure/Notifications/NotificationLifecycle.swift`:
- [BLOCKING] DashWallet/Sources/Infrastructure/Notifications/NotificationLifecycle.swift:142-151: Activation does not cancel pending legacy CrowdNode notifications
  `clearTray` only queries delivered notifications and calls `removeDeliveredNotifications`. Older builds scheduled the legacy `CrowdNode` request with a one-second trigger, so reopening or upgrading the app before that trigger fires leaves the request pending; it can then deliver the retired CrowdNode message after activation. The existing `UserNotificationCenterClient` already exposes `removePendingNotificationRequests`, and the activation reconciliation should cancel the known legacy identifiers (`CrowNode`/`Now`) in addition to removing delivered requests. Add regression coverage for a pending legacy request.

Older builds scheduled the CrowdNode deposit and transaction
notifications under fixed identifiers with a one-second trigger. One
still pending at activation would land after the tray was cleared, so
activation now cancels pending requests for those identifiers too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@romchornyi

romchornyi commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor Author

Re the blocking finding (pending legacy CrowdNode notifications): confirmed and fixed in 731df5c.

Before eae2f05, BalanceNotifier scheduled the CrowdNode deposit notification (id CrowdNode) and transaction notifications (id Now) with a one-second UNTimeIntervalNotificationTrigger. A request still pending at activation would land after the tray was cleared. reconcileAfterBecomingActive now also calls removePendingNotificationRequests for those identifiers (NotificationLifecycle.swift). The call runs on the same serial queue as the tray clear. Regression test: NotificationLifecycleTests.testReconcileCancelsPendingLegacyRequests.

🤖 Generated with Claude Code

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

Re-review — Final validation — Phase 1 + Phase 2

The CrowdNode removal is internally consistent, and the exact-head CI snapshot reports successful simulator tests, accessibility, target-membership, and recovery checks. One security-relevant cleanup gap remains: wallet deletion can report success before retired CrowdNode UserDefaults and asynchronous WebKit data cleanup are guaranteed to be complete.

🟡 1 suggestion(s)

1 finding(s) not shown inline (GitHub refused the PR diff as too large)

🟡 Suggestion: Successful wallet wipe leaves retired CrowdNode data behind
DashWallet/Sources/Infrastructure/SwiftDashSDK/SwiftDashSDKWalletWiper.swift:318-329

The wipe commit clears the SDK and current app-owned stores, but it no longer clears the retired CrowdNode defaults at the deletion boundary. RetiredCrowdNodeDefaults.removeAllOnce() is only invoked from AppDelegate.handleDidBecomeActive, and its WebKit cookie/website-data removal is asynchronous and not awaited by either full wipe or per-wallet deletion. If a wallet is removed before activation cleanup runs or while the WebKit purge is still pending, the wiper can report success while CrowdNode account addresses, balances, state, and web-session data remain in app storage. Cleanup of retired sensitive data should be part of the fail-closed wipe/delete boundary, or the deletion flow must explicitly await and verify it before reporting success.

source: gpt-6.1-sol (phase2-reviewer: security-auditor)

Review provenance

Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: ffi-engineer); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 6: gpt-6.1-sol (agent: phase2-reviewer, role: ffi-engineer); reviewer 7: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: critical by gpt-6.1-sol (effort low) — This is a large cross-cutting change that modifies wallet transaction construction and signing paths in SwiftDashSDKTransactionSender and WalletSendService, directly affecting funds movement and coin selection.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity quota lookup failed), glm-5.3-flash (not used above high effort; tier asks max)
  • Single stage: Phase 1 and Phase 2 reviewed this head side by side, with no blocker gate between them (triage tier)
  • Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — ffi-engineer (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — general (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — ffi-engineer (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort xhigh); agent phase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `DashWallet/Sources/Infrastructure/SwiftDashSDK/SwiftDashSDKWalletWiper.swift`:
- [SUGGESTION] DashWallet/Sources/Infrastructure/SwiftDashSDK/SwiftDashSDKWalletWiper.swift:318-329: Successful wallet wipe leaves retired CrowdNode data behind
  The wipe commit clears the SDK and current app-owned stores, but it no longer clears the retired CrowdNode defaults at the deletion boundary. `RetiredCrowdNodeDefaults.removeAllOnce()` is only invoked from `AppDelegate.handleDidBecomeActive`, and its WebKit cookie/website-data removal is asynchronous and not awaited by either full wipe or per-wallet deletion. If a wallet is removed before activation cleanup runs or while the WebKit purge is still pending, the wiper can report success while CrowdNode account addresses, balances, state, and web-session data remain in app storage. Cleanup of retired sensitive data should be part of the fail-closed wipe/delete boundary, or the deletion flow must explicitly await and verify it before reporting success.

The CrowdNode keys were removed only by the activation cleanup. The
full wipe now removes all of them at its commit point, and per-wallet
deletion removes that wallet's keys, as the old CrowdNode reset did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@romchornyi

Copy link
Copy Markdown
Contributor Author

Re the suggestion (wallet wipe leaves retired CrowdNode data behind): fixed in 0751b6e.

The UserDefaults part now sits at the deletion boundary again, as the old CrowdNode.resetForWipe / clearPerWalletKeys calls did:

  • full wipe: RetiredCrowdNodeDefaults.removeAll at the commit point, next to the other UserDefaults stores (SwiftDashSDKWalletWiper.swift#L334);
  • per-wallet deletion: removePerWalletKeys(forWalletIdHex:) for the removed wallet (#L1002), with a test that other wallets' keys stay.

The crowdnode.io cookies and website data are not added to the wipe: the old wipe never cleared them, and removing them is asynchronous WebKit work that the wipe's background queue shouldn't wait on. The activation cleanup removes them and keeps retrying until WebKit reports completion.

🤖 Generated with Claude Code

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

Re-review — Final validation — Phase 1 + Phase 2

Reviewed the complete base-to-head diff at 0751b6e and found no actionable in-scope defects. Both prior findings are fixed: wallet deletion clears retired CrowdNode defaults independently of activation, and activation cancels pending legacy notifications. Validation was static-only; current GitHub checks report passing simulator tests, test-target membership, recovery, and accessibility checks.

🔴 0 blocking | 🟡 0 suggestion(s) | 💬 0 nitpick(s)

Review provenance

Source: reviewer 1: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 2: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); reviewer 4: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 5: gpt-6.1-sol (agent: phase2-reviewer, role: security-auditor); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: normal by gpt-6.1-sol (effort low) — This is a large, cross-cutting feature-removal change spanning UI, notifications, persisted settings cleanup, transaction handling, and project configuration, but the diff does not itself change a critical surface such as funds movement, cryptography, key handling, or storage migrations.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity quota lookup failed), glm-5.3-flash (not used above high effort; tier asks max)
  • Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — general (completed, effort high); agent phase2-reviewer, gpt-6.1-sol — security-auditor (completed, effort high); agent phase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify the current code and confirm that no unresolved issues remain.

No unresolved findings remain from the prior review on this head.

The retired CrowdNode clean-up now removes only CrowdNode's UserDefaults.
The cookies and website data its web screens left stay, as in Android's
CrowdNode removal (dash-wallet #1664); nothing in the app opens the site.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SDD-Published-From: 5efeecdfc70c22c6ffec09ebf457cbf46aabd3f8

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

Re-review — Final validation — Phase 1 + Phase 2

At af201e8, the CrowdNode removal is consistent across UI entry points, sending, transaction observation, notifications, persistence cleanup, and resources; both prior findings are fixed. A static merge check confirms eight conflicting paths against develop, which has advanced 13 commits; resolving those conflicts is the sole remaining suggestion. This review was static-only: current checks show a passing title check and a skipped CodeRabbit review, but no build or XCTest results independently corroborating the reported local validation.

🟡 1 suggestion(s)

1 finding(s) not shown inline (GitHub refused the PR diff as too large)

🟡 Suggestion: Resolve conflicts with current develop before landing
DashWallet.xcodeproj/project.pbxproj:1-5

A static merge of this exact head with develop at 3201192 produces eight conflicting paths, including the project, SDK host and sender, HomeView, and four modify/delete conflicts involving retired CrowdNode code and tests. Integrate the updated base while preserving the CrowdNode deletions and the newer wallet-key and sender changes. Also preserve #1286's grouped-transaction navigation update at the relocated GroupedTransactionsScreen path. Revalidate the resulting integration with the dashpay build and unit-test target; validation reported for this head does not cover the conflict resolution.

source: muse-spark-1.3-contributor (phase1-reviewer: general)

Review provenance

Source: reviewer 1: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: critical by gpt-6.1-sol (effort low) — This large, cross-cutting removal directly deletes coin-selection, signing, and broadcast logic in SwiftDashSDKTransactionSender.buildAndSignFromAddress and adds persistent-data migration behavior in NotifiedEventStore and RetiredCrowdNodeDefaults.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity quota lookup failed), glm-5.3-flash (not used above high effort; tier asks max)
  • Single stage: Phase 1 and Phase 2 reviewed this head side by side, with no blocker gate between them (triage tier)
  • Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — general (completed, effort xhigh); agent phase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `DashWallet.xcodeproj/project.pbxproj`:
- [SUGGESTION] DashWallet.xcodeproj/project.pbxproj:1-5: Resolve conflicts with current develop before landing
  A static merge of this exact head with develop at 3201192bafb4851f1692c10e6476d68d5027ce3a produces eight conflicting paths, including the project, SDK host and sender, HomeView, and four modify/delete conflicts involving retired CrowdNode code and tests. Integrate the updated base while preserving the CrowdNode deletions and the newer wallet-key and sender changes. Also preserve #1286's grouped-transaction navigation update at the relocated GroupedTransactionsScreen path. Revalidate the resulting integration with the dashpay build and unit-test target; validation reported for this head does not cover the conflict resolution.

…oinbase error and wipe tests

SDD-Published-From: df35aed12eb5c1e43de381ce44c14a5a472da345
SDD-Published-From: 5e969b9c1834a2db127bb7b7eb54eb787ef6ec37
SDD-Published-From: 6e04ea97046e8e7ce4acce2b2bdc1ff4502cd6c8

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

Re-review — Final validation — Phase 1 + Phase 2

Static verification of the complete diff at 2047727 found no actionable in-scope defects, and all three prior findings are fixed. CrowdNode retirement preserves surviving transaction observers, fee calculations, shortcut values, and wallet-deletion ordering. Current CI passes target membership, recovery, and accessibility checks; simulator tests remain pending, and no local builds or tests were run.

🔴 0 blocking | 🟡 0 suggestion(s) | 💬 0 nitpick(s)

Review provenance

Source: reviewer 1: gpt-6.1-sol (agent: phase2-reviewer, role: general); reviewer 2: muse-spark-1.3-contributor (agent: phase1-reviewer, role: general); reviewer 3: gpt-6.1-sol (agent: phase2-reviewer, role: general); final verifier: gpt-6.1-sol (agent: sol-verifier, role: final-verifier)

  • Triage: critical by gpt-6.1-sol (effort low) — This large, cross-cutting removal directly deletes the selected-input funds-movement path in SwiftDashSDKTransactionSender.buildAndSignFromAddress and introduces the MarkRetiredNotificationTopicsSeen storage migration in NotifiedEventStore.swift.
  • Phase 1 reviewers: muse-spark-1.3-contributor — general (completed, effort xhigh); agent phase1-reviewer
  • Phase 1 model: muse-spark-1.3-contributor — not quota-gated; passed over gemini-3.8-flash-high (antigravity quota lookup failed), glm-5.3-flash (not used above high effort; tier asks max)
  • Single stage: Phase 1 and Phase 2 reviewed this head side by side, with no blocker gate between them (triage tier)
  • Fresh final gate: an independent Phase-2 review ran after iterative findings were reconciled
  • Fresh verifier: gpt-6.1-sol — final-verifier; agent sol-verifier
  • Phase 2 reviewers: gpt-6.1-sol — general (completed, effort xhigh); agent phase2-reviewer, gpt-6.1-sol — general (completed, effort xhigh); agent phase2-reviewer
🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify the current code and confirm that no unresolved issues remain.

No unresolved findings remain from the prior review on this head.

This branch has not been deployed

No deployments
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