Skip to content

feat(platform-wallet): wallet-level DPNS username marketplace with FFI and Swift wrappers - #4348

Merged
QuantumExplorer merged 14 commits into
v4.2-devfrom
feat/dpns-marketplace-wallet
Aug 9, 2026
Merged

feat(platform-wallet): wallet-level DPNS username marketplace with FFI and Swift wrappers#4348
QuantumExplorer merged 14 commits into
v4.2-devfrom
feat/dpns-marketplace-wallet

Conversation

@QuantumExplorer

@QuantumExplorer QuantumExplorer commented Aug 9, 2026

Copy link
Copy Markdown
Member

What this is

Wallet-level orchestration for the DPNS username marketplace — the durable layer underneath the dashwallet-ios marketplace UI (which currently composes the generic setDocumentPrice / purchaseDocument / transferDocument primitives directly), plus the pieces the app cannot do at all today (per-name trade history, typed trade errors, sale-state persistence). Design record + investigation results: packages/rs-platform-wallet/docs/DPNS_MARKETPLACE.md.

Wallet layer (rs-platform-wallet)

  • dpns_marketplace.rs: search_dpns_names_with_state (cursor-paginated, keeps $id/$price which the SDK's DpnsUsername drops), dpns_name_state, set_dpns_name_price, delist_dpns_name (transfer-to-self — consensus clears $price; the confirmed document is verified), transfer_dpns_name, purchase_dpns_name, dpns_name_history, sync_dpns_marketplace. Signing keys are auto-selected (AUTHENTICATION/ECDSA at the doc type's required security level) — no hardcoded key ids.
  • Typed errors: DpnsNameNotFound, DocumentNotForSale, DocumentPriceChanged{expected,actual}, InsufficientIdentityCredits{required,available}, ContestedNameNotTradable — raised pre-flight and recovered from consensus rejections (40108/40109/IdentityInsufficientBalanceError downcasts, wired into the generic trade paths too). A purchase always broadcasts the user-confirmed price; the consensus equality check is the backstop and a lost race surfaces as typed DocumentPriceChanged.
  • Contested guard: a name in an active vote isn't in the documents tree (trades would fail with a bare 40101); the wallet classifies that miss into ContestedNameNotTradable before broadcast.
  • Persistence: new DpnsNameStateEntry store (changeset + DPNS_NAME_STATES capability bit + SQLite V005 + FFI mirror) rather than mutating the bincode-positional IdentityEntry. The legacy dpns_names label-list merge switches append-only → LWW wholesale (same policy as contested_dpns_names) so sold names can leave an identity; set_dpns_names / remove_dpns_name mutations added.
  • DpnsSyncManager: 60s coordinator (full quiesce/shutdown parity with siblings) that refreshes sale state, detects acquisitions and departures (sold-vs-transferred classified through the history contract), removes departed labels, and refreshes seller balances. Completion event on PlatformEventHandler.
  • History: DPNS v2's keeps*History flags write transfer/purchase/priceUpdate documents into the Document History system contract (byDocument index) — per-name timelines are three indexed queries, merged and $createdAt-ordered. (The getDocumentHistory endpoint serves the GroveDB documentsKeepHistory mechanism, which DPNS does not use — returns empty; documented.)

FFI + swift-sdk

  • Error codes 37–40 with stable JSON detail payloads in the result message, decoded into typed Swift cases (priceChanged(documentId:expected:actual:) etc.).
  • platform_wallet_dpns_* entry points for all ops + DpnsSyncManager start/stop/sync-now; pointer-only repr(C) rows with paired destructors.
  • on_persist_dpns_name_states_fn persister vtable slot (ABI-additive) mirroring rows into PersistentDPNSName, which grows marketplace columns (documentId, price, sale status, counterparty) — meaningful only while documentIdBase58 != nil.
  • ManagedPlatformWallet methods matching the app's seven conceptual marketplace operations one-for-one, so the app swaps over without UI changes.

Browse-for-sale investigation (doc §7)

A global "names for sale ordered by price" needs a $price index, which is not buildable today at any layer: $price is not in rs-dpp's indexable system-property set (contract parse fails with UndefinedIndexPropertyError), index definitions are immutable on DataContractUpdate for all contracts, and the DPNS owner id ([0;32]) is unsignable. Only a protocol-version upgrade can add it (the $creatorId feature-gate precedent; new index trees start empty — no backfill machinery). The exact PV15 path is documented; until then the marketplace is search-driven.

Testnet verification (doc §9)

examples/dpns_marketplace_testnet.rs ran the full flow against real testnet (two identities of one wallet): register → list 1M credits → re-price 2M → typed stale-price rejection → purchase (ownership flip, $price cleared, records.identity rewritten by the protocol) → history shows Registered + PriceSet×2 + Purchased with prices/parties/block heights → typed not-for-sale → delist-via-self-transfer clears $price on-chain. All checks passed. Verification exposed and fixed a real bug: Sdk::register_dpns_name never registers the DPNS contract with the context provider, so hosts that don't pre-seed known contracts failed post-broadcast proof verification — registration now self-heals, and the marketplace contract caches hold the on-chain-fetched, provider-registered contract.

Tests

  • cargo test -p platform-wallet — 752 passed
  • cargo test -p platform-wallet-ffi — 290 passed
  • cargo test -p platform-wallet-storage — new round-trip coverage for the V005 table
  • build_ios.sh --target mac + swift build — clean

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added DPNS marketplace support across wallet APIs, including search, name lookup, pricing, listing, delisting, transfers, purchases, and trade history.
    • Added automatic marketplace synchronization with configurable intervals, status reporting, on-demand sync, and completion events.
    • Added marketplace views and controls to the Swift and Kotlin example apps.
    • Added persistence for ownership, sale status, pricing, transfer details, and marketplace history.
  • Bug Fixes

    • Improved handling of stale names, ownership changes, contested names, unavailable listings, price changes, and insufficient credits with typed errors.

QuantumExplorer and others added 3 commits August 9, 2026 14:20
Wallet-level orchestration for the DPNS marketplace, composing the
existing generic document-trade transitions with the DPNS specifics the
app layer should not own:

- DpnsDomainState queries keeping $id and $price (search by prefix with
  cursor pagination, exact-name state, per-identity states via the
  records.identity index)
- set_dpns_name_price / delist_dpns_name (transfer-to-self, verified to
  clear $price on the confirmed document) / transfer_dpns_name /
  purchase_dpns_name with typed pre-flight checks and automatic
  AUTHENTICATION+ECDSA signing-key selection
- typed errors (DpnsNameNotFound, DocumentNotForSale,
  DocumentPriceChanged, InsufficientIdentityCredits,
  ContestedNameNotTradable) incl. consensus-error downcasts (40108 /
  40109 / IdentityInsufficientBalanceError) wired into the generic
  set-price/purchase/transfer paths
- dpns_name_history: per-name Registered/PriceSet/Purchased/Transferred
  timeline from the Document History system contract (byDocument index)
- DpnsNameStateEntry persistence: new changeset + capability bit
  DPNS_NAME_STATES + sqlite table (V005) + in-memory working set on
  PlatformWalletInfo; sold/transferred rows retained with counterparty
- dpns_names label-list merge/apply switched from append-only-by-label
  to LWW wholesale (every emitter snapshots the full list) so sold names
  can leave; set_dpns_names/remove_dpns_name mutations added
- DpnsSyncManager: periodic marketplace sweep (60s default) detecting
  price changes, acquisitions, and departures (sold vs transferred
  classified through the history contract), with quiesce/shutdown
  parity with the sibling coordinators and a completion event

Design record + browse-for-sale ($price index) investigation:
docs/DPNS_MARKETPLACE.md — a global browse-by-price query is not
buildable at any layer today (protocol-upgrade path documented).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…etplace; self-registering contract caches

examples/dpns_marketplace_testnet.rs — discover phase (HD identity map,
key-layout probe for an external identity id) and run phase exercising
the full wallet-level flow against real testnet: register -> list ->
re-price -> typed stale-price rejection -> purchase by a second identity
(owner/records/label reconciliation) -> history timeline -> typed
not-for-sale -> re-list -> delist via transfer-to-self with $price
verified cleared. All checks green 2026-08-09; transcript recorded in
docs/DPNS_MARKETPLACE.md §9.

Verification-driven fixes:
- register_name_with_external_signer now fetches and registers the DPNS
  contract with the context provider BEFORE broadcasting — without it,
  hosts that never seed known contracts fail post-broadcast proof
  verification ("unknown contract ... in document verification") even
  though the registration landed on-chain.
- the marketplace DPNS / Document History contract caches now hold the
  on-chain FETCHED contract (via fetch_contract_arc_for_document_op,
  which also registers it with the provider) instead of the bundled
  system contract, so query proof verification matches the network's
  active contract version and works on unseeded hosts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… Swift wrappers

FFI (rs-platform-wallet-ffi):
- error codes 37-40 (DocumentNotForSale, DocumentPriceChanged,
  InsufficientIdentityCredits, ContestedNameNotTradable) with stable JSON
  detail payloads in the result message so hosts recover typed values;
  DpnsNameNotFound maps to NotFound (98)
- dpns_marketplace.rs: search / name-state / my-names / set-price /
  delist / transfer / purchase / history / sync entry points over the
  wallet layer, with pointer-only repr(C) rows and paired destructors
- dpns_sync.rs: manager-handle start/stop/sync-now/interval wrappers for
  the DpnsSyncManager coordinator
- persistence: on_persist_dpns_name_states_fn vtable slot (appended,
  ABI-additive) + DpnsNameStateFFI mirror rows + DPNS_NAME_STATES
  capability dispatch

swift-sdk:
- PlatformWalletResultCode/PlatformWalletError mirrors incl. typed
  priceChanged/insufficientIdentityCredits/contestedNameNotTradable cases
  decoded from the JSON detail
- DpnsMarketplace.swift: DpnsMarketplaceName / DpnsNameStateRow /
  DpnsNameHistoryEvent value types + ManagedPlatformWallet methods
  (searchDpnsMarketplace, dpnsMarketplaceNameState,
  myDpnsMarketplaceNames, setDpnsNamePrice, delistDpnsName,
  transferDpnsName, purchaseDpnsName, dpnsNameHistory,
  syncDpnsMarketplace)
- DpnsSyncManager Swift wrappers; PersistentDPNSName grows marketplace
  columns (documentId, price, sale status, counterparty) fed by the new
  persister callback; marketplace columns are meaningful only while
  documentIdBase58 != nil (clear-don't-delete: the row is shared with
  the identity label cache)

Verified: cargo test -p platform-wallet-ffi (290 tests) and
-p platform-wallet (752) green; build_ios.sh --target mac + swift build
clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added this to the v4.2.0 milestone Aug 9, 2026
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

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

Adds end-to-end DPNS marketplace support across Rust wallet logic, persistence, FFI, JNI, Swift, and Kotlin. The change includes trading, history, synchronization, typed errors, versioned callbacks, database state, and example interfaces.

Changes

DPNS marketplace and wallet coordination

Layer / File(s) Summary
Wallet marketplace operations
packages/rs-platform-wallet/src/wallet/identity/network/*, packages/rs-platform-wallet/src/changeset/*
Adds DPNS search, trading, history, ownership reconciliation, typed errors, marketplace state, and wallet-wide synchronization support.
Synchronization coordinator
packages/rs-platform-wallet/src/manager/dpns_sync.rs, packages/rs-platform-wallet/src/events.rs, packages/rs-platform-wallet-ffi/src/dpns_sync.rs
Adds recurring and on-demand synchronization with lifecycle controls, per-wallet results, completion events, and shutdown handling.
Name-state persistence
packages/rs-platform-wallet-storage/src/sqlite/*, packages/rs-platform-wallet-ffi/src/persistence.rs, packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift
Adds DPNS state storage, changeset replay, versioned persistence callbacks, tombstones, and canonical ownership reconciliation.
Rust FFI surface
packages/rs-platform-wallet-ffi/src/dpns_marketplace.rs, packages/rs-platform-wallet-ffi/src/error.rs, packages/rs-platform-wallet-ffi/src/event_handler.rs
Adds C-compatible marketplace structures, operations, synchronization summaries, cleanup functions, typed error codes, and event extensions.
JNI bridge
packages/rs-unified-sdk-jni/src/dpns_marketplace.rs, packages/rs-unified-sdk-jni/src/persistence.rs, packages/rs-unified-sdk-jni/src/events.rs
Adds JNI validation, JSON conversion, marketplace calls, persistence forwarding, synchronization controls, and completion callbacks.
Swift and Kotlin SDKs
packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/*, packages/kotlin-sdk/sdk/src/main/kotlin/*
Adds typed marketplace APIs, persistence models, migrations, error mappings, synchronization events, and manager integration.
Example applications and verification
packages/swift-sdk/SwiftExampleApp/*, packages/kotlin-sdk/KotlinExampleApp/*, packages/rs-platform-wallet/examples/dpns_marketplace_testnet.rs
Adds marketplace screens, navigation, storage presentation, lifecycle wiring, and a testnet verification harness.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • dashpay/platform#4268: Shares the platform-wallet FFI manager, persistence, and event callback infrastructure.

Suggested reviewers: lklimek, llbartekll, shumkov

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: a wallet-level DPNS username marketplace with FFI and Swift wrapper support.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dpns-marketplace-wallet

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

@thepastaclaw

thepastaclaw commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

🕓 Ready for review — next in queue (commit 0dd004c)
Queue position: 1/1
ETA: start ~12:50 UTC · complete ~13:04 UTC (median 14m across 30 recent reviews; 2 slots)
Queued 10m ago · Last checked: 2026-08-09 12:50 UTC

@QuantumExplorer QuantumExplorer changed the title feat(platform-wallet): DPNS username-marketplace wallet layer, FFI, and Swift wrappers feat(platform-wallet): wallet-level DPNS username marketplace with FFI and Swift wrappers Aug 9, 2026
@codecov

codecov Bot commented Aug 9, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.29630% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.12%. Comparing base (23a0dc3) to head (0dd004c).

Files with missing lines Patch % Lines
...rs-platform-wallet-storage/src/sqlite/persister.rs 0.00% 4 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           v4.2-dev    #4348      +/-   ##
============================================
- Coverage     87.62%   86.12%   -1.50%     
============================================
  Files          2668     2706      +38     
  Lines        339345   345369    +6024     
============================================
+ Hits         297362   297466     +104     
- Misses        41983    47903    +5920     
Components Coverage Δ
dpp 88.86% <ø> (ø)
drive 86.25% <ø> (ø)
drive-abci 89.66% <ø> (ø)
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 92.88% <ø> (ø)
platform-wallet ∅ <ø> (∅)
drive-proof-verifier 48.02% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Actionable comments posted: 13

🧹 Nitpick comments (6)
packages/rs-platform-wallet-ffi/src/dpns_sync.rs (1)

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

Wrap the raw-pointer writes in explicit unsafe blocks.

This file writes through out_running, out_syncing, out_last_sync_unix, and the three sync_now out-params without an unsafe { } block. The sibling module dpns_marketplace.rs wraps every equivalent write. Explicit blocks keep the two modules consistent and make the file safe under unsafe_op_in_unsafe_fn, which becomes the default in edition 2024.

♻️ Proposed change for one site
     check_ptr!(out_running);
     // Define the out-slot before the stale-handle early return below can
     // fire, so the caller never reads uninitialized stack contents.
-    *out_running = false;
+    unsafe { *out_running = false };
 
     let option = PLATFORM_WALLET_MANAGER_STORAGE
         .with_item(handle, |manager| manager.dpns_sync().is_running());
     let running = unwrap_option_or_return!(option);
-    *out_running = running;
+    unsafe { *out_running = running };

Also applies to: 89-89, 109-109, 170-178

🤖 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 `@packages/rs-platform-wallet-ffi/src/dpns_sync.rs` at line 73, Wrap every
raw-pointer dereference and write through out_running, out_syncing,
out_last_sync_unix, and the three sync_now output parameters in explicit unsafe
blocks within the affected functions, matching the established pattern in
dpns_marketplace.rs and preserving the existing assignments.
packages/rs-platform-wallet-ffi/src/persistence.rs (1)

6213-6225: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a round-level test for the DPNS projection.

store_projects_dashpay_payments_overlay_only pins the payments fan-out through a sink callback. The new DPNS fan-out has no equivalent test. A mirrored test would pin three contracts that the unit tests in dpns_name_state_persistence.rs cannot cover: the callback fires only when changeset.dpns_name_states carries rows or tombstones, the tombstone array projects the 32-byte document ids, and a nonzero callback return flips round_success.

🤖 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 `@packages/rs-platform-wallet-ffi/src/persistence.rs` around lines 6213 - 6225,
Add a round-level test adjacent to store_projects_dashpay_payments_overlay_only
for the DPNS projection, covering callback invocation only when
changeset.dpns_name_states contains rows or tombstones, projection of tombstones
as 32-byte document IDs, and propagation of a nonzero callback result to
round_success. Mirror the existing sink-callback test setup without changing the
production fan-out behavior.
packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs (1)

1098-1137: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Paginate the history query or report truncation.

fetch_history_documents sends one query with limit: HISTORY_QUERY_LIMIT (100) and start: None. A name with more than 100 events of one type returns a silently truncated timeline, and dpns_name_history presents it as complete. A frequently re-priced name reaches this bound through priceUpdate documents alone.

Loop with Start::StartAfter(last_id) until a page returns fewer than HISTORY_QUERY_LIMIT rows, or return a "truncated" flag so the caller can render it.

🤖 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 `@packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs`
around lines 1098 - 1137, Update fetch_history_documents to paginate through all
history documents using the query’s start cursor, continuing with
Start::StartAfter(last_id) until a page contains fewer than HISTORY_QUERY_LIMIT
rows; preserve ordering and accumulate results across pages so dpns_name_history
does not receive a silently truncated timeline.
packages/rs-platform-wallet/src/wallet/identity/network/dpns.rs (1)

135-154: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Apply the same contract pre-registration to register_name_with_signer.

register_name_with_external_signer now calls self.dpns_contract().await? at Line 195 so the post-broadcast proof can resolve the DPNS contract. register_name_with_signer reaches the same self.sdk.register_dpns_name(input) broadcast at Line 152 without that step, so it stays exposed to the "unknown contract … in document verification" failure on a host that does not pre-seed known contracts.

dpns_contract() returns PlatformWalletError, while this method returns dash_sdk::Error, so the call needs a mapping or an ignore-on-failure.

🤖 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 `@packages/rs-platform-wallet/src/wallet/identity/network/dpns.rs` around lines
135 - 154, Update register_name_with_signer to call self.dpns_contract().await
before constructing or submitting RegisterDpnsNameInput, matching
register_name_with_external_signer’s pre-registration contract step. Convert the
PlatformWalletError into dash_sdk::Error, or explicitly ignore the lookup
failure as appropriate, while preserving the existing registration and
full_domain_name return flow.
packages/rs-platform-wallet/src/manager/mod.rs (1)

798-800: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add DpnsSyncManager to the shutdown doc list.

The doc comment enumerates the quiesced coordinators. DpnsSyncManager is now quiesced at Line 883 and Line 896 but is absent from the list.

📝 Proposed doc fix
-    /// Stops SPV and **quiesces** the periodic coordinators
-    /// (`PlatformAddressSyncManager`, `IdentitySyncManager`,
-    /// `DashPaySyncManager`, `ShieldedSyncManager`) — cancelling each loop
+    /// Stops SPV and **quiesces** the periodic coordinators
+    /// (`PlatformAddressSyncManager`, `IdentitySyncManager`,
+    /// `DashPaySyncManager`, `DpnsSyncManager`, `ShieldedSyncManager`) — cancelling each loop
🤖 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 `@packages/rs-platform-wallet/src/manager/mod.rs` around lines 798 - 800,
Update the shutdown documentation comment near the coordinator list to include
DpnsSyncManager alongside PlatformAddressSyncManager, IdentitySyncManager,
DashPaySyncManager, and ShieldedSyncManager, matching the coordinators quiesced
by the shutdown implementation.
packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentDPNSName.swift (1)

100-112: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Declare the defaults that the comments promise.

Line 101 states that saleStatusRaw "Defaults to 0", and Line 111 states that marketplaceUpdatedAt is 0 when never written. Neither property declares a default value. Both are non-optional, so the documented default exists only because init assigns it at Line 153 and Line 155. Add the defaults to the declarations so the stated contract holds for every construction path.

♻️ Proposed change
-    public var saleStatusRaw: Int16
+    public var saleStatusRaw: Int16 = 0
@@
-    public var marketplaceUpdatedAt: UInt64
+    public var marketplaceUpdatedAt: UInt64 = 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
`@packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentDPNSName.swift`
around lines 100 - 112, Declare inline default values of 0 for saleStatusRaw and
marketplaceUpdatedAt in the PersistentDPNSName model, while preserving the
existing initializer behavior and types.
🤖 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 `@packages/rs-platform-wallet-ffi/src/dpns_marketplace.rs`:
- Around line 179-181: Run cargo fmt --all to apply rustfmt-default formatting
across both new FFI modules. In
packages/rs-platform-wallet-ffi/src/dpns_marketplace.rs lines 179-181, reformat
owned_c_string and all reported chains in lines 177-917, including the iterator
collects at 384-385 and 457-458; in
packages/rs-platform-wallet-ffi/src/dpns_sync.rs lines 75-76, also reformat the
PLATFORM_WALLET_MANAGER_STORAGE.with_item chains at 75-76, 91-92, and 111-112
using rustfmt’s leading .with_item continuation form.

In `@packages/rs-platform-wallet-ffi/src/error.rs`:
- Around line 1405-1413: Move the marketplace helper `message_of` and its
associated tests so the documentation block for
`message_signing_failed_falls_through_to_unknown` directly precedes that test.
Ensure the helper’s own documentation remains attached only to `message_of`, and
preserve the existing test behavior.

In `@packages/rs-platform-wallet-storage/migrations/V005__dpns_name_states.rs`:
- Line 23: Update the migration schema near the price column in V005 to enforce
CHECK (price IS NULL OR price >= 0), preserving NULL as valid while rejecting
negative prices. Add or verify the migration test covering price = -1 and assert
that inserting such a row fails.

In `@packages/rs-platform-wallet-storage/src/sqlite/schema/dpns_name_states.rs`:
- Around line 16-61: Run rustfmt with workspace defaults on both affected files:
reformat the params! argument list in apply within dpns_name_states.rs,
including the inline map/transpose expressions, and reformat the
DpnsNameStateFFI struct literal in build_dpns_name_state_entries within
dpns_name_state_persistence.rs; preserve all behavior and ensure cargo fmt
--check --all passes.

In `@packages/rs-platform-wallet/examples/dpns_marketplace_testnet.rs`:
- Around line 116-132: Run cargo fmt on
packages/rs-platform-wallet/examples/dpns_marketplace_testnet.rs and accept
rustfmt’s formatting throughout the file, including collapsing
private_key_matches’s signature and wrapping the over-width println! calls.
- Around line 120-124: Update the SecretKey construction in the secp256k1
key-generation flow to pass the byte array by value: dereference sk_bytes when
calling SecretKey::from_byte_array, while preserving the existing failure return
and public-key derivation.

In `@packages/rs-platform-wallet/src/manager/load.rs`:
- Line 102: Update the wallet load restore path to retrieve persisted DPNS rows
despite the load contract lacking a DPNS field, convert them into the expected
state map, and assign that map to dpns_name_states instead of initializing an
empty BTreeMap. Preserve the local_dpns_name_states behavior after restart
without requiring synchronization.

In `@packages/rs-platform-wallet/src/test_support.rs`:
- Line 257: Adjust the indentation of dpns_name_states in all five
PlatformWalletInfo initializers to match the surrounding fields, especially
tracked_asset_locks, using standard rustfmt formatting.

In `@packages/rs-platform-wallet/src/wallet/apply.rs`:
- Around line 165-176: Update DpnsNameStateChangeSet::merge so it preserves the
chronological order between names upserts and removed tombstones, preventing
stale tombstones from deleting newer upserts when apply_changeset processes
removed after names. Ensure both upsert-then-remove and remove-then-upsert
sequences behave correctly, and add tests covering both orders plus repeated
replay.

In `@packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs`:
- Around line 45-49: Run cargo fmt on the entire Rust file and apply all
rustfmt-generated formatting changes, including reordering the dash_sdk imports
around Start and any differences through the rest of the file. Do not make
unrelated code changes.
- Around line 903-913: Update the documentation comment for purchase_dpns_name
to remove the claim that a wallet-owned seller’s row transitions to Sold; state
that only the seller label and balance are reconciled, and that no separate Sold
row is persisted when buyer and seller share a wallet due to document_id-only
row keying.
- Around line 301-341: Scope the caches used by dpns_contract and
document_history_contract to the wallet manager’s network rather than
process-wide function-local OnceLock values. Key each cache by a stable network
identifier, or move the caches onto the network-bound SDK/wallet instance, so
wallets on different networks cannot share contract definitions. Preserve
concurrent initialization, cache-hit provider registration, and returning the
contract for the caller’s network.

In
`@packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentDPNSName.swift`:
- Around line 177-187: Update the sale-status mapping in PersistentDPNSName to
match raw value 0 explicitly to .owned, while preserving the existing cases for
sold and transferred; return nil for any other unrecognized value so unreliable
rows are not reported as owned.

---

Nitpick comments:
In `@packages/rs-platform-wallet-ffi/src/dpns_sync.rs`:
- Line 73: Wrap every raw-pointer dereference and write through out_running,
out_syncing, out_last_sync_unix, and the three sync_now output parameters in
explicit unsafe blocks within the affected functions, matching the established
pattern in dpns_marketplace.rs and preserving the existing assignments.

In `@packages/rs-platform-wallet-ffi/src/persistence.rs`:
- Around line 6213-6225: Add a round-level test adjacent to
store_projects_dashpay_payments_overlay_only for the DPNS projection, covering
callback invocation only when changeset.dpns_name_states contains rows or
tombstones, projection of tombstones as 32-byte document IDs, and propagation of
a nonzero callback result to round_success. Mirror the existing sink-callback
test setup without changing the production fan-out behavior.

In `@packages/rs-platform-wallet/src/manager/mod.rs`:
- Around line 798-800: Update the shutdown documentation comment near the
coordinator list to include DpnsSyncManager alongside
PlatformAddressSyncManager, IdentitySyncManager, DashPaySyncManager, and
ShieldedSyncManager, matching the coordinators quiesced by the shutdown
implementation.

In `@packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs`:
- Around line 1098-1137: Update fetch_history_documents to paginate through all
history documents using the query’s start cursor, continuing with
Start::StartAfter(last_id) until a page contains fewer than HISTORY_QUERY_LIMIT
rows; preserve ordering and accumulate results across pages so dpns_name_history
does not receive a silently truncated timeline.

In `@packages/rs-platform-wallet/src/wallet/identity/network/dpns.rs`:
- Around line 135-154: Update register_name_with_signer to call
self.dpns_contract().await before constructing or submitting
RegisterDpnsNameInput, matching register_name_with_external_signer’s
pre-registration contract step. Convert the PlatformWalletError into
dash_sdk::Error, or explicitly ignore the lookup failure as appropriate, while
preserving the existing registration and full_domain_name return flow.

In
`@packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentDPNSName.swift`:
- Around line 100-112: Declare inline default values of 0 for saleStatusRaw and
marketplaceUpdatedAt in the PersistentDPNSName model, while preserving the
existing initializer behavior and types.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1c7a22db-8b5c-4f8d-9c93-657f7dcaf5e6

📥 Commits

Reviewing files that changed from the base of the PR and between 838ae66 and 748159e.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (42)
  • packages/rs-platform-wallet-ffi/src/dpns_marketplace.rs
  • packages/rs-platform-wallet-ffi/src/dpns_name_state_persistence.rs
  • packages/rs-platform-wallet-ffi/src/dpns_sync.rs
  • packages/rs-platform-wallet-ffi/src/error.rs
  • packages/rs-platform-wallet-ffi/src/lib.rs
  • packages/rs-platform-wallet-ffi/src/persistence.rs
  • packages/rs-platform-wallet-storage/migrations/V005__dpns_name_states.rs
  • packages/rs-platform-wallet-storage/src/sqlite/persister.rs
  • packages/rs-platform-wallet-storage/src/sqlite/schema/dpns_name_states.rs
  • packages/rs-platform-wallet-storage/src/sqlite/schema/mod.rs
  • packages/rs-platform-wallet/Cargo.toml
  • packages/rs-platform-wallet/docs/DPNS_MARKETPLACE.md
  • packages/rs-platform-wallet/examples/dpns_marketplace_testnet.rs
  • packages/rs-platform-wallet/src/changeset/changeset.rs
  • packages/rs-platform-wallet/src/changeset/mod.rs
  • packages/rs-platform-wallet/src/changeset/persistence_capabilities.rs
  • packages/rs-platform-wallet/src/error.rs
  • packages/rs-platform-wallet/src/events.rs
  • packages/rs-platform-wallet/src/manager/accessors.rs
  • packages/rs-platform-wallet/src/manager/dpns_sync.rs
  • packages/rs-platform-wallet/src/manager/load.rs
  • packages/rs-platform-wallet/src/manager/mod.rs
  • packages/rs-platform-wallet/src/manager/wallet_lifecycle.rs
  • packages/rs-platform-wallet/src/test_support.rs
  • packages/rs-platform-wallet/src/wallet/apply.rs
  • packages/rs-platform-wallet/src/wallet/asset_lock/sync/recovery.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/contact_requests.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/document.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/dpns.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/mod.rs
  • packages/rs-platform-wallet/src/wallet/identity/state/managed_identity/identity_ops.rs
  • packages/rs-platform-wallet/src/wallet/identity/state/manager/apply.rs
  • packages/rs-platform-wallet/src/wallet/platform_wallet.rs
  • packages/rs-platform-wallet/src/wallet/platform_wallet_traits.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/Persistence/DashModelContainer.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentDPNSName.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/DpnsMarketplace.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManager.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerDpnsSync.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift

Comment thread packages/rs-platform-wallet-ffi/src/dpns_marketplace.rs
Comment thread packages/rs-platform-wallet-ffi/src/error.rs Outdated
Comment thread packages/rs-platform-wallet-storage/migrations/V005__dpns_name_states.rs Outdated
Comment thread packages/rs-platform-wallet/examples/dpns_marketplace_testnet.rs Outdated
Comment thread packages/rs-platform-wallet/src/wallet/apply.rs
QuantumExplorer and others added 6 commits August 9, 2026 15:33
…n the JNI vtable

`PersistenceCallbacks` is constructed exhaustively in
rs-unified-sdk-jni's `build_vtable`, so adding
`on_persist_dpns_name_states_fn` broke the Android build — a break no
local Rust or Swift verification compiles (only CI's Kotlin job does).

Android has no marketplace surface and no Kotlin mirror for the
name-state rows, so the slot is an explicit `None` with the consequence
documented: the FFI persister only attests `DPNS_NAME_STATES` when the
callback is wired, so the capability stays unattested and marketplace
sale state is session-scoped on Android rather than reported as durable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI's Rust workspace job runs `cargo fmt --all --check` before the test
run; the new marketplace files (wallet, storage, FFI, example) were not
rustfmt-clean. Formatting only — no behavioural change; tests re-run
green (752 + 268 + 132).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… merge ordering, honest unknowns

CI (secrets scan):
- rename the `seed` fixture discriminator in the dpns_name_states test;
  `seed` is a FORBIDDEN substring in schema files (secrets_scan guard,
  see SECRETS.md). The guard is a deliberate blunt scan — rename rather
  than weaken it.

Review fixes:
- **contract caches are now keyed by network.** Switching these to
  on-chain FETCHED contracts (4adfca7) made a process-global OnceLock
  wrong: a host can hold managers on several networks at once, and the
  first caller's network would decide the contract definition every
  later caller used for queries and proof verification. Networks run
  different protocol versions, so the DPNS / Document History contracts
  can differ in schema. Now a (network, contract id)-keyed cache, with
  no lock held across the await.
- `DpnsNameStateChangeSet::merge` is last-OPERATION-wins per document
  id: each side evicts the key from the other, so a stale tombstone can
  no longer swallow a newer upsert (the sqlite writer applies inserts
  before deletes, and a marketplace row can legitimately come back when
  a name is re-acquired). Tests cover both orders plus replay.
- `purchase_dpns_name` doc no longer claims the seller gets a `Sold`
  row — the buyer's `Owned` row already occupies that document_id key,
  so the seller's departure is the label removal; `Sold` rows come from
  the sync pass.
- Swift `PersistentDPNSName.saleStatus` returns nil for an unknown
  discriminant instead of `.owned`, so an older build reports a
  departed name as unreadable rather than still-owned.
- V005 `price` column gains CHECK (price IS NULL OR price >= 0) as a
  corruption backstop, with a test.
- move the FFI `message_of` helper / `MessageSigningFailed` doc block so
  the doc block attaches to its own test again.

Verified: 752 + 268 + 133 tests, secrets_scan, cargo fmt --all --check,
cargo check --workspace --all-features, swift build — all clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…scoped

`local_dpns_name_states` reads an in-memory map that starts empty on
every process start and is repopulated by the first sync pass — the
wallet load path does not rehydrate it, matching the invitations store
(SqlitePersister does not attest WALLET_RESTORE). Say so on the method
and record the restore work as a known follow-up in the design doc, so
no caller reads an empty result as "no names".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… alias

CI runs `cargo clippy --workspace --all-targets --all-features --locked
-- --no-deps -D warnings`, which denies `clippy::type_complexity` on the
inline `OnceLock<RwLock<BTreeMap<(Network, Identifier), Arc<DataContract>>>>`
introduced with the per-network cache. Extract `SystemContractCache`.

Verified with CI's exact clippy invocation across the workspace.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ead-only

TC-P1-003 requires every `.prepare(` in `src/sqlite/schema/*.rs` to be a
writer using `prepare_cached`, unless the SELECT is listed in
`READ_ONLY_PREPARE_ALLOWED`. `dpns_name_states::read_all` is a
test-gated one-shot reader — same shape as `invitations::read_all` —
so list it rather than caching a statement no production path runs.

Verified: 1393 tests across platform-wallet{,-ffi,-storage}, 0 failures.

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

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

Actionable comments posted: 1

🤖 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 `@packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs`:
- Line 1006: Update the required-amount calculation near expected_price to
detect overflow when adding DOCUMENT_TRANSITION_FEE_RESERVE_CREDITS and return
InvalidParameter before broadcast instead of using saturating_add; preserve the
existing availability validation for non-overflowing sums.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: be07844c-79a7-4621-bfd1-264866e9472a

📥 Commits

Reviewing files that changed from the base of the PR and between 748159e and d1d0b33.

📒 Files selected for processing (15)
  • packages/rs-platform-wallet-ffi/src/dpns_marketplace.rs
  • packages/rs-platform-wallet-ffi/src/dpns_name_state_persistence.rs
  • packages/rs-platform-wallet-ffi/src/dpns_sync.rs
  • packages/rs-platform-wallet-ffi/src/error.rs
  • packages/rs-platform-wallet-storage/migrations/V005__dpns_name_states.rs
  • packages/rs-platform-wallet-storage/src/sqlite/schema/dpns_name_states.rs
  • packages/rs-platform-wallet-storage/tests/sqlite_compile_time.rs
  • packages/rs-platform-wallet/docs/DPNS_MARKETPLACE.md
  • packages/rs-platform-wallet/examples/dpns_marketplace_testnet.rs
  • packages/rs-platform-wallet/src/changeset/changeset.rs
  • packages/rs-platform-wallet/src/changeset/mod.rs
  • packages/rs-platform-wallet/src/test_support.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs
  • packages/rs-unified-sdk-jni/src/persistence.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentDPNSName.swift
🚧 Files skipped from review as they are similar to previous changes (11)
  • packages/rs-platform-wallet/src/test_support.rs
  • packages/rs-platform-wallet/src/changeset/mod.rs
  • packages/rs-platform-wallet-storage/migrations/V005__dpns_name_states.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentDPNSName.swift
  • packages/rs-platform-wallet-ffi/src/error.rs
  • packages/rs-platform-wallet-ffi/src/dpns_marketplace.rs
  • packages/rs-platform-wallet/src/changeset/changeset.rs
  • packages/rs-platform-wallet-ffi/src/dpns_sync.rs
  • packages/rs-platform-wallet/docs/DPNS_MARKETPLACE.md
  • packages/rs-platform-wallet/examples/dpns_marketplace_testnet.rs
  • packages/rs-platform-wallet-ffi/src/dpns_name_state_persistence.rs

Comment thread packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs Outdated
…ts Swift call sites type-check

Three layers, all masked by an incremental `swift build` reusing a
stale generated header:

- `PersistenceCallbacksExtension.on_persist_dpns_name_states_fn` was
  declared as `Option<PersistDpnsNameStatesFn>`. cbindgen does not
  expand a named fn-pointer alias inside `Option`: it emitted an opaque
  `struct Option_PersistDpnsNameStatesFn` forward declaration and then
  used it BY VALUE, an incomplete type that made the whole DashSDKFFI
  clang module unbuildable. Declared inline like every sibling
  callback, with the reason recorded so the next one stays inline.
- With the struct finally generated correctly, its `struct_size` /
  `version` fields are `uintptr_t` / `uint32_t`, but the Swift setup
  assigned `Int` / `Int32` — code that had never type-checked because
  the module never built. Added explicit UInt / UInt32 conversions at
  both call sites.
- `InvitationPersistenceTests` pins the exact attested capability set;
  add `dpnsNameStates`, which the handler genuinely attests now that
  `on_persist_dpns_name_states_fn` is wired to PersistentDPNSName.

Verified after a real `build_ios.sh --target mac` regeneration (not an
incremental cache hit): header emits a proper function pointer,
`swift test` 326 passed / 0 failed, `cargo test -p platform-wallet-ffi`
292 passed.

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

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/swift-sdk/SwiftTests/SwiftDashSDKTests/DpnsMarketplacePersistenceTests.swift (1)

84-100: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the removed: path of persistDpnsNameStates.

Both tests pass removed: []. The removal branch is untested. That branch enforces a specific contract: it clears only the marketplace columns and keeps the label-cache row. If a future change deletes the row instead, it destroys state that the identity snapshot owns, and no test fails.

The setup helpers in this file already cover everything the test needs.

💚 Proposed test for the removal path
+    func testRemovalClearsOnlyTheMarketplaceSection() throws {
+        let context = ModelContext(container)
+        let owner = PersistentIdentity(identityId: ownerId, isLocal: false, network: .testnet)
+        let alice = PersistentDPNSName(identity: owner, label: "Alice", acquiredAt: 10)
+        let documentId = Data(repeating: 0x33, count: 32).toBase58String()
+        alice.documentIdBase58 = documentId
+        alice.priceCredits = 5_000
+        alice.saleStatusRaw = 1
+        alice.counterpartyIdBase58 = Data(repeating: 0x44, count: 32).toBase58String()
+        alice.marketplaceUpdatedAt = 100
+        context.insert(owner)
+        context.insert(alice)
+        try context.save()
+
+        handler.beginChangeset(walletId: walletId)
+        XCTAssertTrue(handler.persistDpnsNameStates(
+            walletId: walletId,
+            upserts: [],
+            removed: [documentId]
+        ))
+        XCTAssertTrue(handler.endChangeset(walletId: walletId, success: true))
+
+        let readContext = ModelContext(container)
+        let rows = try readContext.fetch(FetchDescriptor<PersistentDPNSName>())
+        XCTAssertEqual(rows.count, 1, "removal must clear the marketplace section, not delete the row")
+        XCTAssertEqual(rows.first?.label, "Alice")
+        XCTAssertNil(rows.first?.documentIdBase58)
+        XCTAssertNil(rows.first?.priceCredits)
+        XCTAssertEqual(rows.first?.saleStatusRaw, 0)
+        XCTAssertNil(rows.first?.counterpartyIdBase58)
+        XCTAssertEqual(rows.first?.marketplaceUpdatedAt, 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
`@packages/swift-sdk/SwiftTests/SwiftDashSDKTests/DpnsMarketplacePersistenceTests.swift`
around lines 84 - 100, Add a test in the existing DPNS marketplace persistence
test suite covering persistDpnsNameStates with a non-empty removed argument.
Assert that removal clears only marketplace-owned columns while preserving the
label-cache row and identity-snapshot state, using the file’s existing setup
helpers and test symbols.
🤖 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
`@packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentDPNSName.swift`:
- Around line 74-78: Make the canonical identity snapshot the sole authority for
PersistentDPNSName.isOwned, preventing later marketplace Owned updates from
restoring true after an identity snapshot sets false. Reconcile marketplace
persistence with the stored identity state, and add a regression test covering
the identity-then-marketplace update sequence.

In
`@packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentIdentity.swift`:
- Around line 87-90: Update upsertDPNSNames in PlatformWalletPersistenceHandler
so a sold or transferred DPNS row is not reassigned to the current identity when
the previous identity remains stored; preserve the previous-owner relationship
and retained history, using separate history storage or narrowing the
PersistentIdentity documentation contract accordingly. Add a regression test
covering same-store transfers with both identities present.

---

Nitpick comments:
In
`@packages/swift-sdk/SwiftTests/SwiftDashSDKTests/DpnsMarketplacePersistenceTests.swift`:
- Around line 84-100: Add a test in the existing DPNS marketplace persistence
test suite covering persistDpnsNameStates with a non-empty removed argument.
Assert that removal clears only marketplace-owned columns while preserving the
label-cache row and identity-snapshot state, using the file’s existing setup
helpers and test symbols.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: b7d9fcf2-27a4-4794-8113-1f332795638d

📥 Commits

Reviewing files that changed from the base of the PR and between d1d0b33 and 8b40c7a.

📒 Files selected for processing (10)
  • packages/rs-platform-wallet-ffi/src/manager.rs
  • packages/rs-platform-wallet-ffi/src/persistence.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentDPNSName.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentIdentity.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/DpnsMarketplace.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManager.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift
  • packages/swift-sdk/SwiftTests/SwiftDashSDKTests/DpnsMarketplacePersistenceTests.swift
  • packages/swift-sdk/SwiftTests/SwiftDashSDKTests/InvitationPersistenceTests.swift
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/DpnsMarketplace.swift

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Preliminary review — Codex only

The marketplace ownership, synchronization, and cache boundaries still permit incorrect wallet state, stale proof-verification context, and remotely amplified recurring work; six in-scope blocking findings require changes. The Rust DPNS completion event also stops at the FFI boundary, leaving recurring Swift clients without the documented refresh signal.
Source: reviewer backends codex-general=gpt-5.6-sol, codex-security-auditor=gpt-5.6-sol, codex-rust-quality=gpt-5.6-sol, codex-ffi-engineer=gpt-5.6-sol; final verifier backend codex-verifier=gpt-5.6-sol. openclaw-agent/cliproxy/gpt-5.6-sol is orchestration-only and not reviewer evidence.

Validated blockers were found in the Codex precheck. Opus is deferred until a fresh Codex revalidation clears the blocker gate.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (completed), gpt-5.6-sol — security-auditor (completed), gpt-5.6-sol — rust-quality (completed), gpt-5.6-sol — ffi-engineer (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet: not run (deferred by blocker gate)

🔴 6 blocking | 🟡 1 suggestion(s)

🤖 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 `packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs`:
- [BLOCKING] packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs:1297-1304: Marketplace sync treats observed identities as wallet-owned
  `IdentityManager::identity_ids()` deliberately includes both signing-capable identities in `wallet_identities` and observed, read-only identities in `out_of_wallet_identities`. This pass consequently queries `records.identity` for every observed contact, persists their documents as this wallet's `Owned` names, and can mutate their legacy `dpns_names` lists. It also discloses the wallet's observed/contact identity set through periodic DAPI queries. The same ownership erasure occurs in `is_wallet_identity` at lines 798-802 because `IdentityManager::identity()` searches both buckets; transferring a name to an observed contact is therefore treated as an intra-wallet transfer and replaces the sender's departure row with an `Owned` row for that contact. Build the sync snapshot and ownership predicates from `wallet_identities[&self.wallet_id]` only, and apply the same restriction before signing, balance, and label mutations.
- [BLOCKING] packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs:1427-1447: Clear or reclassify the persisted row when a DPNS document was deleted
  `Ok(None)` is a proof-verified result that the domain document no longer exists, but it is handled together with a transport or proof failure. The caller removes the identity's label and writes no row or tombstone, leaving the previous `DpnsNameStateEntry` intact—often with `status == Owned` and a stale sale price. The Rust `my names` API and Swift marketplace fields can therefore continue reporting a deleted name as owned indefinitely. Split `Ok(None)` from `Err(_)`: a confirmed absence must remove or explicitly reclassify the tracked row, while a failed lookup may retain it for retry.
- [BLOCKING] packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs:1402-1403: Serialize sync reconciliation with marketplace trade operations
  The sync pass performs its network reads before taking wallet-state locks and later writes every fetched row unconditionally. The manager's `is_syncing` flag only serializes manager-level sync passes; the public per-wallet sync entry point and `set_dpns_name_price`, `delist_dpns_name`, `transfer_dpns_name`, and `purchase_dpns_name` bypass it. A sync can fetch an old owned or unlisted document, a concurrent trade can confirm and persist a newer listed or transferred state, and this final write can then overwrite the confirmed result and re-add a departed label. Add a per-wallet DPNS operation gate shared by sync and trade reconciliation, or reject stale reconciliation with an authoritative freshness check covering both rows and label mutations.
- [BLOCKING] packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs:334-345: Do not reuse a system contract solely by Network enum
  The process-wide cache now prevents mainnet/testnet collisions, but `(dashcore::Network, contract_id)` still does not identify an SDK trust context, a particular devnet/regtest deployment, or the active contract version. System-contract upgrades retain the contract ID, and `Sdk::set_context_provider` explicitly permits provider replacement. A cache hit after a provider/chain replacement or contract upgrade therefore registers the old contract into the new provider without fetching or proving it against that context. Queries, transition construction, and post-broadcast proof verification can then use a stale or foreign schema until process restart. Scope the cache to the SDK/provider/chain lifetime and invalidate it when that context or active contract version changes, rather than using a permanent process-global map.
- [BLOCKING] packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs:1314-1317: Unsolicited name transfers can make every sync pass unbounded
  Every recurring pass calls `dpns_domain_states_for_identity(..., None)`, whose pagination loop drains all pages and accumulates every document in one `Vec`. A DPNS transfer requires the current owner's authorization but no recipient consent, and the protocol imposes no per-identity name-count bound. An attacker can therefore transfer many names to a known victim identity and force that wallet to repeat unbounded proof queries, allocations, conversions, and persistence work every 60 seconds. The attacker pays the on-chain cost once while the victim repeatedly pays the client cost. Process pages incrementally under a fixed per-pass budget and retain a cursor so injected names cannot create sustained network, CPU, and memory pressure or starve legitimate names.
- [BLOCKING] packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs:1491-1499: Multi-hop departures are attributed to the latest owner instead of the actual counterparty
  `classify_departure` does not receive the identity whose label departed. It only checks whether a purchase document's buyer and timestamp match the live domain document's latest owner and transfer timestamp. If `S` owned the name before the app went offline and ownership then moved `S → A → B`, the latest `A → B` purchase satisfies this predicate and records `S` as having sold directly to `B`; if `S → A` was a transfer, it can also refresh `S` as a seller even though `A` made the sale. Match history against the departing identity—`sellerId` for purchases and the history document's `$ownerId` for transfers—and persist the recipient from that matching departure event rather than the current live owner.

In `packages/rs-platform-wallet/src/events.rs`:
- [SUGGESTION] packages/rs-platform-wallet/src/events.rs:37-46: Forward DPNS sync completion through the C and Swift event bridge
  `DpnsSyncManager::sync_now` dispatches this new event, but `FFIEventHandler` does not override `on_dpns_marketplace_sync_completed`, and Swift's `PlatformWalletEventHandler.makeCallbacks()` has no corresponding callback or trampoline. Recurring synchronization therefore silently discards the per-wallet summaries at the Rust/FFI boundary, so Swift clients cannot receive the departure signal that this method documents as the trigger for refreshing main-username and profile state. Add a C/Swift event bridge with owned summary values. Because the existing `EventHandlerCallbacks` is unsized and consumed via `ptr::read`, use a size/version-tagged extension or registration API rather than introducing another unsafe by-value growth for older callers.

Comment thread packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs Outdated
Comment thread packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs Outdated
Comment thread packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs Outdated
Comment thread packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs Outdated
Comment thread packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs Outdated
Comment thread packages/rs-platform-wallet/src/events.rs

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

Actionable comments posted: 7

🧹 Nitpick comments (10)
packages/kotlin-sdk/sdk/src/androidTest/kotlin/org/dashfoundation/dashsdk/persistence/DashDatabaseMigrationTest.kt (1)

378-392: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Assert preservation of the legacy DPNS columns.

The fixture writes label, normalizedLabel, parentDomainName, normalizedParentDomainName, acquiredAt, and identityId. The query checks only the new marketplace columns. A migration that loses or changes the existing values would still pass.

Extend the query and assertions to verify the inserted legacy values and row identity.

This follows the test's stated v9-to-v10 data-preservation objective.

🤖 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
`@packages/kotlin-sdk/sdk/src/androidTest/kotlin/org/dashfoundation/dashsdk/persistence/DashDatabaseMigrationTest.kt`
around lines 378 - 392, Extend the query in DashDatabaseMigrationTest to include
the legacy DPNS columns label, normalizedLabel, parentDomainName,
normalizedParentDomainName, acquiredAt, and identityId, then assert their
inserted values along with the existing marketplace-column checks. Also verify
the expected row identity, preserving the v9-to-v10 data-preservation coverage.
packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/DpnsMarketplacePresentationTests.swift (1)

25-52: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Add an assertion for the .notForSale mapping.

DpnsMarketplaceUI.error(_:) maps five marketplace cases explicitly. This test covers .priceChanged, .insufficientIdentityCredits, .contestedNameNotTradable, and .signingKeyUnavailable. It does not cover .notForSale. A regression that drops that case would fall through to default and emit the raw localizedDescription without detection.

💚 Proposed additional assertion
         let signing = DpnsMarketplaceUI.error(
             PlatformWalletError.signingKeyUnavailable("key 3 is watch-only")
         )
         XCTAssertTrue(signing.contains("signing key is unavailable"))
         XCTAssertTrue(signing.contains("Unlock or repair"))
+
+        let notListed = DpnsMarketplaceUI.error(PlatformWalletError.notForSale)
+        XCTAssertTrue(notListed.contains("no longer listed"))
     }

Confirm the exact notForSale case signature before applying; it takes no associated value in DpnsMarketplaceView.swift line 757.

🤖 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
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/DpnsMarketplacePresentationTests.swift`
around lines 25 - 52, Extend testTypedTradeErrorsProduceActionableMessages to
construct PlatformWalletError.notForSale with no associated value, pass it to
DpnsMarketplaceUI.error(_:), and assert the result contains the expected
not-for-sale mapping text. Preserve the existing assertions for the other typed
marketplace errors.
packages/swift-sdk/SwiftTests/SwiftDashSDKTests/DpnsMarketplaceDecodingTests.swift (2)

251-257: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the fixed yield count with an explicit expectation.

The loop yields at most 10 times and then falls through. The bound is arbitrary. If the manager needs more than 10 suspension points to publish, the test fails intermittently instead of deterministically. XCTAssertEqual does report the failure, so the test is not silently green, but the diagnostic points at the assertion rather than at the missed event.

Use an XCTestExpectation with a timeout, or poll against a deadline and call XCTFail when the deadline passes.

♻️ Proposed deadline-based wait
-        for _ in 0..<10 where manager.lastDpnsSyncEvent == nil {
-            await Task.yield()
-        }
-
-        let event = manager.lastDpnsSyncEvent
+        let deadline = Date().addingTimeInterval(2)
+        while manager.lastDpnsSyncEvent == nil, Date() < deadline {
+            await Task.yield()
+        }
+
+        let event = try XCTUnwrap(manager.lastDpnsSyncEvent, "No DPNS sync event was published")
         XCTAssertEqual(event?.syncUnixSeconds, 123)

XCTUnwrap throws, so mark the test method async throws if you adopt this form.

🤖 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
`@packages/swift-sdk/SwiftTests/SwiftDashSDKTests/DpnsMarketplaceDecodingTests.swift`
around lines 251 - 257, Replace the bounded Task.yield loop in the test with an
explicit XCTestExpectation or deadline-based polling wait for
manager.lastDpnsSyncEvent. Fail explicitly with XCTFail when the event is not
published before the timeout, and mark the test async throws if using XCTUnwrap;
keep the existing event assertions after successful retrieval.

72-88: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the unclassified-departure path.

This test exercises DpnsNameDeparture only with has_status = true and status = 1. The decoder at DpnsMarketplace.swift lines 347-348 maps every other combination to status = nil, and that is the documented "the sweep could only establish that the name departed" case. That branch is reachable from the Rust sweep and is currently untested.

Add a case with has_status = false and assert status is nil while identityId and label still decode.

💚 Proposed additional coverage
         let departed = DpnsNameDeparture(ffi: departedFFI)
         XCTAssertEqual(departed.identityId, identityId)
         XCTAssertEqual(departed.documentId, documentId)
         XCTAssertEqual(departed.status, .sold(to: counterparty))
+
+        var unclassifiedFFI = departedFFI
+        unclassifiedFFI.has_status = false
+        let unclassified = DpnsNameDeparture(ffi: unclassifiedFFI)
+        XCTAssertEqual(unclassified.identityId, identityId)
+        XCTAssertNil(unclassified.status)
🤖 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
`@packages/swift-sdk/SwiftTests/SwiftDashSDKTests/DpnsMarketplaceDecodingTests.swift`
around lines 72 - 88, Add a test case alongside the existing DpnsNameDeparture
decoding coverage that constructs DpnsNameDepartedFFI with has_status = false,
then assert DpnsNameDeparture.status is nil while identityId and label still
decode correctly. Reuse the existing departure fixture/setup patterns and keep
the sold-status case unchanged.
packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs (1)

1365-1365: 🩺 Stability & Availability | 🔵 Trivial | 🏗️ Heavy lift

A stuck departure blocks the ownership scan for that identity.

Line 1365 runs the ownership page scan only when pending_departures is empty. Lines 1462-1465 push the item back and break on a retryable failure, leaving the queue non-empty.

If one departed name keeps failing its lookup, the identity never refreshes owned rows or price changes again for the process lifetime. Consider rotating the failed item to the back of the queue, or tracking an attempt count so a persistently failing item cannot starve the ownership scan.

Also applies to: 1454-1482

🤖 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 `@packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs`
at line 1365, Update the departure-processing flow around the pending_departures
check and the retryable-failure handling near the departure loop so one
repeatedly failing item cannot prevent the ownership scan from running. Rotate
failed departures behind other queued items or enforce an attempt limit, while
preserving retries and allowing the ownership/price refresh path to execute even
when a failure remains queued.
packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/ui/storage/StorageModels.kt (1)

84-92: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Handle saleStatusRaw == 0 separately from an unclassified departure.

DpnsNameSaleStatus.OWNED is raw value 0. A row with isOwned == false and saleStatusRaw == 0 is a name whose marketplace state was cleared or never synced. The current else branch labels it "departed". Add an explicit 0L branch so an uncategorised row reads as unknown instead of asserting a departure.

♻️ Proposed change
             val ownership = if (row.bool("isOwned")) "owned" else when (row.long("saleStatusRaw")) {
                 1L -> "sold"
                 2L -> "transferred"
-                else -> "departed"
+                0L, null -> "not synced"
+                else -> "departed"
             }
🤖 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
`@packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/ui/storage/StorageModels.kt`
around lines 84 - 92, Update the ownership label logic in the subtitle lambda to
handle saleStatusRaw == 0L explicitly, returning "unknown" for uncategorised
non-owned rows; retain the existing "sold", "transferred", and "departed"
mappings for their respective values.
packages/rs-unified-sdk-jni/src/dpns_marketplace.rs (1)

419-500: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Confirm the signed-to-unsigned price contract at the JNI boundary.

setPrice and purchase cast price as u64 without a range check, while syncSetInterval rejects negative values through nonnegative_u64. The Kotlin wrapper passes ULong.toLong(), so the bit-preserving cast is required there. A direct Java caller that passes -1 silently requests u64::MAX credits. Document this bit-pattern contract in the two entry points so the asymmetry with nonnegative_u64 is intentional and visible.

🤖 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 `@packages/rs-unified-sdk-jni/src/dpns_marketplace.rs` around lines 419 - 500,
Document the signed-to-unsigned bit-pattern contract at the JNI entry points
Java_org_dashfoundation_dashsdk_ffi_DpnsMarketplaceNative_setPrice and
Java_org_dashfoundation_dashsdk_ffi_DpnsMarketplaceNative_purchase, explicitly
noting that price as u64 preserves Kotlin ULong.toLong() values and that
negative Java inputs map to their corresponding u64 bit pattern. Keep the
existing casts unchanged and make the intentional difference from
syncSetInterval’s nonnegative_u64 validation visible.
packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/dpns/DpnsMarketplace.kt (1)

10-20: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Override equals and hashCode on the data classes that hold ByteArray fields.

DpnsMarketplaceName, DpnsNameState, DpnsNameHistoryEvent, DpnsNameAdded, DpnsNameDeparted, and DpnsPriceChange all carry ByteArray properties. The generated equals compares arrays by reference, so two decodes of the same row are unequal. This breaks set membership, distinctUntilChanged, and Compose recomposition keys. Other SDK models in this package, such as WalletSyncEvent, already override both methods using contentEquals and contentHashCode.

Also applies to: 32-44, 56-78

🤖 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
`@packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/dpns/DpnsMarketplace.kt`
around lines 10 - 20, Override equals and hashCode for DpnsMarketplaceName,
DpnsNameState, DpnsNameHistoryEvent, DpnsNameAdded, DpnsNameDeparted, and
DpnsPriceChange so every ByteArray property is compared with contentEquals and
hashed with contentHashCode, while other properties retain value-based
comparison. Follow the existing WalletSyncEvent implementation pattern to ensure
independently decoded equivalent models compare and hash identically.
packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/errors/DashSdkError.kt (1)

482-511: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Document the reserved marketplace codes in the Kotlin mapping.

Rust reserves codes 37–40 for these marketplace errors and pins them with a test. Add a short comment above this block that references packages/rs-platform-wallet-ffi/src/error.rs.

🤖 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
`@packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/errors/DashSdkError.kt`
around lines 482 - 511, The marketplace error mappings for codes 37–40 in the
surrounding error-mapping function need documentation. Add a short comment
immediately above the code 37 branch referencing
packages/rs-platform-wallet-ffi/src/error.rs and noting that these codes are
reserved for marketplace errors; leave the mappings unchanged.
packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/persistence/PlatformWalletPersistenceHandlerTest.kt (1)

763-810: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for removal on an owned label row.

This test removes the DPNS name state for a row whose identity snapshot already dropped the label, so full row deletion is correct. The Swift equivalent, testMarketplaceRemovalClearsOnlyMarketplaceColumns in packages/swift-sdk/SwiftTests/SwiftDashSDKTests/DpnsMarketplacePersistenceTests.swift, covers the other case: removal on a still-owned row must clear only the marketplace columns and keep the label, acquiredAt, and isOwned.

Android has no equivalent test. A regression that deletes the label cache on an owned row would pass this suite.

Also consider asserting retained.priceCredits is null, since the callback passed hasPrice = false.

🤖 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
`@packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/persistence/PlatformWalletPersistenceHandlerTest.kt`
around lines 763 - 810, Add a second removal scenario to
marketplaceStateRetainsDepartedNameAndCanRemoveIt, or a dedicated test, where
the DPNS row remains owned with its label and acquiredAt populated; after
onRemoveDpnsNameState, assert the row remains and only marketplace fields are
cleared while label, acquiredAt, and isOwned are preserved. Also assert the
existing retained row’s priceCredits is null when hasPrice is false.
🤖 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
`@packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/ui/identity/DpnsMarketplaceScreen.kt`:
- Around line 117-132: Capture the current manager and wallet into non-null
local values before invoking launch in the refresh action, then use those locals
inside the coroutine instead of manager!! and wallet!!. Apply the same
pre-launch capture pattern to the search action and history action, preserving
their existing operations and messages.
- Line 271: Update the result Card testTag in DpnsMarketplaceScreen to use the
dpnsMarketplace.search.${row.normalizedLabel} identifier, while leaving the
existing dpnsMarketplace.owned tag unchanged.

In
`@packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/persistence/PlatformWalletPersistenceHandler.kt`:
- Around line 1169-1172: Update onRemoveDpnsNameState to match the Swift removal
semantics: preserve the dpns_names label-cache row and clear only the
marketplace-related columns instead of deleting the entire record. Reuse the
existing DAO update operation or add the corresponding targeted persistence
method, while keeping the guarded staging flow intact.
- Around line 1116-1167: Update onPersistDpnsNameState to check whether
walletIdentityId exists before staging and upserting the DpnsNameEntity. If the
identity is absent, skip the marketplace-row persistence and return success
without executing the upsert; otherwise preserve the existing flow.

In `@packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs`:
- Around line 1591-1633: Update classify_departure to return a result that
distinguishes successful classification with no matching event from
fetch_history_documents failure, preserving the failure state when either
history lookup fails. In resolve_departed_name, handle that failure outcome by
retaining the marketplace row and requesting retry, while keeping the existing
removal behavior only for a successful classification with no direct departure
event.

In `@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/SwiftExampleAppApp.swift`:
- Line 246: Separate the coordinator calls in SwiftExampleAppApp from the shared
do/catch by giving each DPNS and sync operation its own best-effort error
boundary, or route them through a helper that logs failures and continues.
Ensure a failure in an earlier stop, sync check, or start call does not prevent
stopDpnsSync at the wallet-removal path or the DPNS startup operations at the
wallet-load path. Add regression coverage for both continuation scenarios.

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/IdentityDetailView.swift`:
- Around line 237-246: Update the marketplace NavigationLink condition in
IdentityDetailView so it requires both !identity.isLocal and
hasLoadedWallet(for: identity), matching the gating used by the Top Up,
Transfer, and Withdraw actions. Keep the surrounding DPNS section visibility
logic unchanged.

---

Nitpick comments:
In
`@packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/ui/storage/StorageModels.kt`:
- Around line 84-92: Update the ownership label logic in the subtitle lambda to
handle saleStatusRaw == 0L explicitly, returning "unknown" for uncategorised
non-owned rows; retain the existing "sold", "transferred", and "departed"
mappings for their respective values.

In
`@packages/kotlin-sdk/sdk/src/androidTest/kotlin/org/dashfoundation/dashsdk/persistence/DashDatabaseMigrationTest.kt`:
- Around line 378-392: Extend the query in DashDatabaseMigrationTest to include
the legacy DPNS columns label, normalizedLabel, parentDomainName,
normalizedParentDomainName, acquiredAt, and identityId, then assert their
inserted values along with the existing marketplace-column checks. Also verify
the expected row identity, preserving the v9-to-v10 data-preservation coverage.

In
`@packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/dpns/DpnsMarketplace.kt`:
- Around line 10-20: Override equals and hashCode for DpnsMarketplaceName,
DpnsNameState, DpnsNameHistoryEvent, DpnsNameAdded, DpnsNameDeparted, and
DpnsPriceChange so every ByteArray property is compared with contentEquals and
hashed with contentHashCode, while other properties retain value-based
comparison. Follow the existing WalletSyncEvent implementation pattern to ensure
independently decoded equivalent models compare and hash identically.

In
`@packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/errors/DashSdkError.kt`:
- Around line 482-511: The marketplace error mappings for codes 37–40 in the
surrounding error-mapping function need documentation. Add a short comment
immediately above the code 37 branch referencing
packages/rs-platform-wallet-ffi/src/error.rs and noting that these codes are
reserved for marketplace errors; leave the mappings unchanged.

In
`@packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/persistence/PlatformWalletPersistenceHandlerTest.kt`:
- Around line 763-810: Add a second removal scenario to
marketplaceStateRetainsDepartedNameAndCanRemoveIt, or a dedicated test, where
the DPNS row remains owned with its label and acquiredAt populated; after
onRemoveDpnsNameState, assert the row remains and only marketplace fields are
cleared while label, acquiredAt, and isOwned are preserved. Also assert the
existing retained row’s priceCredits is null when hasPrice is false.

In `@packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs`:
- Line 1365: Update the departure-processing flow around the pending_departures
check and the retryable-failure handling near the departure loop so one
repeatedly failing item cannot prevent the ownership scan from running. Rotate
failed departures behind other queued items or enforce an attempt limit, while
preserving retries and allowing the ownership/price refresh path to execute even
when a failure remains queued.

In `@packages/rs-unified-sdk-jni/src/dpns_marketplace.rs`:
- Around line 419-500: Document the signed-to-unsigned bit-pattern contract at
the JNI entry points
Java_org_dashfoundation_dashsdk_ffi_DpnsMarketplaceNative_setPrice and
Java_org_dashfoundation_dashsdk_ffi_DpnsMarketplaceNative_purchase, explicitly
noting that price as u64 preserves Kotlin ULong.toLong() values and that
negative Java inputs map to their corresponding u64 bit pattern. Keep the
existing casts unchanged and make the intentional difference from
syncSetInterval’s nonnegative_u64 validation visible.

In
`@packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/DpnsMarketplacePresentationTests.swift`:
- Around line 25-52: Extend testTypedTradeErrorsProduceActionableMessages to
construct PlatformWalletError.notForSale with no associated value, pass it to
DpnsMarketplaceUI.error(_:), and assert the result contains the expected
not-for-sale mapping text. Preserve the existing assertions for the other typed
marketplace errors.

In
`@packages/swift-sdk/SwiftTests/SwiftDashSDKTests/DpnsMarketplaceDecodingTests.swift`:
- Around line 251-257: Replace the bounded Task.yield loop in the test with an
explicit XCTestExpectation or deadline-based polling wait for
manager.lastDpnsSyncEvent. Fail explicitly with XCTFail when the event is not
published before the timeout, and mark the test async throws if using XCTUnwrap;
keep the existing event assertions after successful retrieval.
- Around line 72-88: Add a test case alongside the existing DpnsNameDeparture
decoding coverage that constructs DpnsNameDepartedFFI with has_status = false,
then assert DpnsNameDeparture.status is nil while identityId and label still
decode correctly. Reuse the existing departure fixture/setup patterns and keep
the sold-status case unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 41df46bc-537c-4eb6-a49e-0340d198a74f

📥 Commits

Reviewing files that changed from the base of the PR and between 95d596d and 6387267.

📒 Files selected for processing (55)
  • packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/di/AppContainer.kt
  • packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/navigation/AppNavHost.kt
  • packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/navigation/Routes.kt
  • packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/ui/identity/DpnsMarketplaceScreen.kt
  • packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/ui/identity/IdentityDetailScreen.kt
  • packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/ui/storage/StorageModels.kt
  • packages/kotlin-sdk/KotlinExampleApp/app/src/main/java/org/dashfoundation/example/ui/storage/StorageRecordDetailScreen.kt
  • packages/kotlin-sdk/sdk/schemas/org.dashfoundation.dashsdk.persistence.DashDatabase/10.json
  • packages/kotlin-sdk/sdk/src/androidTest/kotlin/org/dashfoundation/dashsdk/persistence/DashDatabaseMigrationTest.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/dpns/DpnsMarketplace.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/errors/DashSdkError.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/ffi/DpnsMarketplaceNative.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/ffi/NativePersistenceBridge.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/ffi/NativeWalletEventBridge.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/persistence/DashDatabase.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/persistence/PlatformWalletPersistenceHandler.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/persistence/dao/DpnsNameDao.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/persistence/entities/DpnsNameEntity.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/wallet/PlatformWalletManager.kt
  • packages/kotlin-sdk/sdk/src/main/kotlin/org/dashfoundation/dashsdk/wallet/WalletSyncEvent.kt
  • packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/dpns/DpnsMarketplaceTest.kt
  • packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/errors/DpnsMarketplaceErrorTest.kt
  • packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/persistence/PlatformWalletPersistenceHandlerTest.kt
  • packages/kotlin-sdk/sdk/src/test/kotlin/org/dashfoundation/dashsdk/wallet/WalletEventFanOutTest.kt
  • packages/rs-platform-wallet-ffi/src/dpns_marketplace.rs
  • packages/rs-platform-wallet-ffi/src/event_handler.rs
  • packages/rs-platform-wallet-ffi/src/manager.rs
  • packages/rs-platform-wallet/examples/dpns_marketplace_testnet.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/dpns_marketplace.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/identity_handle.rs
  • packages/rs-platform-wallet/src/wallet/identity/network/payments.rs
  • packages/rs-platform-wallet/src/wallet/identity/state/manager/accessors.rs
  • packages/rs-platform-wallet/src/wallet/identity/state/manager/mod.rs
  • packages/rs-platform-wallet/src/wallet/platform_wallet.rs
  • packages/rs-unified-sdk-jni/src/dpns_marketplace.rs
  • packages/rs-unified-sdk-jni/src/events.rs
  • packages/rs-unified-sdk-jni/src/lib.rs
  • packages/rs-unified-sdk-jni/src/persistence.rs
  • packages/rs-unified-sdk-jni/src/wallet_manager.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/Persistence/DashModelContainer.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentDPNSName.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/DpnsMarketplace.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManager.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerAddressSync.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManagerDpnsSync.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/SwiftExampleAppApp.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/DpnsMarketplaceView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/IdentityDetailView.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/StorageModelListViews.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Views/StorageRecordDetailViews.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleAppTests/DpnsMarketplacePresentationTests.swift
  • packages/swift-sdk/SwiftTests/SwiftDashSDKTests/DpnsMarketplaceDecodingTests.swift
  • packages/swift-sdk/SwiftTests/SwiftDashSDKTests/DpnsMarketplacePersistenceTests.swift
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/rs-platform-wallet/src/wallet/platform_wallet.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletManager.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Persistence/DashModelContainer.swift
  • packages/swift-sdk/SwiftTests/SwiftDashSDKTests/DpnsMarketplacePersistenceTests.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/Persistence/Models/PersistentDPNSName.swift
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletResult.swift
  • packages/rs-platform-wallet/examples/dpns_marketplace_testnet.rs
  • packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletPersistenceHandler.swift

Comment thread packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/SwiftExampleAppApp.swift Outdated

@QuantumExplorer QuantumExplorer left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Approved

@QuantumExplorer
QuantumExplorer merged commit 6373e00 into v4.2-dev Aug 9, 2026
19 checks passed
@QuantumExplorer
QuantumExplorer deleted the feat/dpns-marketplace-wallet branch August 9, 2026 12:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants