Skip to content

Bump bls-signatures-pod to 0.2.4 - #5

Merged
QuantumExplorer merged 1 commit into
developfrom
feature/bump-bls-version
Nov 17, 2018
Merged

Bump bls-signatures-pod to 0.2.4#5
QuantumExplorer merged 1 commit into
developfrom
feature/bump-bls-version

Conversation

@podkovyrin

Copy link
Copy Markdown
Contributor

@QuantumExplorer
QuantumExplorer merged commit 214ee87 into develop Nov 17, 2018
@podkovyrin
podkovyrin deleted the feature/bump-bls-version branch November 25, 2018 20:55
llbartekll added a commit that referenced this pull request Apr 9, 2026
M6 stopped DashSync's SPV but left BalanceModel reading from
DSWallet.balance, which is now frozen at whatever DashSync had cached
the moment M6 ran. This commit unfreezes the home screen balance by
sourcing it from SwiftDashSDK via a new SwiftDashSDKWalletState
singleton.

A new SwiftDashSDKWalletState class is the right home for wallet-side
@published state: SPV chain sync (handled by SwiftDashSDKSPVCoordinator)
and wallet state (balance, transactions, addresses) are different
concerns even though the FFI couples their event delivery. Future
follow-ups for transactions (#6), addresses (#1), and identities (#16)
will live alongside `balance` in this class instead of bloating the
SPV coordinator.

- Add SwiftDashSDKWalletState singleton with @published var balance,
  applyBalance(_:), seedInitialBalance(walletManager:walletId:), and
  clearBalance() methods. Holds the WalletBalance struct (4 UInt64
  fields, with `total` and `spendable` computed). 💰 WALLET :: log tag.
- SwiftDashSDKSPVCoordinator: WalletEventsHandler.onBalanceUpdated
  becomes a thin forwarder to SwiftDashSDKWalletState.shared.applyBalance.
  performStart calls SwiftDashSDKWalletState.shared.seedInitialBalance
  after walletManager.importWallet succeeds. Coordinator no longer
  owns any wallet-side @published state.
- BalanceModel subscribes to SwiftDashSDKWalletState.shared.\$balance
  via Combine and reads from .balance?.total instead of
  DWEnvironment.sharedInstance().currentWallet.balance.
- SwiftDashSDKWalletWiper calls SwiftDashSDKWalletState.shared.clearBalance()
  after the SwiftData wipe so post-wipe state doesn't show the
  previous wallet's balance.
- Register the new file in both dashwallet and dashpay targets in
  project.pbxproj (UUID family A5D5DD000000000000010C/D/E).

Other DashSync balance consumers (BalanceNotifier, SendAmountModel,
DWPhoneWCSessionManager, DashPay/CrowdNode/CoinJoin/DashSpendPay) still
read from DSWallet and remain stale. Each gets its own follow-up commit.
Aim of this commit is the smallest change that fixes the most visible
part of the M6 regression — the home screen number — with the right
architectural shape so #6 and beyond can build on it cleanly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
llbartekll added a commit that referenced this pull request Apr 9, 2026
…tate

Function #5 follow-up. Commit 2b447fc unfroze the home screen
balance by sourcing BalanceModel from SwiftDashSDKWalletState. This
commit extends the same fix to the four other Swift consumers that
were still reading from DashSync's frozen DSWallet.balance:

- BalanceNotifier — the "you got coins" local notification trigger.
  Replaces the DSWalletBalanceChangedNotification observer with a
  Combine subscription on SwiftDashSDKWalletState.shared.\$balance.
  Adds `import Combine` and a `cancellableBag`. The delta-tracking
  logic for "received" notifications is unchanged — only the source
  of `wallet.balance` shifts.
- BaseAmountModel — the Send-screen amount entry. The existing
  Combine pipeline already had the right shape; just repoint the
  source publisher and the read in `refreshBalance`.
- DashSpendPayViewModel — same pattern, gift card pay flow.
- CreateUsernameViewModel — DashPay username validation. Two reads
  (currentWallet.balance and currentAccount.balance, semantically
  equal in single-account dashwallet) both become `.balance?.total`.
  The Combine pipeline gets the same source swap.

Out of scope for this commit:
- 5 Obj-C DSWalletBalanceDidChange observers (DWHomeModel,
  DWLocalCurrencyVC, DWRequestAmountVC, DWPhoneWCSessionManager,
  DWHomeModelStub) — Obj-C can't subscribe to @published directly.
- 3 Obj-C wallet.balance reads (DWNotificationsVC, DWHomeModel,
  DWRecoverModel).
- Indirect balance consumers (CrowdNode, CoinJoin, Coinbase) — they
  read different APIs (account.maxOutputAmount, coinJoinBalance, etc.).

These each get their own follow-up commits.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
llbartekll added a commit that referenced this pull request Apr 9, 2026
Function #5 follow-up. Adds an @objc notification + accessor on
SwiftDashSDKWalletState so the remaining Obj-C consumers can
participate in the live balance update path. Closes out the
DSWallet.balance usage in dashwallet's Obj-C surface (except for
indirect-balance APIs and DWRecoverModel which is already broken
for unrelated reasons).

The bridge:
- Add @objc balanceDidChangeNotification (NSNotification.Name)
- Add @objc static currentTotalBalance: UInt64
- applyBalance and clearBalance now post the notification on the
  main queue after the @published mutation runs

Obj-C call sites swapped:
- DWHomeModel: observer (line 113) + canRegisterUsername read (271)
- DWLocalCurrencyVC: observer (line 222) — empty handler left as-is
- DWRequestAmountVC: observer (line 56) — receive-amount listener
- DWPhoneWCSessionManager: observer (line 70) + sync-state gate
  switched from currentChainManager.combinedSyncProgress (DashSync,
  frozen) to SyncingActivityMonitor.shared.state == SyncDone. Fixes
  the known Apple Watch update regression.
- DWHomeModelStub: observer (line 63) — onboarding stub
- DWNotificationsVC: invitationMessageHidden read (line 77)

Out of scope for this commit:
- DWRecoverModel.isWalletEmpty (line 59) — also reads chain state
  which is frozen post-M6. Belongs to a future chain-state commit.
- CrowdNode / CoinJoin / Coinbase balance — different APIs entirely
  (account.maxOutputAmount, coinJoinBalance, exchange-side).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
llbartekll added a commit that referenced this pull request Apr 9, 2026
Brings DASHSYNC_MIGRATION.md in line with what's actually shipped:

- Add "Where we are" entries for #5 wallet balance (commits
  2b447fc, f1b481b, 7c00be4) and #11 SPV chain sync via
  M5 + M6 (3cf5962 + 86ed727).
- Update #14 wipe entry to mention the post-#5 SPV stop +
  clearBalance calls.
- Flip Status column for rows #5 and #11 from `—` to `🌗 Flipped`.
- Update file paths and storage notes in rows #5/#6/#7/#11 to
  reflect the actual code locations and migration story.
- Drop the Core Data → SwiftData migrator from Hard Blockers.
  After the #5 work landed, the migrator turned out unnecessary:
  chain-derived data (UTXOs, tx history, masternode list, sync
  state) is re-derivable via SPV resync from SwiftDashSDK's own
  on-disk chain data. User-entered metadata (tx categories, tax
  categories, gift card receipts, address labels) was never in
  DashSync's Core Data — it lives in dashwallet's own SQLite via
  TransactionMetadataDAOImpl and AddressUserInfo, keyed by txHash
  / address, so it stays attached after resync automatically.
- Rewrite the "Storage migration" section with the corrected
  picture (no migrator required).
- Rework the "Recommended order" wave structure: the chain +
  balance push (Wave 2 now) ran ahead of DashPay/Platform work
  because the storage groundwork was unblocked. Tx history (Wave
  4 now) is the next big wave and follows the same shape as #5.

No code changes — doc only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
llbartekll added a commit that referenced this pull request Apr 10, 2026
M6 stopped DashSync's chain sync, which froze the home screen
transaction list at whatever was cached at the moment of the cutover.
This commit unfreezes it by sourcing the tx list purely from
SwiftDashSDK, following the same pattern as function #5 wallet balance.

Pure SwiftDashSDK source — DashSync dropped entirely for the tx list
path. On cold launch, the list starts empty and fills progressively as
SPV replays blocks from cached chain data. After a full sync, all
historical transactions are visible. The tx detail screen opens for all
txs but shows reduced info because WalletTransaction only has basic
fields (txid, netAmount, height, timestamp, fee). Input/output
addresses, instant-send flags, and account-validity state are not
available from the SDK yet — those sections are hidden/empty.

- Add SwiftDashSDKWalletState.transactions @published,
  applyTransactions, seedTransactions, clearTransactions. Same pattern
  as the balance plumbing. Includes @objc bridge notification.
- SwiftDashSDKSPVCoordinator wires onTransactionReceived to re-fetch
  and forward via DispatchQueue.global to avoid re-entering the FFI
  from within the callback (the SPV client holds a Rust lock during
  callback dispatch; calling getWalletManager synchronously causes a
  Rust panic). performStart calls seedTransactions after wallet import.
- Transaction.swift becomes a sum type backed by either DSTransaction
  (existing rich path for other consumers) or WalletTransaction (new
  SDK path for the home screen). var isMinimal: Bool flag indicates
  the SDK path. Properties exclusive to DSTransaction return
  empty/default values for the .sdk case.
- HomeViewModel switches TransactionSource to SwiftDashSDKWalletSource
  reading purely from SwiftDashSDKWalletState.shared.transactions.
  Subscribes to \$transactions via Combine. CrowdNode/CoinJoin grouping
  disabled for SDK-sourced txs (matchers need DSTransaction).
- TxDetailModel gracefully handles optional transaction.tx via
  optional chaining — shows available fields, hides unavailable.
- SwiftDashSDKWalletWiper clears transactions alongside balance.
- StubTransactionSource and Taxes.swift adapted to new Transaction
  sum type (optional DSTransaction? accessor).

Out of scope: rich tx detail (waiting on SDK enrichment), 7 other
wallet.allTransactions consumers, CrowdNode/CoinJoin grouping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
llbartekll added a commit that referenced this pull request Jun 29, 2026
…helper

`DSAccount.maxOutputAmount` (fee-aware max single-tx output) had no
SwiftDashSDK equivalent — the last #5 balance read blocking BuyCredits,
DashSpend, and CrowdNode. Add a `maxSendable` computed on `WalletBalance`
(spendable − a conservative fee reserve, floored at 0; mirrors the shielded
`creditsMinusFeeReserve` pattern) and repoint the call sites.

Split by send semantics:
- buy-exactly-X (fee reserved on top): BuyCredits Select-All + DashSpend
  insufficient-funds gate use `balance?.maxSendable`.
- send-what's-left (the send path carves the fee via adjustAmountDownwards):
  CrowdNode canWithdraw gate uses plain `.spendable` (fee immaterial vs the
  30k leftover threshold, matching SendAmountModel); CrowdNode deposit caps
  keep the fee-aware value via `maxSendable`.

The reserve is a stopgap constant (100_000 duffs ≈ 0.001 DASH) — Core has no
pre-build fee-estimate FFI yet; the send path settles the exact fee.

Not build-verified in this session (../platform on feat/dashpay-m1-sync-
correctness lacks the CoinJoin SDK APIs to link the app); static checks pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
llbartekll added a commit that referenced this pull request Jul 2, 2026
SwiftDashSDKReceiveAddressReader ignored its DSChain argument (it resolves the
wallet via SwiftDashSDKHost.shared), so every call site fetched
DWEnvironment.currentChain / account.wallet.chain purely to feed a discarded
value. Drop the parameter: receiveAddress(on:) -> receiveAddress(),
@objc(receiveAddressOnChain:) -> plain @objc (auto selector `receiveAddress`).

- 10 Swift sites + the BIP70 provider drop the arg; 8 shed a now-dead
  `let chain` binding.
- 5 ObjC sites become a selector swap; DWURLRequestHandler sheds a dead
  `account` local. Sites that still need chain/account for DSPaymentRequest /
  Apple Watch balance keep them (those belong to #22 / #5-#6).
- The reader file is now completely DashSync-free (zero DS* symbols); kept as a
  permanent SDK-only helper (same shape as #2 / #21).

Flips migration item #1 (receive address) from Solo to Done. dashpay scheme
builds clean on iPhone 17 sim (arm64).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
llbartekll added a commit that referenced this pull request Jul 2, 2026
CrowdNodeModel.checkBalance read DWEnvironment.currentAccount.balance — the
DashSync account, frozen post-M6, so it reported ~0 regardless of the real
wallet balance. That made the "New CrowdNode Account" gate show
"You need at least DASH 0.01" even for a funded wallet.

Read SwiftDashSDKWalletState.shared.balance?.total instead (total =
confirmed+unconfirmed+immature ≈ legacy DSAccount.balance), and drive the
refresh off SwiftDashSDKWalletState.$balance instead of the
DSWalletBalanceDidChange notification (which no longer fires with DashSync sync
stopped). Consumers (canSignUp, minimumRequiredDash, minimumLeftoverBalance)
are unchanged.

Closes one of the two #5 balance tails (Apple Watch payload remains).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
llbartekll added a commit that referenced this pull request Jul 6, 2026
SyncingActivityMonitor.postLegacyNotifications re-posted the three
DSChainManagerSync* names from SwiftDashSDK-sourced state, so observers
and grep audits saw "DashSync sync running" when it wasn't (the
architecture-guardrail-#5 violation recorded in the row #11 audit).

The re-emitter, its dedup state, and the three legacy name constants are
deleted; the three consumers move to the typed surface:

- HomeViewModel reloads txs/shortcuts from its existing syncModel.$state
  sink on the transition into .syncing (removeDuplicates keeps it one
  reload per transition; a model created mid-sync does one extra reload).
- DWPhoneWCSessionManager adopts SyncingActivityMonitorObserver and sends
  the watch application context on .syncDone/.syncFailed. The monitor's
  strong observers array is safe here — the class is a process-lifetime
  singleton.
- DWAboutViewController observes the new ObjC-visible
  SyncingActivityMonitor.syncStateChangedNotificationName (NC observation
  rather than protocol adoption — the strong observers array would retain
  a pushed VC forever). Its old name, DSChainManagerSyncStateDidChange,
  was never re-emitted by the monitor at all.

grep DSChainManagerSync is a clean audit signal again: any remaining hit
is a genuine residual DashSync poster (e.g. the Settings-rescan hole,
removed separately).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
llbartekll added a commit that referenced this pull request Jul 6, 2026
The watch application-context payload read the frozen post-M6 DashSync
account.balance (always zero/stale). Both display strings now read the
@objc DWSwiftDashSDKWalletState.currentTotalBalance accessor — closing
row #5's last tail. The account local stays for recentTransactions and
the tx-status mapping, which travel with the watch keep-vs-remove
decision (teardown D1); the watch app currently doesn't ship from this
branch (embed phase removed in 45da5fd).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
QuantumExplorer added a commit that referenced this pull request Jul 9, 2026
… a Wallets screen (#790)

* feat(wallet): active-wallet registry + walletId-scoped data reads (switch-wallet phase 0)

The app assumed a single SDK wallet and arbitrarily bound
`PlatformWalletManager.firstWallet`, while every wallet-data read scanned
all persisted rows across all wallets. Phase 0 makes the app honest about
which wallet is active and scopes reads to it — no UI, no behavior change
for existing single-wallet installs.

Active-wallet registry: `WalletEnvironment` gains per-network
`activeWalletId(for:)` / `setActiveWalletId(_:for:)` backed by a
per-network UserDefaults key holding the raw walletId `Data` (nil = unset),
following the file's existing stateless-static-namespace pattern (no new
singleton). `SwiftDashSDKHost` resolves the persisted active walletId among
the manager's loaded `wallets`, falling back to `firstWallet` when unset or
missing, and writes the resolved id back on every bind (fallback included,
and after `createOrImportWallet`) so the registry is concrete after first
launch. For a single-wallet install `firstWallet` == the only wallet, so
the resolved wallet is unchanged.

Scoped reads: `PersistentTransaction` deliberately carries no walletId (one
row is shared across wallets), so per-wallet membership is recovered by
joining through `PersistentTxo.walletId` — gather the active wallet's txids
from its walletId-scoped TXO rows (union of the producing `transaction` and
the spending `spendingTransaction`) and match transactions in that set.
Scoped sites: the home tx list and point-lookup (HomeViewModel), the
receive-address used/received-total reads (SwiftDashSDKReceiveAddressReader),
ZenLedger address export, and the CrowdNode TransactionObserver scan. The
TXO join is one walletId-scoped fetch per reload (not per transaction), so
the home timeline stays a single indexed scan on large wallets. Readers
that run before the host binds a wallet return empty, which is correct.

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

* feat(wallet): runtime wallet switching + activeWalletDidChange (switch-wallet phase 1)

Phase 0 made the app honest about which wallet is active (per-network
registry + walletId-scoped reads) but left no way to change the active
wallet at runtime. Phase 1 adds that engine. No UI (Phase 2 owns UI).

Reuse the network-switch sequence, don't duplicate it. `switchWallet(to:)`
on SwiftDashSDKWalletRuntime validates the target, repoints the per-network
active-wallet registry, then runs the exact stop -> clear -> load -> start
sequence `refresh` already owns for a network switch — the only difference
is the network is unchanged, so the host's Phase-0 `resolveActiveWallet`
binds the newly-recorded wallet when it rebuilds. Added an awaitable variant
of the serial-chain `enqueue` so the switch can block until its rebuild (and
every op queued before it) completes, then verify the bind and post the
change signal. A new `.walletDidChange` refresh trigger never elides the
rebuild (same network would otherwise skip it). Validation is synchronous on
the main actor before any teardown; failures surface as `SwitchError`
(unsupportedNetwork / unknownWallet / bindFailed) rather than
logged-and-swallowed. Switching to the already-active wallet is a no-op.

Typed change signal: `SwiftDashSDKWalletState.activeWalletDidChangeNotification`
(new name, not a re-emission of any DS* name nor of balanceDidChange —
guardrail #5), posted on the main thread after the new wallet is bound and
its balance seeded.

Consumer audit (wired vs. already-covered):
- DWCurrentUserIdentityInfo: WIRED — added the notification to its lazy-
  invalidation observers so the next read rebuilds from the new wallet's
  identity rows.
- SwiftDashSDKContactsService: WIRED — ownerId changes with the wallet;
  observes and refreshes. Forces the identity snapshot fresh first because
  NotificationCenter delivery order between the two observers isn't
  guaranteed.
- HomeViewModel: WIRED — treats the switch like a network change
  (clearCachedData) because the per-hash tx cache belongs to the old wallet
  and a same-total balance event wouldn't clear it.
- MainTabbarController: WIRED via a new unconditional rebuild. The existing
  reconfigureDashPayTabsIfNeeded only ADDS tabs (early-returns without an
  identity), so switching to a wallet without an identity needs a rebuild
  that also REMOVES the DashPay tabs; selection resets to Home.
- BalanceModel / BalanceNotifier / DWPhoneWCSessionManager: NOT wired —
  they follow SwiftDashSDKWalletState.$balance / balanceDidChange, which the
  switch sequence already drives (clearAllState clear + the SPV re-seed).

Registry hygiene on wipe: SwiftDashSDKWalletWiper clears the active-wallet
registry for both registry-backed networks. The wipe removes all wallets
(mnemonics are network-agnostic), so every recorded active id now points at
nothing; Phase 0's fallback would mask a stale id, but clearing it keeps the
registry honest.

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

* feat(wallet): Wallets screen — switch, rename, remove (switch-wallet phase 2)

Replaces the Security menu's destructive "Reset Wallet" entry with a
"Wallets" screen listing the on-device SwiftDashSDK wallets for the
current network, one marked active, with switch-on-tap, rename, and
per-wallet remove. Add Wallet is Phase 3 and is intentionally absent
(no disabled controls, no stubs).

Menu wiring: `SecurityMenuViewModel`'s "Reset Wallet" item becomes a
"Wallets" item; `SecurityMenuScreen` pushes a `UIHostingController`
wrapping the new SwiftUI screen (matching the sibling push pattern).
`DWResetWalletInfoViewController` is kept — the Wallets screen still
presents it for the last-wallet remove path.

WalletsScreen + WalletsViewModel (SwiftUI + @mainactor VM, SwiftUI-first):
- List source: `SwiftDashSDKHost.shared.manager?.wallets` intersected
  with the walletIds that have a persisted mnemonic
  (`SwiftDashSDKHost.persistedMnemonics()`) — a wallet without a mnemonic
  isn't switchable. Per row: display name (`PersistentWallet.name` else
  "Wallet <prefix>…"), balance (`ManagedPlatformWallet.balance().total`
  via the app's `formattedDashAmount`), DPNS username (read from the
  wallet's pinned `PersistentIdentity` row directly — target-neutral SDK
  model, so no DASHPAY gate), and an active checkmark
  (`WalletEnvironment.activeWalletId(for:)`, network via `isTestnet`).
- Switch: confirmation alert → `SwiftDashSDKWalletRuntime.switchWallet`
  with a blocking progress overlay; refreshes on
  `activeWalletDidChangeNotification`; surfaces `SwitchError` in an alert
  on failure (never claims success on failure).
- Rename: alert with TextField → writes `PersistentWallet.name` via the
  host `modelContainer` mainContext (empty clears to nil).
- Remove: recovery-phrase confirmation sheet spelling out that only this
  device's copy is destroyed (funds recoverable only with the phrase).
  Verified read-only by deriving the walletId from the entered mnemonic
  (`Wallet(mnemonic:network:).id`, which persists nothing) and comparing.
  Removing the active wallet auto-switches to another wallet first; the
  last wallet routes to the existing full reset flow.

Guardrail #1 (no copy-then-adapt): the per-wallet deletion body is
promoted out of `SwiftDashSDKWalletWiper` into an internal
`deleteWalletFromSDK(_:)` (manager `deleteWallet` + mnemonic delete),
reused by both the full wipe loop and the new remove flow.

Both `dashpay` and `dashwallet` targets are registered in project.pbxproj
(this UI is not DashPay-gated). `dashpay` builds green; the new code also
compiles clean under `dashwallet` (its remaining failures are the
pre-existing DASHPAY-only breakage this branch documents).

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

* feat(wallet): add-wallet create/import + per-wallet backup flags (switch-wallet phase 3)

Phase 2 shipped the Wallets screen (switch / rename / remove) but left Add
Wallet for Phase 3. This adds it and makes the two genuinely per-wallet
DWGlobalOptions flags wallet-scoped.

Add Wallet (WalletsScreen + WalletsViewModel):
A "+" in the screen header presents a sheet with two paths.
- Create New Wallet: generate a 12-word mnemonic via SwiftDashSDK's
  `Mnemonic.generate(wordCount: 12)`, show it in a clean SwiftUI phrase grid
  (DWPreviewSeedPhraseModel is too DashSync-entangled to reuse — it dual-writes
  to DSWallet — so a new grid, no new UIKit), and require an explicit "I have
  written it down" toggle before creating.
- Import from Phrase: multiline entry, `Mnemonic.validate` gate. If the derived
  walletId is already on device, a typed `.alreadyOnDevice` state offers
  switching to it instead of fabricating a success.
Both paths add additively then `switchWallet(to:)` the new wallet (awaited,
progress state), dismissing on success. All SDK/mnemonic work lives in the VM.

Additive host path (SwiftDashSDKHost):
`createOrImportWallet` is NOT additive — it rebuilds the runtime (`buildRuntime`
calls `stop()`), pins the new wallet active in the registry, and publishes it as
bound. That is correct for onboarding's first/sole wallet but would unbind the
active wallet when adding a second. New `addWallet(mnemonic:)` uses the LIVE
running manager, persists the mnemonic, and does NOT touch the registry or
publish (the UI switches explicitly). The create-then-persist-with-rollback body
is factored into a shared `createAndPersist` used by both paths (no duplication).
`addWallet` detects the idempotent "already exists" case up front by deriving the
walletId (`Wallet(mnemonic:network:).id`) and checking `persistedMnemonics()`,
returning `.alreadyExists` rather than silently no-opping. Neither path touches
DashSync.

Per-wallet flags (DWGlobalOptions):
`walletNeedsBackup` and `userHasBalance` describe a per-wallet fact but were
app-global. Their getters/setters now resolve `DW_WALLET_NEEDS_BACKUP_<hex>` /
`DW_WALLET_HAS_BALANCE_<hex>` scoped by the active walletId
(`DWWalletEnvironment.activeWalletIdHex`, a new @objc accessor over the same
per-network registry — one place owns it). One-time migration: on first read
with no per-wallet key, seed from the legacy `DW_GLOB_*` key's effective value
(which carries the registered default, e.g. needs-backup = YES), then the legacy
key goes dormant (not deleted) and remains the fallback while no wallet is active
yet (onboarding sets these before a walletId is resolved). The 30+ existing call
sites keep the property names as the active-wallet view, unchanged. `shortcuts`
stays global (a UI preference). New wallets: created → needs-backup TRUE (phrase
shown, not verified — matches onboarding), imported → FALSE (user holds the
phrase — matches recover).

DSDynamicOptions reads/writes UserDefaults live on every accessor (no in-memory
cache), so a wallet switch needs no cache invalidation — the next read simply
resolves the new active wallet's key. B2 has nothing to wire.

dashpay builds green (ARCHS=arm64). Nothing added references DASHPAY-only symbols
un-gated: the per-wallet flags live outside any `#ifdef DASHPAY`, and the UI/host
code is target-neutral SDK surface. No files added, so no pbxproj/plist changes.

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

* feat(wallet): per-wallet CrowdNode + withdrawal state (switch-wallet phase 4)

CrowdNode state describes ONE wallet's CrowdNode account (bound to a
funding address in a specific wallet), but was stored app-global — so a
wallet switch would show wallet A's CrowdNode balance/account under
wallet B (money-display-adjacent wrongness).

Scope the per-wallet keys the same way DWGlobalOptions phase 3 scoped its
per-wallet flags: effective key = `<legacyKey>_<activeWalletIdHex>`,
resolved live per access via a single `resolvedKey(_:)` seam in
CrowdNodeDefaults (and CoinJoinWithdrawalStore), seeded once from the
legacy key on first read, with the legacy key kept dormant as the
no-active-wallet fallback. Per-wallet: account/primary address, online
account state, last known balance, confirmation-dialog / online-info
shown, signed-email message id, pending confirmed-notification, last
withdrawal block, and the CoinJoin withdrawal txid tag set. Kept global:
the CrowdNode info / withdrawal-limits education flags (user-level) and
the withdrawal limits / fee (API service parameters).

CrowdNodeDefaults caches values in in-memory `_`-backed fields, so a
wallet switch also invalidates that cache; CrowdNode.shared caches the
active wallet's published state, so it observes
activeWalletDidChangeNotification (mirroring SwiftDashSDKContactsService)
and reloads from the now-active wallet's defaults without wiping them.

resetForWipe (both stores) now clears EVERY wallet's per-wallet keys by
prefix enumeration plus the dormant legacy keys — the wipe destroys every
wallet. The shared per-wallet deletion primitive deleteWalletFromSDK now
also clears the removed wallet's per-wallet CrowdNode + withdrawal keys,
so the Wallets-screen Remove flow drops them without touching the UI.

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

---------

Co-authored-by: Claude Opus <noreply@anthropic.com>
romchornyi pushed a commit that referenced this pull request Jul 15, 2026
PR #5 (import/final) landed on dashpay/DashUIKit master (046f0875), which carries
everything the release needs — TransactionView.trailingStatusText and the
NumericKeyboardLocaleSupport tests. Point the app at the stable master branch
instead of the import/final feature branch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants