Skip to content

Conversation

@QuantumExplorer
Copy link
Member

@QuantumExplorer QuantumExplorer commented Nov 10, 2025

Issue being fixed or feature implemented

What was done?

How Has This Been Tested?

Breaking Changes

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

Summary by CodeRabbit

  • New Features

    • Comprehensive Platform Wallet FFI with C/Swift bindings, enabling wallet creation, identity management, contact requests (send/accept/reject), established contact management, and contact metadata (alias, note, hide/unhide).
    • DashPay contact request support and cryptographic helpers for encrypting extended public keys and account labels.
    • Swift APIs and UI integrations for PlatformWallet, IdentityManager, ManagedIdentity, ContactRequest, and EstablishedContact.
  • Chores

    • Added new workspace packages for platform encryption and wallet FFI; updated workspace dependency references to local path variants.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 10, 2025

Walkthrough

Adds a new platform-encryption crate, a large rs-platform-wallet-ffi FFI layer, Platform Wallet Rust modules (identity management, contacts, DIP‑15 flows), SDK DashPay contact request features, Swift bindings and app UI updates, and updates workspace manifests to prefer local path dependencies over remote git sources.

Changes

Cohort / File(s) Summary
Workspace & Manifests
Cargo.toml, packages/rs-dpp/Cargo.toml, packages/rs-platform-wallet/Cargo.toml, packages/rs-sdk/Cargo.toml, packages/rs-dapi/Cargo.toml, packages/rs-sdk-ffi/Cargo.toml
Added workspace members; replaced multiple git-based dependencies with local path dependencies; added platform-encryption and other new deps; adjusted features.
Encryption crate
packages/rs-platform-encryption/Cargo.toml, packages/rs-platform-encryption/src/lib.rs
New crate implementing ECDH shared-key derivation, AES-256-CBC encrypt/decrypt with PKCS7, helpers for encrypting/decrypting extended public keys and account labels, CryptoError enum and tests.
Platform Wallet FFI crate (manifest & config)
packages/rs-platform-wallet-ffi/Cargo.toml, packages/rs-platform-wallet-ffi/cbindgen.toml, packages/rs-platform-wallet-ffi/IMPLEMENTATION_SUMMARY.md, packages/rs-platform-wallet-ffi/README.md
New FFI crate manifest, cbindgen config, implementation summary and README documenting C API, generation rules and rename mappings for generated C types.
Platform Wallet FFI public header
packages/rs-platform-wallet-ffi/platform_wallet_ffi.h
New C header declaring Handle, NetworkType, error/result types, IdentifierBytes, BlockTime, ContactRequest, EstablishedContact, IdentifierArray and ~50+ extern C functions for wallet/identity/contact operations and memory management.
Platform Wallet FFI implementation modules
packages/rs-platform-wallet-ffi/src/{lib,error,types,utils,handle,platform_wallet_info,identity_manager,managed_identity,contact_request,contact,established_contact}.rs, packages/rs-platform-wallet-ffi/tests/*, packages/rs-platform-wallet-ffi/tests/test_data/*
Implemented FFI modules: error/result helpers, type conversions, handle storage, wallet constructors (seed/mnemonic), identity manager and managed identity FFI, contact request creation/fields, contact flows, established contact API, utility serialization and identifier helpers, and comprehensive tests + test data.
Platform Wallet Rust modules (core)
packages/rs-platform-wallet/src/{block_time,contact_request,established_contact,crypto,error}.rs
Added BlockTime type, ContactRequest struct and accessors, EstablishedContact struct and mutators, re-exported platform-encryption via crypto.rs, and PlatformWalletError enum.
ManagedIdentity & IdentityManager refactor
packages/rs-platform-wallet/src/managed_identity/*, packages/rs-platform-wallet/src/identity_manager/*
Reorganized ManagedIdentity and IdentityManager into modular subfiles (mod, identity_ops, label, sync, contact_requests, contacts, initializers, accessors), adding lifecycle, contact request handling, auto-establish logic, block-time sync and accessors.
PlatformWalletInfo & integrations
packages/rs-platform-wallet/src/platform_wallet_info/*
New PlatformWalletInfo type with per-network IdentityManager map; added accessors, contact-request handling (including SDK paths), managed account operations delegation, matured transaction processing, WalletInfoInterface and WalletTransactionChecker implementations.
SDK DashPay platform features
packages/rs-sdk/src/platform/dashpay/{mod,contact_request,contact_request_queries}.rs
Added DashPay contact-request creation/sending with ECDH provider abstraction, encryption, document building and SDK methods; added query helpers to fetch sent/received contactRequest documents.
SDK FFI updates & re-exports
packages/rs-sdk-ffi/src/{lib,dashpay/contact_request,platform_wallet_types}.rs
Added dashpay FFI module, re-exported platform-wallet-ffi types/functions, added init/log/version FFI functions; removed legacy core/unified FFI modules (deleted .bak files).
Rust integration tests
packages/rs-platform-wallet/tests/contact_workflow_tests.rs
Added extensive integration tests covering contact request workflows, establishment, metadata, rejection/cancellation and concurrency.
Swift bindings & types
packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/{PlatformWalletFFI,PlatformWalletTypes,PlatformWallet,IdentityManager,ManagedIdentity,ContactRequest,EstablishedContact,README}.swift
New Swift FFI declarations, type mappings (PlatformNetwork, BlockTime, PlatformWalletError), high-level wrappers for PlatformWallet, IdentityManager, ManagedIdentity, ContactRequest, EstablishedContact and documentation.
Swift example app & service / UI
packages/swift-sdk/SwiftExampleApp/.../Services/DashPayService.swift, packages/swift-sdk/SwiftExampleApp/.../Views/FriendsView.swift
Added DashPayService ObservableObject, UI components ContactRowView and ContactRequestRow, replaced friends list with contact/request flows and wired accept/reject actions.
Swift test suites
packages/swift-sdk/SwiftTests/Tests/SwiftDashSDKTests/*.swift
Added multiple Swift unit/integration test files for PlatformWallet, types, IdentityManager, ManagedIdentity, ContactRequest, EstablishedContact and integration scaffolding.
SPV & app runtime tweaks
packages/swift-sdk/Sources/SwiftDashSDK/SPV/SPVClient.swift, packages/swift-sdk/SwiftExampleApp/.../Core/Services/WalletService.swift, packages/swift-sdk/SwiftExampleApp/.../Core/Views/CoreContentView.swift
Added SPV client accessors and transaction/block counts, WalletService initialization/timeout helpers and isInitializing flag, UI navigation and clear behavior adjustments.

Sequence Diagram(s)

sequenceDiagram
    participant Swift as Swift App
    participant FFI as Platform Wallet FFI
    participant Rust as Platform Wallet (Rust)
    participant Crypto as Platform Encryption

    Swift->>FFI: platform_wallet_info_create_from_mnemonic(mnemonic, passphrase)
    FFI->>Rust: create PlatformWalletInfo from mnemonic
    Rust-->>FFI: return wallet handle
    FFI-->>Swift: wallet handle

    Swift->>FFI: identity_manager_create()
    FFI->>Rust: create IdentityManager
    Rust-->>FFI: manager handle
    FFI-->>Swift: manager handle

    Swift->>FFI: managed_identity_send_contact_request(identityHandle, requestHandle)
    FFI->>Rust: forward send request
    Rust->>Crypto: derive_shared_key_ecdh(priv, pub)
    Crypto-->>Rust: 32-byte shared key
    Rust->>Crypto: encrypt_extended_public_key(shared_key, iv, xpub)
    Crypto-->>Rust: iv + ciphertext
    Rust->>Rust: store sent contact request
    Rust-->>FFI: success

    rect `#E8F4FF`
      Note right of Rust: On incoming matching sent request -> auto-establish
      Swift->>FFI: managed_identity_add_incoming_contact_request(...)
      FFI->>Rust: add incoming request
      alt match found
        Rust->>Rust: create EstablishedContact and update storages
      else
        Rust->>Rust: store incoming request
      end
      Rust-->>FFI: status
      FFI-->>Swift: stored/established
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • packages/rs-platform-encryption/src/lib.rs — ECDH, padding, IV handling, ciphertext format correctness vs DIP‑15.
  • packages/rs-platform-wallet-ffi/src/* — FFI memory ownership, pointer/null checks, leak and free paths for C strings/byte arrays.
  • packages/rs-platform-wallet-ffi/src/handle.rs — concurrency and handle lifecycle (AtomicU64 + locks).
  • packages/rs-platform-wallet/src/managed_identity/contact_requests.rs and contact auto-establishment — correctness of reciprocal matching and state mutation.
  • packages/rs-sdk/src/platform/dashpay/* and packages/rs-sdk-ffi/* — API surface, async flows, marshaling of results and error semantics.
  • Swift wrappers — correct mapping of FFI errors to Swift errors and proper freeing of returned resources.

"I nibble at code with tiny paws,
ECDH seeds and IVs in my claws.
Contacts hop, requests delight,
Handles stored safe through the night.
A rabbit cheers—encryption done, off we pause! 🐇"

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: platform wallet' is concise and directly related to the main change—a comprehensive new platform wallet implementation with FFI bindings, cryptographic utilities, contact management, and Swift SDK integration.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/platformwallet

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

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🧹 Nitpick comments (13)
packages/rs-platform-wallet-ffi/README.md (4)

49-98: Add a contact request workflow example to demonstrate the advertised contact system.

The example demonstrates wallet and identity manager setup but omits the contact system (sent/incoming/established contacts), which is highlighted as a major feature on line 13. Adding a concrete workflow—e.g., sending a contact request, querying incoming requests, establishing a contact—would significantly improve practical utility for developers integrating the contact system.

Extend the example to include contact request operations:

     // Set identity manager for testnet
     result = platform_wallet_info_set_identity_manager(
         wallet_handle,
         NETWORK_TYPE_TESTNET,
         manager_handle,
         &error
     );

+    // Create and add a managed identity
+    Handle identity_handle = NULL_HANDLE;
+    result = managed_identity_create_from_identity_bytes(
+        identity_bytes,
+        &identity_handle,
+        &error
+    );
+
+    if (result != PLATFORM_WALLET_FFI_SUCCESS) {
+        printf("Error: %s\n", error.message);
+        platform_wallet_ffi_error_free(error);
+        identity_manager_destroy(manager_handle);
+        platform_wallet_info_destroy(wallet_handle);
+        return 1;
+    }
+
+    result = identity_manager_add_identity(manager_handle, identity_handle, &error);
+
+    // Send a contact request
+    result = managed_identity_send_contact_request(identity_handle, recipient_id, &error);
+
+    // Query sent contact requests
+    Handle request_ids_handle = NULL_HANDLE;
+    result = managed_identity_get_sent_contact_request_ids(identity_handle, &request_ids_handle, &error);

     // Cleanup
+    managed_identity_destroy(identity_handle);
     identity_manager_destroy(manager_handle);
     platform_wallet_info_destroy(wallet_handle);

101-154: Add parameter descriptions and signatures to the API overview.

The API overview lists function names but omits parameter types, return semantics, and descriptions (e.g., what parameters does platform_wallet_info_create_from_mnemonic() take? What does it return besides the status code?). Developers currently must inspect the header file to understand the interface, which reduces usability.

Enhance the API overview by including parameter and return information. For example:

 ### Wallet Management

- `platform_wallet_info_create_from_seed()` - Create wallet from seed bytes
- `platform_wallet_info_create_from_mnemonic()` - Create wallet from BIP39 mnemonic
+ `platform_wallet_info_create_from_seed(seed_bytes, seed_len, handle, error)` - Create wallet from seed bytes. Returns Handle via out-parameter; error details on failure.
+ `platform_wallet_info_create_from_mnemonic(mnemonic, passphrase, handle, error)` - Create wallet from BIP39 mnemonic (24 or 12 words). Optional passphrase for security.

18-37: Document header file location and cbindgen build process.

The README explains how to build the library but does not mention how to obtain or generate the platform_wallet_ffi.h header file referenced on line 44. Given that this is an FFI crate using cbindgen (as noted in the AI summary), clarify whether the header is pre-committed or generated, and provide instructions.

Add a subsection after the "Building" section:

 The dynamic library will be available at:
 - Linux: `target/release/libplatform_wallet_ffi.so`
 - macOS: `target/release/libplatform_wallet_ffi.dylib`
 - Windows: `target/release/platform_wallet_ffi.dll`

+### Generating C/C++ Header
+
+The C header file is generated using cbindgen during the build process:
+
+```bash
+cbindgen --config cbindgen.toml -o platform_wallet_ffi.h
+```
+
+Or include it directly from the generated artifact:
+
+```
+target/release/platform_wallet_ffi.h
+```

192-194: Clarify thread safety constraints and consequences.

Line 194 advises "you should not use the same handle from multiple threads simultaneously" but does not explain what happens if violated (e.g., data race, panic, undefined behavior). This incomplete guidance may leave developers uncertain about severity and mitigation.

Expand the thread safety note with specifics:

 ## Thread Safety

 The library uses thread-safe storage for handles, making it safe to use from multiple threads. However, you should not use the same handle from multiple threads simultaneously.
+
+**Important**: Concurrent access to the same handle without synchronization will result in undefined behavior and potential data corruption. If you need shared access, use external synchronization (e.g., mutexes) or clone/copy the underlying data before passing to different threads.
packages/rs-platform-wallet/src/platform_wallet_info/wallet_transaction_checker.rs (1)

8-8: Minor documentation inconsistency.

The comment references "ManagedWalletInfo" but the actual field being delegated to is wallet_info.

Apply this diff to fix the comment:

-/// Implement WalletTransactionChecker by delegating to ManagedWalletInfo
+/// Implement WalletTransactionChecker by delegating to wallet_info
packages/rs-platform-wallet/src/block_time.rs (1)

35-38: Consider clarifying age threshold semantics.

The is_older_than method uses strict inequality (>) rather than >=, meaning a block exactly at the threshold age is considered "not older than". This is internally consistent with the tests, but the method name might suggest inclusive behavior to some users.

Consider enhancing the documentation to clarify this behavior:

-    /// Check if this block time is older than a given age in milliseconds
+    /// Check if this block time is older than a given age in milliseconds
+    /// Returns true only if the age strictly exceeds max_age_millis
     pub fn is_older_than(&self, current_timestamp: TimestampMillis, max_age_millis: u64) -> bool {
         (current_timestamp - self.timestamp) > max_age_millis
     }
packages/rs-platform-wallet/src/contact_request.rs (1)

43-68: LGTM with optional refactor suggestion.

The ContactRequest constructor correctly initializes all fields. The #[allow(clippy::too_many_arguments)] is justified for this data-transfer struct.

For future consideration, a builder pattern could improve ergonomics:

impl ContactRequest {
    pub fn builder(sender_id: Identifier, recipient_id: Identifier) -> ContactRequestBuilder {
        ContactRequestBuilder::new(sender_id, recipient_id)
    }
}

However, this is purely optional and the current implementation is perfectly acceptable for FFI-facing data structures.

packages/swift-sdk/SwiftTests/Tests/SwiftDashSDKTests/ContactRequestTests.swift (1)

196-209: Assert deterministic behaviour for empty encrypted keys.

Line 196 currently lets the test pass whether ContactRequest.create succeeds or throws, so regressions around empty-key validation will go unnoticed. Please make the expectation explicit—e.g., assert that the call throws PlatformWalletError if empty keys must be rejected (or assert success if that’s the contract). Tightening the assertion keeps the test meaningful.

-        do {
-            let contactRequest = try ContactRequest.create(
-                senderId: senderId,
-                recipientId: recipientId,
-                senderKeyIndex: 0,
-                recipientKeyIndex: 0,
-                accountReference: 0,
-                encryptedPublicKey: emptyKey,
-                createdAt: UInt64(Date().timeIntervalSince1970 * 1000)
-            )
-            XCTAssertNotNil(contactRequest)
-        } catch {
-            // Expected to fail - empty key should not be allowed
-            XCTAssertTrue(error is PlatformWalletError)
-        }
+        XCTAssertThrowsError(
+            try ContactRequest.create(
+                senderId: senderId,
+                recipientId: recipientId,
+                senderKeyIndex: 0,
+                recipientKeyIndex: 0,
+                accountReference: 0,
+                encryptedPublicKey: emptyKey,
+                createdAt: UInt64(Date().timeIntervalSince1970 * 1000)
+            )
+        ) { error in
+            XCTAssertTrue(error is PlatformWalletError)
+        }
packages/rs-platform-wallet-ffi/tests/integration_tests.rs (1)

80-113: Destroy managed identity handles during cleanup.

Line 80 and Line 286 insert identities into MANAGED_IDENTITY_STORAGE, but the corresponding handles never get released. The entries stick around for the lifetime of the test process, so repeated runs (especially with --ignored) can accumulate leaked handles and hide lifecycle bugs. Please call managed_identity_destroy(identity_handle) once the manager has cloned the identity.

     platform_wallet_identifier_array_free(array);

     // Cleanup
+    managed_identity_destroy(identity_handle);
     identity_manager_destroy(manager_handle);
 }
@@
     // Cleanup
+    managed_identity_destroy(identity_handle);
     identity_manager_destroy(retrieved_manager_handle);
     identity_manager_destroy(manager_handle);
     platform_wallet_info_destroy(wallet_handle);

Also applies to: 286-317

packages/rs-platform-wallet-ffi/IMPLEMENTATION_SUMMARY.md (2)

119-129: Add language identifier to code block for better rendering.

The dependency chain diagram would benefit from a language identifier (e.g., text or mermaid) for proper syntax highlighting and rendering.

Apply this diff:

-```
+```text
 rs-sdk (library)
     ↑
 platform-wallet (optional dependency on rs-sdk)

406-414: Consider using proper heading syntax.

The "Total:" line uses emphasis (**Total:**) instead of a heading, which may affect document structure and navigation.

Consider changing to a proper heading if it represents a section:

-**Total: ~3,620 lines of production-quality code**
+### Total
+
+~3,620 lines of production-quality code
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Services/DashPayService.swift (1)

277-290: Use a stable identifier for SwiftUI diffing

Because id is a fresh UUID() each time we build a DashPayContactRequest, SwiftUI treats every refresh as brand-new data and will blow away row state/animations. Please derive id from request data (e.g., sender/recipient IDs plus timestamp) so it stays stable across reloads:

-struct DashPayContactRequest: Identifiable {
-    let id: UUID = UUID()
+struct DashPayContactRequest: Identifiable {
+    let id: String
     let senderId: Identifier
     let recipientId: Identifier
     let createdAt: Date
     let encryptedPublicKey: Data

     init(from contactRequest: ContactRequest) throws {
         self.senderId = try contactRequest.getSenderId()
         self.recipientId = try contactRequest.getRecipientId()
         let timestamp = try contactRequest.getCreatedAt()
         self.createdAt = Date(timeIntervalSince1970: Double(timestamp) / 1000.0)
         self.encryptedPublicKey = try contactRequest.getEncryptedPublicKey()
+        self.id = [
+            senderId.hexString,
+            recipientId.hexString,
+            String(timestamp)
+        ].joined(separator: ":")
     }
 }
packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/PlatformWalletFFI.swift (1)

8-403: Consider adding doc comments for maintainability.

While FFI declarations are internal implementation details, brief doc comments explaining parameters and behavior would help future maintainers, especially for complex functions like managed_identity_send_contact_request that take many parameters.

Example for one function:

/// Creates a Platform Wallet from a BIP39 seed.
/// - Parameters:
///   - seed: 64-byte seed derived from mnemonic
///   - seed_len: Length of seed (must be 64)
///   - out_handle: Output handle to created wallet
///   - out_error: Output error if creation fails
/// - Returns: Success or error result code
@_silgen_name("platform_wallet_info_create_from_seed")
func platform_wallet_info_create_from_seed(
    _ seed: UnsafePointer<UInt8>?,
    _ seed_len: Int,
    _ out_handle: UnsafeMutablePointer<Handle>,
    _ out_error: UnsafeMutablePointer<PlatformWalletFFIError>
) -> PlatformWalletFFIResult

This is entirely optional since the public API surface should be documented instead.

Comment on lines +449 to +575
/**
* Add a sent contact request
* @param identity_handle Identity handle
* @param contact_id Contact identity ID
* @param label Optional label (can be NULL)
* @param timestamp Request timestamp
* @param out_error Optional error output
* @return Result code
*/
PlatformWalletFFIResult managed_identity_add_sent_contact_request(
Handle identity_handle,
IdentifierBytes contact_id,
const char* label,
uint64_t timestamp,
PlatformWalletFFIError* out_error
);

/**
* Add an incoming contact request
* @param identity_handle Identity handle
* @param contact_id Contact identity ID
* @param label Optional label (can be NULL)
* @param timestamp Request timestamp
* @param out_error Optional error output
* @return Result code
*/
PlatformWalletFFIResult managed_identity_add_incoming_contact_request(
Handle identity_handle,
IdentifierBytes contact_id,
const char* label,
uint64_t timestamp,
PlatformWalletFFIError* out_error
);

/**
* Remove a sent contact request
* @param identity_handle Identity handle
* @param contact_id Contact identity ID
* @param out_error Optional error output
* @return Result code
*/
PlatformWalletFFIResult managed_identity_remove_sent_contact_request(
Handle identity_handle,
IdentifierBytes contact_id,
PlatformWalletFFIError* out_error
);

/**
* Remove an incoming contact request
* @param identity_handle Identity handle
* @param contact_id Contact identity ID
* @param out_error Optional error output
* @return Result code
*/
PlatformWalletFFIResult managed_identity_remove_incoming_contact_request(
Handle identity_handle,
IdentifierBytes contact_id,
PlatformWalletFFIError* out_error
);

/**
* Get all sent contact request IDs
* @param identity_handle Identity handle
* @param out_array Output array (caller must free with platform_wallet_identifier_array_free)
* @param out_error Optional error output
* @return Result code
*/
PlatformWalletFFIResult managed_identity_get_sent_contact_request_ids(
Handle identity_handle,
IdentifierArray* out_array,
PlatformWalletFFIError* out_error
);

/**
* Get all incoming contact request IDs
* @param identity_handle Identity handle
* @param out_array Output array (caller must free with platform_wallet_identifier_array_free)
* @param out_error Optional error output
* @return Result code
*/
PlatformWalletFFIResult managed_identity_get_incoming_contact_request_ids(
Handle identity_handle,
IdentifierArray* out_array,
PlatformWalletFFIError* out_error
);

/**
* Get all established contact IDs
* @param identity_handle Identity handle
* @param out_array Output array (caller must free with platform_wallet_identifier_array_free)
* @param out_error Optional error output
* @return Result code
*/
PlatformWalletFFIResult managed_identity_get_established_contact_ids(
Handle identity_handle,
IdentifierArray* out_array,
PlatformWalletFFIError* out_error
);

/**
* Check if a contact is established
* @param identity_handle Identity handle
* @param contact_id Contact identity ID
* @param out_is_established Output boolean
* @param out_error Optional error output
* @return Result code
*/
PlatformWalletFFIResult managed_identity_is_contact_established(
Handle identity_handle,
IdentifierBytes contact_id,
bool* out_is_established,
PlatformWalletFFIError* out_error
);

/**
* Remove an established contact
* @param identity_handle Identity handle
* @param contact_id Contact identity ID
* @param out_error Optional error output
* @return Result code
*/
PlatformWalletFFIResult managed_identity_remove_established_contact(
Handle identity_handle,
IdentifierBytes contact_id,
PlatformWalletFFIError* out_error
);

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Header/implementation mismatch for contact APIs.

The C header still declares the old managed_identity_add/remove_* functions and omits the new managed_identity_send_contact_request, managed_identity_accept_contact_request, managed_identity_reject_contact_request, managed_identity_get_sent_contact_request, managed_identity_get_incoming_contact_request, managed_identity_get_established_contact, plus every contact_request_* and established_contact_* entry point we now export from Rust. A C client compiling against this header will either fail to link (symbols not found) or have no way to call the new APIs. Please update the header so its prototypes exactly mirror the extern "C" surface we re-export in Rust.

🤖 Prompt for AI Agents
packages/rs-platform-wallet-ffi/platform_wallet_ffi.h lines 449-575: the header
currently declares old managed_identity_add/remove_* contact functions and is
missing the new exported Rust APIs, causing a header/implementation mismatch;
update this section so the C prototypes exactly match the Rust extern "C"
exports — remove or replace the old managed_identity_add/remove_* declarations
with the new managed_identity_send_contact_request,
managed_identity_accept_contact_request,
managed_identity_reject_contact_request,
managed_identity_get_sent_contact_request,
managed_identity_get_incoming_contact_request,
managed_identity_get_established_contact declarations, and add all
contact_request_* and established_contact_* entrypoint prototypes exported from
Rust, ensuring parameter types, names, return type (PlatformWalletFFIResult) and
error pointer (PlatformWalletFFIError*) signatures match the Rust definitions
exactly and include the same comments and caller-owned/free semantics for any
returned arrays.

Comment on lines +164 to +176
## Error Handling

All functions return a `PlatformWalletFFIResult` status code. Check for `PLATFORM_WALLET_FFI_SUCCESS` and handle errors appropriately.

Error codes:
- `PLATFORM_WALLET_FFI_SUCCESS` - Operation succeeded
- `PLATFORM_WALLET_FFI_ERROR_INVALID_HANDLE` - Invalid handle provided
- `PLATFORM_WALLET_FFI_ERROR_NULL_POINTER` - Null pointer provided
- `PLATFORM_WALLET_FFI_ERROR_SERIALIZATION` - Serialization failed
- `PLATFORM_WALLET_FFI_ERROR_DESERIALIZATION` - Deserialization failed
- `PLATFORM_WALLET_FFI_ERROR_IDENTITY_NOT_FOUND` - Identity not found
- `PLATFORM_WALLET_FFI_ERROR_CONTACT_NOT_FOUND` - Contact not found
- And more...
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Incomplete error codes documentation — finish the error enumeration.

Line 176 uses "And more..." which leaves developers guessing about other error codes they should handle. Since error handling is critical for FFI robustness, provide an exhaustive list or explicitly reference where the complete enum is defined (e.g., "See platform_wallet_ffi.h for the full PlatformWalletFFIError enum").

Consider replacing the vague ellipsis:

- And more...
+ See `platform_wallet_ffi.h` for the complete error code enumeration.
📝 Committable suggestion

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

Suggested change
## Error Handling
All functions return a `PlatformWalletFFIResult` status code. Check for `PLATFORM_WALLET_FFI_SUCCESS` and handle errors appropriately.
Error codes:
- `PLATFORM_WALLET_FFI_SUCCESS` - Operation succeeded
- `PLATFORM_WALLET_FFI_ERROR_INVALID_HANDLE` - Invalid handle provided
- `PLATFORM_WALLET_FFI_ERROR_NULL_POINTER` - Null pointer provided
- `PLATFORM_WALLET_FFI_ERROR_SERIALIZATION` - Serialization failed
- `PLATFORM_WALLET_FFI_ERROR_DESERIALIZATION` - Deserialization failed
- `PLATFORM_WALLET_FFI_ERROR_IDENTITY_NOT_FOUND` - Identity not found
- `PLATFORM_WALLET_FFI_ERROR_CONTACT_NOT_FOUND` - Contact not found
- And more...
## Error Handling
All functions return a `PlatformWalletFFIResult` status code. Check for `PLATFORM_WALLET_FFI_SUCCESS` and handle errors appropriately.
Error codes:
- `PLATFORM_WALLET_FFI_SUCCESS` - Operation succeeded
- `PLATFORM_WALLET_FFI_ERROR_INVALID_HANDLE` - Invalid handle provided
- `PLATFORM_WALLET_FFI_ERROR_NULL_POINTER` - Null pointer provided
- `PLATFORM_WALLET_FFI_ERROR_SERIALIZATION` - Serialization failed
- `PLATFORM_WALLET_FFI_ERROR_DESERIALIZATION` - Deserialization failed
- `PLATFORM_WALLET_FFI_ERROR_IDENTITY_NOT_FOUND` - Identity not found
- `PLATFORM_WALLET_FFI_ERROR_CONTACT_NOT_FOUND` - Contact not found
- See `platform_wallet_ffi.h` for the complete error code enumeration.
🤖 Prompt for AI Agents
In packages/rs-platform-wallet-ffi/README.md around lines 164 to 176, the error
codes section ends with "And more..." which is incomplete; update the
documentation to either enumerate all PlatformWalletFFI error codes or point
readers to the definitive source. Fix by replacing the ellipsis with either a
full list of the remaining error constants from the FFI (copy the names and
short descriptions from the codebase) or add an explicit reference such as "See
platform_wallet_ffi.h for the full PlatformWalletFFIError enum" with a relative
path link; ensure the wording makes it clear where to find the canonical enum
and remove the vague phrasing.

Comment on lines +44 to +76
let mut seed_array = [0u8; 64];
seed_array.copy_from_slice(seed_slice);

// Create wallet from seed - use empty network list, accounts can be added later
let wallet = match key_wallet::Wallet::from_seed_bytes(
seed_array,
&[], // No networks initially
WalletAccountCreationOptions::None, // No accounts initially
) {
Ok(w) => w,
Err(e) => {
if !out_error.is_null() {
unsafe {
*out_error = PlatformWalletFFIError::new(
PlatformWalletFFIResult::ErrorWalletOperation,
format!("Failed to create wallet from seed: {}", e),
);
}
}
return PlatformWalletFFIResult::ErrorWalletOperation;
}
};

// Create ManagedWalletInfo from the wallet
let wallet_info = key_wallet::wallet::ManagedWalletInfo::from_wallet(&wallet);

// Create PlatformWalletInfo wrapping the ManagedWalletInfo
let platform_wallet = PlatformWalletInfo {
wallet_info,
identity_managers: std::collections::BTreeMap::new(),
};

// Store in handle storage
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Zeroize the seed buffer before returning

We copy the wallet seed into seed_array on Line 44 but never wipe it. That leaves 64 bytes of highly sensitive material on the stack after this FFI returns, which is a security posture gap for wallet code. Please clear the local copy regardless of success or failure before exiting the function.

Apply this diff to zeroize the local seed:

-    let wallet = match key_wallet::Wallet::from_seed_bytes(
-        seed_array,
-        &[],                                // No networks initially
-        WalletAccountCreationOptions::None, // No accounts initially
-    ) {
+    let wallet_result = key_wallet::Wallet::from_seed_bytes(
+        seed_array,
+        &[],                                // No networks initially
+        WalletAccountCreationOptions::None, // No accounts initially
+    );
+    seed_array.fill(0);
+    let wallet = match wallet_result {
         Ok(w) => w,
         Err(e) => {
             if !out_error.is_null() {
                 unsafe {
                     *out_error = PlatformWalletFFIError::new(
📝 Committable suggestion

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

Suggested change
let mut seed_array = [0u8; 64];
seed_array.copy_from_slice(seed_slice);
// Create wallet from seed - use empty network list, accounts can be added later
let wallet = match key_wallet::Wallet::from_seed_bytes(
seed_array,
&[], // No networks initially
WalletAccountCreationOptions::None, // No accounts initially
) {
Ok(w) => w,
Err(e) => {
if !out_error.is_null() {
unsafe {
*out_error = PlatformWalletFFIError::new(
PlatformWalletFFIResult::ErrorWalletOperation,
format!("Failed to create wallet from seed: {}", e),
);
}
}
return PlatformWalletFFIResult::ErrorWalletOperation;
}
};
// Create ManagedWalletInfo from the wallet
let wallet_info = key_wallet::wallet::ManagedWalletInfo::from_wallet(&wallet);
// Create PlatformWalletInfo wrapping the ManagedWalletInfo
let platform_wallet = PlatformWalletInfo {
wallet_info,
identity_managers: std::collections::BTreeMap::new(),
};
// Store in handle storage
let mut seed_array = [0u8; 64];
seed_array.copy_from_slice(seed_slice);
// Create wallet from seed - use empty network list, accounts can be added later
let wallet_result = key_wallet::Wallet::from_seed_bytes(
seed_array,
&[], // No networks initially
WalletAccountCreationOptions::None, // No accounts initially
);
seed_array.fill(0);
let wallet = match wallet_result {
Ok(w) => w,
Err(e) => {
if !out_error.is_null() {
unsafe {
*out_error = PlatformWalletFFIError::new(
PlatformWalletFFIResult::ErrorWalletOperation,
format!("Failed to create wallet from seed: {}", e),
);
}
}
return PlatformWalletFFIResult::ErrorWalletOperation;
}
};
// Create ManagedWalletInfo from the wallet
let wallet_info = key_wallet::wallet::ManagedWalletInfo::from_wallet(&wallet);
// Create PlatformWalletInfo wrapping the ManagedWalletInfo
let platform_wallet = PlatformWalletInfo {
wallet_info,
identity_managers: std::collections::BTreeMap::new(),
};
// Store in handle storage
🤖 Prompt for AI Agents
In packages/rs-platform-wallet-ffi/src/platform_wallet_info.rs around lines 44
to 76, the local seed_array (64 bytes) is left in memory after returning; ensure
the seed buffer is zeroized on all exit paths. After creating the wallet (or
immediately before every return on error), overwrite or zeroize seed_array
(e.g., call the Zeroize trait or fill the array with zeros) so the sensitive
bytes are cleared; place the zeroization both in the error branch before
returning and after successful use (before continuing/returning) to guarantee no
residual secret remains on the stack.

Comment on lines +71 to +178
// Derive the first authentication key (identity_index 0, key_index 0)
let identity_index = 0u32;
let key_index = 0u32;

// Build identity authentication derivation path
// Path format: m/9'/COIN_TYPE'/5'/0'/identity_index'/key_index'
use key_wallet::bip32::{ChildNumber, DerivationPath};
use key_wallet::dip9::{
IDENTITY_AUTHENTICATION_PATH_MAINNET, IDENTITY_AUTHENTICATION_PATH_TESTNET,
};

let base_path = match network {
Network::Dash => IDENTITY_AUTHENTICATION_PATH_MAINNET,
Network::Testnet => IDENTITY_AUTHENTICATION_PATH_TESTNET,
_ => {
return Err(PlatformWalletError::InvalidIdentityData(
"Unsupported network for identity derivation".to_string(),
));
}
};

// Create full derivation path: base path + identity_index' + key_index'
let mut full_path = DerivationPath::from(base_path);
full_path = full_path.extend([
ChildNumber::from_hardened_idx(identity_index).map_err(|e| {
PlatformWalletError::InvalidIdentityData(format!("Invalid identity index: {}", e))
})?,
ChildNumber::from_hardened_idx(key_index).map_err(|e| {
PlatformWalletError::InvalidIdentityData(format!("Invalid key index: {}", e))
})?,
]);

// Derive the extended private key at this path
let auth_key = wallet
.derive_extended_private_key(network, &full_path)
.map_err(|e| {
PlatformWalletError::InvalidIdentityData(format!(
"Failed to derive authentication key: {}",
e
))
})?;

// Get public key bytes and hash them
use dashcore::secp256k1::Secp256k1;
use key_wallet::bip32::ExtendedPubKey;
let secp = Secp256k1::new();
let public_key = ExtendedPubKey::from_priv(&secp, &auth_key);
let public_key_bytes = public_key.public_key.serialize();
let key_hash = ripemd160_sha256(&public_key_bytes);

// Create a fixed-size array from the hash
let mut key_hash_array = [0u8; 20];
key_hash_array.copy_from_slice(&key_hash);

// Query Platform for identity by public key hash
match dpp::identity::Identity::fetch(&sdk, PublicKeyHash(key_hash_array)).await {
Ok(Some(identity)) => {
let identity_id = identity.id();

// Add identity to manager if not already present
if !self
.identity_manager(network)
.map(|mgr| mgr.identities().contains_key(&identity_id))
.unwrap_or(false)
{
self.identity_manager_mut(network)
.add_identity(identity.clone())?;
}

// Fetch DashPay contact requests for this identity
match sdk
.fetch_all_contact_requests_for_identity(&identity, Some(100))
.await
{
Ok((sent_docs, received_docs)) => {
// Process sent contact requests
for (_doc_id, maybe_doc) in sent_docs {
if let Some(doc) = maybe_doc {
if let Ok(contact_request) = parse_contact_request_document(&doc) {
// Add to managed identity
if let Some(managed_identity) = self
.identity_manager_mut(network)
.managed_identity_mut(&identity_id)
{
managed_identity.add_sent_contact_request(contact_request);
}
}
}
}

// Process received contact requests
for (_doc_id, maybe_doc) in received_docs {
if let Some(doc) = maybe_doc {
if let Ok(contact_request) = parse_contact_request_document(&doc) {
// Add to managed identity
if let Some(managed_identity) = self
.identity_manager_mut(network)
.managed_identity_mut(&identity_id)
{
managed_identity
.add_incoming_contact_request(contact_request);
}
}
}
}

identities_processed.push(identity_id);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Process each matured identity instead of hardcoding index 0

Right now we only check that some matured asset lock exists and then derive identity m/…/0'/0'. Any matured transaction that registered an identity on a different derivation index will never be discovered, so their DashPay contact requests remain unfetched. ImmatureTransaction already carries the derivation metadata we recorded when tracking these asset locks—please iterate the incoming matured_transactions, pull the identity/key indices from each asset lock, and derive the corresponding key hash per item instead of assuming 0/0. Otherwise the wallet breaks as soon as a second identity matures.

Comment on lines +229 to +233
if recipient_key.purpose() != Purpose::DECRYPTION {
return Err(Error::Generic(format!(
"Recipient key at index {} is not a decryption key",
input.recipient_key_index
)));
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Recipient key purpose check rejects valid identities

DashPay identities expose their ECDH keys with Purpose::ENCRYPTION (see existing wallet code in platform_wallet_info/contact_requests.rs). Requiring Purpose::DECRYPTION makes every legitimate recipient fail this guard, so create_contact_request will always error out. Please align the check with the encryption purpose.

-        if recipient_key.purpose() != Purpose::DECRYPTION {
+        if recipient_key.purpose() != Purpose::ENCRYPTION {
📝 Committable suggestion

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

Suggested change
if recipient_key.purpose() != Purpose::DECRYPTION {
return Err(Error::Generic(format!(
"Recipient key at index {} is not a decryption key",
input.recipient_key_index
)));
if recipient_key.purpose() != Purpose::ENCRYPTION {
return Err(Error::Generic(format!(
"Recipient key at index {} is not a decryption key",
input.recipient_key_index
)));
🤖 Prompt for AI Agents
In packages/rs-sdk/src/platform/dashpay/contact_request.rs around lines 229-233,
the recipient key purpose check currently requires Purpose::DECRYPTION which
rejects valid DashPay identities that expose ECDH keys as Purpose::ENCRYPTION;
change the guard to check for Purpose::ENCRYPTION (or accept both ENCRYPTION and
DECRYPTION if broader compatibility is desired) and update the returned error
message to reflect the expected purpose so legitimate recipients no longer fail
this check.

Comment on lines +17 to +51
public static func create(
senderId: Identifier,
recipientId: Identifier,
senderKeyIndex: UInt32,
recipientKeyIndex: UInt32,
accountReference: UInt32,
encryptedPublicKey: Data,
createdAt: UInt64
) throws -> ContactRequest {
var handle: Handle = NULL_HANDLE
var error = PlatformWalletFFIError()
var ffiSenderId = identifierToFFI(senderId)
var ffiRecipientId = identifierToFFI(recipientId)

let result = encryptedPublicKey.withUnsafeBytes { keyPtr in
contact_request_create(
ffiSenderId,
ffiRecipientId,
senderKeyIndex,
recipientKeyIndex,
accountReference,
keyPtr.baseAddress?.assumingMemoryBound(to: UInt8.self),
encryptedPublicKey.count,
createdAt,
&handle,
&error
)
}

guard result == Success else {
throw PlatformWalletError(result: result, error: error)
}

return ContactRequest(handle: handle)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Pass coreHeightCreatedAt into the FFI create call

contact_request_create expects both the Core height and the creation timestamp. We currently skip the height, so the timestamp argument shifts into the height slot and the handle pointer shifts into the timestamp slot, corrupting the call. Please thread coreHeightCreatedAt through the API and pass it along to the FFI layer.

-    public static func create(
+    public static func create(
         senderId: Identifier,
         recipientId: Identifier,
         senderKeyIndex: UInt32,
         recipientKeyIndex: UInt32,
         accountReference: UInt32,
         encryptedPublicKey: Data,
+        coreHeightCreatedAt: UInt32,
         createdAt: UInt64
     ) throws -> ContactRequest {
         var handle: Handle = NULL_HANDLE
         var error = PlatformWalletFFIError()
         var ffiSenderId = identifierToFFI(senderId)
         var ffiRecipientId = identifierToFFI(recipientId)

         let result = encryptedPublicKey.withUnsafeBytes { keyPtr in
             contact_request_create(
                 ffiSenderId,
                 ffiRecipientId,
                 senderKeyIndex,
                 recipientKeyIndex,
                 accountReference,
                 keyPtr.baseAddress?.assumingMemoryBound(to: UInt8.self),
                 encryptedPublicKey.count,
+                coreHeightCreatedAt,
                 createdAt,
                 &handle,
                 &error
             )
         }
📝 Committable suggestion

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

Suggested change
public static func create(
senderId: Identifier,
recipientId: Identifier,
senderKeyIndex: UInt32,
recipientKeyIndex: UInt32,
accountReference: UInt32,
encryptedPublicKey: Data,
createdAt: UInt64
) throws -> ContactRequest {
var handle: Handle = NULL_HANDLE
var error = PlatformWalletFFIError()
var ffiSenderId = identifierToFFI(senderId)
var ffiRecipientId = identifierToFFI(recipientId)
let result = encryptedPublicKey.withUnsafeBytes { keyPtr in
contact_request_create(
ffiSenderId,
ffiRecipientId,
senderKeyIndex,
recipientKeyIndex,
accountReference,
keyPtr.baseAddress?.assumingMemoryBound(to: UInt8.self),
encryptedPublicKey.count,
createdAt,
&handle,
&error
)
}
guard result == Success else {
throw PlatformWalletError(result: result, error: error)
}
return ContactRequest(handle: handle)
}
public static func create(
senderId: Identifier,
recipientId: Identifier,
senderKeyIndex: UInt32,
recipientKeyIndex: UInt32,
accountReference: UInt32,
encryptedPublicKey: Data,
coreHeightCreatedAt: UInt32,
createdAt: UInt64
) throws -> ContactRequest {
var handle: Handle = NULL_HANDLE
var error = PlatformWalletFFIError()
var ffiSenderId = identifierToFFI(senderId)
var ffiRecipientId = identifierToFFI(recipientId)
let result = encryptedPublicKey.withUnsafeBytes { keyPtr in
contact_request_create(
ffiSenderId,
ffiRecipientId,
senderKeyIndex,
recipientKeyIndex,
accountReference,
keyPtr.baseAddress?.assumingMemoryBound(to: UInt8.self),
encryptedPublicKey.count,
coreHeightCreatedAt,
createdAt,
&handle,
&error
)
}
guard result == Success else {
throw PlatformWalletError(result: result, error: error)
}
return ContactRequest(handle: handle)
}

Comment on lines +120 to +133
var bytesPtr: UnsafeMutablePointer<UInt8>? = nil
var length: Int = 0
var error = PlatformWalletFFIError()

let result = contact_request_get_encrypted_public_key(handle, &bytesPtr, &length, &error)
guard result == Success else {
throw PlatformWalletError(result: result, error: error)
}

defer {
if let ptr = bytesPtr {
platform_wallet_bytes_free(ptr)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Free encrypted key buffer with the correct length

platform_wallet_bytes_free reconstructs a Vec using both the pointer and its length. Passing only the pointer leaves the length parameter uninitialised, causing undefined behaviour (or a compile error, depending on the header). Please provide the length we just received:

         defer {
             if let ptr = bytesPtr {
-                platform_wallet_bytes_free(ptr)
+                platform_wallet_bytes_free(ptr, length)
             }
         }
📝 Committable suggestion

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

Suggested change
var bytesPtr: UnsafeMutablePointer<UInt8>? = nil
var length: Int = 0
var error = PlatformWalletFFIError()
let result = contact_request_get_encrypted_public_key(handle, &bytesPtr, &length, &error)
guard result == Success else {
throw PlatformWalletError(result: result, error: error)
}
defer {
if let ptr = bytesPtr {
platform_wallet_bytes_free(ptr)
}
}
var bytesPtr: UnsafeMutablePointer<UInt8>? = nil
var length: Int = 0
var error = PlatformWalletFFIError()
let result = contact_request_get_encrypted_public_key(handle, &bytesPtr, &length, &error)
guard result == Success else {
throw PlatformWalletError(result: result, error: error)
}
defer {
if let ptr = bytesPtr {
platform_wallet_bytes_free(ptr, length)
}
}
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 120-120: Initializing an optional variable with nil is redundant

(redundant_optional_initialization)

🤖 Prompt for AI Agents
In packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/ContactRequest.swift
around lines 120 to 133, the defer block calls platform_wallet_bytes_free(ptr)
with only the pointer while the FFI expects both pointer and length; update the
defer to call platform_wallet_bytes_free(ptr, length) (passing the length
variable received from contact_request_get_encrypted_public_key) so the Vec is
reconstructed with the correct size before freeing.

Comment on lines +96 to +104
let result = identity_manager_get_primary_identity_id(handle, &ffiId, &error)

if result == ErrorIdentityNotFound {
return nil
}

guard result == Success else {
throw PlatformWalletError(result: result, error: error)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Free the not-found error to avoid leaking

When identity_manager_get_primary_identity_id returns ErrorIdentityNotFound, the FFI populates error with an allocated message. Because we early-return nil instead of throwing, that buffer is never released and leaks every time this path is hit. Please free the error before returning. For example:

         let result = identity_manager_get_primary_identity_id(handle, &ffiId, &error)

         if result == ErrorIdentityNotFound {
+            platform_wallet_error_free(error)
             return nil
         }
📝 Committable suggestion

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

Suggested change
let result = identity_manager_get_primary_identity_id(handle, &ffiId, &error)
if result == ErrorIdentityNotFound {
return nil
}
guard result == Success else {
throw PlatformWalletError(result: result, error: error)
}
let result = identity_manager_get_primary_identity_id(handle, &ffiId, &error)
if result == ErrorIdentityNotFound {
platform_wallet_error_free(error)
return nil
}
guard result == Success else {
throw PlatformWalletError(result: result, error: error)
}
🤖 Prompt for AI Agents
In packages/swift-sdk/Sources/SwiftDashSDK/PlatformWallet/IdentityManager.swift
around lines 96-104, when identity_manager_get_primary_identity_id returns
ErrorIdentityNotFound the FFI-populated error must be freed before returning
nil; call the appropriate FFI error-free function (for example the generated
free function for the error type, e.g. platform_wallet_error_free(error) or
similar) to release the allocated message buffer, then return nil (and nul the
pointer if required by the free function).

Comment on lines +332 to +341
/// Accept a contact request from another identity
public func acceptContactRequest(senderId: Identifier) throws {
var error = PlatformWalletFFIError()
var ffiSenderId = identifierToFFI(senderId)

let result = managed_identity_accept_contact_request(handle, ffiSenderId, &error)
guard result == Success else {
throw PlatformWalletError(result: result, error: error)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Pass the expected request handle into managed_identity_accept_contact_request.

The FFI function accepts (Handle identity_handle, Handle request_handle, PlatformWalletFFIError* out_error), but this code supplies an IdentifierBytes. As with sendContactRequest, the ABI mismatch breaks the call entirely. You need to hand the FFI a valid contact-request handle (e.g. obtained from managed_identity_get_incoming_contact_request) or update both sides of the interface together. citeturn0files1

Comment on lines +64 to +107
/// Get the identity manager for a specific network
public func getIdentityManager(for network: PlatformNetwork) throws -> IdentityManager {
// Check if we already have it cached
if let manager = identityManagers[network] {
return manager
}

var managerHandle: Handle = NULL_HANDLE
var error = PlatformWalletFFIError()

let result = platform_wallet_info_get_identity_manager(
handle,
network.ffiValue,
&managerHandle,
&error
)

guard result == Success else {
throw PlatformWalletError(result: result, error: error)
}

let manager = IdentityManager(handle: managerHandle)
identityManagers[network] = manager
return manager
}

/// Set the identity manager for a specific network
public func setIdentityManager(_ manager: IdentityManager, for network: PlatformNetwork) throws {
var error = PlatformWalletFFIError()

let result = platform_wallet_info_set_identity_manager(
handle,
network.ffiValue,
manager.handle,
&error
)

guard result == Success else {
throw PlatformWalletError(result: result, error: error)
}

identityManagers[network] = manager
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Create missing identity managers on demand

platform_wallet_info_get_identity_manager returns ErrorInvalidNetwork for a fresh wallet because the underlying identity_managers map is empty until something registers a manager via platform_wallet_info_set_identity_manager. With the current guard this method throws for every new wallet, so the integration suite (and real clients) can never obtain a manager. Please detect the invalid-network case, create a new IdentityManager, register it with setIdentityManager, and return it instead of bubbling up the error.

Apply this diff:

-        guard result == Success else {
-            throw PlatformWalletError(result: result, error: error)
-        }
-
-        let manager = IdentityManager(handle: managerHandle)
-        identityManagers[network] = manager
-        return manager
+        if result == Success {
+            let manager = IdentityManager(handle: managerHandle)
+            identityManagers[network] = manager
+            return manager
+        }
+
+        if result == ErrorInvalidNetwork {
+            let manager = try IdentityManager.create()
+            try setIdentityManager(manager, for: network)
+            return manager
+        }
+
+        throw PlatformWalletError(result: result, error: error)
📝 Committable suggestion

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

Suggested change
/// Get the identity manager for a specific network
public func getIdentityManager(for network: PlatformNetwork) throws -> IdentityManager {
// Check if we already have it cached
if let manager = identityManagers[network] {
return manager
}
var managerHandle: Handle = NULL_HANDLE
var error = PlatformWalletFFIError()
let result = platform_wallet_info_get_identity_manager(
handle,
network.ffiValue,
&managerHandle,
&error
)
guard result == Success else {
throw PlatformWalletError(result: result, error: error)
}
let manager = IdentityManager(handle: managerHandle)
identityManagers[network] = manager
return manager
}
/// Set the identity manager for a specific network
public func setIdentityManager(_ manager: IdentityManager, for network: PlatformNetwork) throws {
var error = PlatformWalletFFIError()
let result = platform_wallet_info_set_identity_manager(
handle,
network.ffiValue,
manager.handle,
&error
)
guard result == Success else {
throw PlatformWalletError(result: result, error: error)
}
identityManagers[network] = manager
}
}
/// Get the identity manager for a specific network
public func getIdentityManager(for network: PlatformNetwork) throws -> IdentityManager {
// Check if we already have it cached
if let manager = identityManagers[network] {
return manager
}
var managerHandle: Handle = NULL_HANDLE
var error = PlatformWalletFFIError()
let result = platform_wallet_info_get_identity_manager(
handle,
network.ffiValue,
&managerHandle,
&error
)
if result == Success {
let manager = IdentityManager(handle: managerHandle)
identityManagers[network] = manager
return manager
}
if result == ErrorInvalidNetwork {
let manager = try IdentityManager.create()
try setIdentityManager(manager, for: network)
return manager
}
throw PlatformWalletError(result: result, error: error)
}
/// Set the identity manager for a specific network
public func setIdentityManager(_ manager: IdentityManager, for network: PlatformNetwork) throws {
var error = PlatformWalletFFIError()
let result = platform_wallet_info_set_identity_manager(
handle,
network.ffiValue,
manager.handle,
&error
)
guard result == Success else {
throw PlatformWalletError(result: result, error: error)
}
identityManagers[network] = manager
}
}

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (1)
packages/rs-platform-wallet/src/platform_wallet_info/matured_transactions.rs (1)

86-239: asset_lock_transactions slice is effectively ignored; only identity 0/0 is ever discovered

This method currently:

  • Uses asset_lock_transactions only to check is_empty().
  • Always derives a single authentication key at identity_index = 0, key_index = 0.
  • Calls Identity::fetch exactly once and can push at most one identity_id into identities_processed.

Any matured asset lock that registered an identity on a different derivation index will never be discovered, so additional identities’ DashPay contact requests will not be fetched. ImmatureTransaction already carries the derivation metadata for each asset lock; you should iterate over asset_lock_transactions, pull the identity/key indices per item, derive the key hash for each, and query/add/fetch contacts per identity instead of assuming 0/0 globally.

This is the same functional gap that was raised in a previous review on this file and remains unresolved.

🧹 Nitpick comments (13)
packages/rs-platform-wallet/src/platform_wallet_info/wallet_info_interface.rs (1)

80-86: Clarify the misleading comment.

The comment says "Use the default trait implementation which filters utxos" but the code provides a custom filtering implementation. The filtering logic is correct, but the comment should accurately describe what the code does.

Apply this diff to clarify:

-    fn get_spendable_utxos(&self) -> BTreeSet<&Utxo> {
-        // Use the default trait implementation which filters utxos
-        self.utxos()
+    fn get_spendable_utxos(&self) -> BTreeSet<&Utxo> {
+        // Filter to spendable UTXOs: not locked and either confirmed or instantlocked
+        self.utxos()
             .into_iter()
             .filter(|utxo| !utxo.is_locked && (utxo.is_confirmed || utxo.is_instantlocked))
             .collect()
     }
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift (1)

325-325: Optional: Remove redundant nil initialization.

In Swift, optional variables are implicitly initialized to nil, so the explicit = nil is redundant.

Apply this diff:

-    var navigationDestination: AnyView? = nil
+    var navigationDestination: AnyView?
packages/swift-sdk/Sources/SwiftDashSDK/SPV/SPVClient.swift (1)

230-233: Consider adding a warning about handle usage in documentation.

Exposing the raw FFI handle is appropriate for filter match queries, but callers could misuse it by making arbitrary FFI calls that might violate memory safety or invalidate internal state.

Consider enhancing the documentation:

-    // Public accessor for client handle (needed for filter match queries)
+    /// Public accessor for the underlying FFI client handle.
+    ///
+    /// - Warning: This handle is provided for advanced use cases such as filter match queries.
+    ///   Do not use this handle to make FFI calls that modify client state, as this may
+    ///   invalidate the Swift wrapper's internal state or cause memory safety issues.
+    ///
+    /// - Returns: The underlying FFI client handle, or nil if not initialized
     public var clientHandle: UnsafeMutablePointer<FFIDashSpvClient>? {
         return client
     }
packages/rs-platform-wallet/src/established_contact.rs (1)

85-95: LGTM!

The account management methods correctly handle adding and removing accounts with proper duplicate prevention.

Optional: Consider using HashSet for accepted_accounts if scalability matters.

The current Vec<u32> implementation with contains() has O(n) lookup. If the list of accepted accounts could grow large, consider using a HashSet<u32> for O(1) operations:

pub accepted_accounts: std::collections::HashSet<u32>,

However, for typical contact scenarios with a small number of accounts, the current implementation is perfectly adequate.

packages/rs-platform-wallet/src/platform_wallet_info/wallet_transaction_checker.rs (1)

27-47: Consider flattening nested conditionals for better readability.

The nested if statements could be simplified using early continues or by combining conditions to reduce nesting depth.

Here's a more readable alternative:

         // If the transaction is relevant, and it's an asset lock, automatically fetch identities
-        if result.is_relevant {
-            use dashcore::transaction::special_transaction::TransactionPayload;
-
-            if matches!(
-                &tx.special_transaction_payload,
-                Some(TransactionPayload::AssetLockPayloadType(_))
-            ) {
-                // Check if we have an SDK configured for this network
-                if let Some(identity_manager) = self.identity_managers.get(&network) {
-                    if identity_manager.sdk.is_some() {
-                        // Call the identity fetching logic
-                        if let Err(e) = self
-                            .fetch_identity_and_contacts_for_asset_lock(wallet, network, tx)
-                            .await
-                        {
-                            eprintln!("Failed to fetch identity for asset lock: {}", e);
-                        }
-                    }
-                }
-            }
-        }
+        if result.is_relevant {
+            use dashcore::transaction::special_transaction::TransactionPayload;
+
+            if let Some(TransactionPayload::AssetLockPayloadType(_)) = &tx.special_transaction_payload {
+                if let Some(identity_manager) = self.identity_managers.get(&network) {
+                    if identity_manager.sdk.is_some() {
+                        if let Err(e) = self
+                            .fetch_identity_and_contacts_for_asset_lock(wallet, network, tx)
+                            .await
+                        {
+                            tracing::error!("Failed to fetch identity for asset lock: {}", e);
+                        }
+                    }
+                }
+            }
+        }

This reduces one level of nesting by using if let pattern matching instead of matches! followed by another if.

packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Services/WalletService.swift (2)

10-28: Consider guard statement instead of force unwrap.

Line 24 uses force unwrap (group.next()!) which is safe in the current implementation since two tasks are always added to the group. However, using a guard statement would make this safer against future refactoring mistakes and more explicit about the invariant.

Apply this diff:

-        // Return first result (either completion or timeout)
-        let result = try await group.next()!
-        group.cancelAll()
-        return result
+        // Return first result (either completion or timeout)
+        guard let result = try await group.next() else {
+            throw TimeoutError() // Should never happen since we added two tasks
+        }
+        group.cancelAll()
+        return result

581-602: Consider making timeout configurable.

The 30-second timeout for storage clear operations provides good protection against indefinite hangs. However, the optimal timeout may vary based on storage size and device performance.

Consider making the timeout configurable:

public func clearSpvStorage(fullReset: Bool = true, timeoutSeconds: TimeInterval = 30) {
    // ...
    try await withTimeout(seconds: timeoutSeconds) {
        // clear operations
    }
}

Alternatively, use different timeouts for full reset vs. sync state clear:

let timeout = fullReset ? 30.0 : 10.0
try await withTimeout(seconds: timeout) {
    // clear operations
}
packages/rs-platform-wallet/src/identity_manager/mod.rs (2)

18-27: Consider hiding IdentityManager fields behind accessors

You already have dedicated accessors (identity, identities, managed_identity, sdk, etc.), but identities, primary_identity_id, and sdk are all pub. That allows external callers to mutate internal state directly and bypass invariants (e.g., primary identity handling) and leads to direct field access like manager.sdk instead of the accessor. Consider making these fields private and using the accessor methods throughout (including in matured_transactions.rs) to keep the API surface tighter.


39-125: IdentityManager tests exercise core lifecycle well

The test module covers adding/removing identities, primary identity switching, and basic ManagedIdentity metadata, which is a good baseline and matches intended behavior. If needed later, you could add a duplicate‑identity test, but nothing is blocking here.

packages/rs-platform-wallet/src/platform_wallet_info/matured_transactions.rs (1)

242-328: ContactRequest document parsing is correct but drops optional fields

The parser defensively extracts toUserId, senderKeyIndex, recipientKeyIndex, accountReference, encryptedPublicKey, and timestamps with clear error messages, which aligns with the DashPay contact request schema and ContactRequest::new. However, any optional fields such as an encrypted account label or auto‑accept proof present on the document are currently discarded. If you plan to support those features in the wallet UI, consider extending ContactRequest (or adding a separate constructor) so this helper can preserve them instead of always defaulting to None.

packages/rs-platform-wallet/src/platform_wallet_info/contact_requests.rs (3)

29-147: add_sent_contact_request wiring and invariants look solid

This helper sensibly validates that the identity exists, enforces uniqueness of the DashPay receiving account at both wallet and managed‑wallet layers, derives and registers the account when missing, updates last_used, and then delegates to ManagedIdentity::add_sent_contact_request. The flow is coherent and keeps both representations in sync.

One small nit: send_contact_request also derives the same account path/xpub before calling this helper, so you’re doing the derivation work twice in that path. If you care about avoiding that duplication, consider plumbing the already‑derived xpub into this function.


323-471: Clarify behavior on local failure after a successful SDK send, and consider improving stored metadata

The wrapper around sdk.send_contact_request is nicely structured, but there’s a subtle behavior issue:

  • If the SDK call succeeds (the contact request is on Platform) and add_sent_contact_request then fails (e.g., wallet or managed-account write issues), the method returns an error even though the remote state was updated. Callers see it as a full failure, while the network state actually contains the request.

If this is acceptable, it may be worth documenting it as a “remote success, local persistence error”; otherwise, consider making the local step more robust or providing a way for callers to reconcile.

Separately, the locally stored ContactRequest uses a dummy 96‑byte zero vector for encrypted_public_key and a hard-coded core_height_created_at of 100000. Since the real encrypted xpub and core height are available from the SDK/document, you may want to persist those to keep the local representation closer to what’s on Platform.


492-828: Good error-path coverage in tests; add at least one happy-path scenario

The test module does a nice job covering:

  • Identity-not-found errors for sent/incoming requests.
  • Sender/recipient mismatches and missing/wrong-purpose keys.
  • Accepting requests when identity or contact doesn’t exist.

What’s missing is any positive-path coverage for:

  • add_sent_contact_request successfully creating accounts and updating metadata.
  • add_incoming_contact_request successfully wiring up an external account and adding the request.
  • The send_contact_request async wrapper (even with a mocked SDK).

Adding at least one success-case test per path would better guard the account-key wiring and would help catch issues like the encryptedPublicKey/xpub mismatch earlier.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b1ebc57 and 07fa830.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (12)
  • packages/rs-dapi/Cargo.toml (1 hunks)
  • packages/rs-platform-wallet/Cargo.toml (1 hunks)
  • packages/rs-platform-wallet/src/established_contact.rs (1 hunks)
  • packages/rs-platform-wallet/src/identity_manager/initializers.rs (1 hunks)
  • packages/rs-platform-wallet/src/identity_manager/mod.rs (1 hunks)
  • packages/rs-platform-wallet/src/platform_wallet_info/contact_requests.rs (1 hunks)
  • packages/rs-platform-wallet/src/platform_wallet_info/matured_transactions.rs (1 hunks)
  • packages/rs-platform-wallet/src/platform_wallet_info/wallet_info_interface.rs (1 hunks)
  • packages/rs-platform-wallet/src/platform_wallet_info/wallet_transaction_checker.rs (1 hunks)
  • packages/swift-sdk/Sources/SwiftDashSDK/SPV/SPVClient.swift (2 hunks)
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Services/WalletService.swift (8 hunks)
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/rs-platform-wallet/src/identity_manager/initializers.rs
🧰 Additional context used
📓 Path-based instructions (4)
packages/swift-sdk/**/*.swift

📄 CodeRabbit inference engine (CLAUDE.md)

Make DPP types public in Swift where needed for visibility

Files:

  • packages/swift-sdk/Sources/SwiftDashSDK/SPV/SPVClient.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Services/WalletService.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift
**/*.rs

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rs: Format Rust code with cargo fmt
Run Clippy linter for Rust code

Files:

  • packages/rs-platform-wallet/src/platform_wallet_info/wallet_transaction_checker.rs
  • packages/rs-platform-wallet/src/established_contact.rs
  • packages/rs-platform-wallet/src/platform_wallet_info/matured_transactions.rs
  • packages/rs-platform-wallet/src/platform_wallet_info/wallet_info_interface.rs
  • packages/rs-platform-wallet/src/platform_wallet_info/contact_requests.rs
  • packages/rs-platform-wallet/src/identity_manager/mod.rs
packages/swift-sdk/SwiftExampleApp/**/*.swift

📄 CodeRabbit inference engine (packages/swift-sdk/SwiftExampleApp/CLAUDE.md)

packages/swift-sdk/SwiftExampleApp/**/*.swift: Use Core SDK functions with the dash_core_sdk_* prefix
Use Platform SDK functions with the dash_sdk_* prefix
Use Unified SDK functions with the dash_unified_sdk_* prefix
Prefer using PersistentToken predicate helpers (e.g., mintableTokensPredicate, tokensWithControlRulePredicate) instead of manual filtering

Files:

  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Services/WalletService.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift
packages/swift-sdk/SwiftExampleApp/**/*View.swift

📄 CodeRabbit inference engine (packages/swift-sdk/SwiftExampleApp/CLAUDE.md)

packages/swift-sdk/SwiftExampleApp/**/*View.swift: Use SwiftUI with @query for reactive data in views
Break complex SwiftUI views into smaller components to avoid compiler timeouts
Use NavigationLink for drill-down navigation in the UI
Implement proper loading and error states in UI screens

Files:

  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift
🧠 Learnings (23)
📓 Common learnings
Learnt from: shumkov
Repo: dashpay/platform PR: 2489
File: packages/rs-dpp/Cargo.toml:32-32
Timestamp: 2025-03-11T09:39:23.071Z
Learning: In the Dash Platform project, dependencies are currently managed using Git repository references with tags (repo+tag format in Cargo.toml) rather than published crates, as the team is not currently publishing crates to crates.io.
Learnt from: lklimek
Repo: dashpay/platform PR: 2254
File: packages/rs-sdk/src/sdk.rs:585-585
Timestamp: 2024-10-18T15:39:51.172Z
Learning: The 'platform' project uses Rust version 1.80, so code in 'packages/rs-sdk' can use features available in Rust 1.80, such as the `abs_diff()` method.
Learnt from: CR
Repo: dashpay/platform PR: 0
File: packages/swift-sdk/SwiftExampleApp/CLAUDE.md:0-0
Timestamp: 2025-09-07T22:19:59.217Z
Learning: Applies to packages/swift-sdk/SwiftExampleApp/**/*.swift : Use Platform SDK functions with the dash_sdk_* prefix
Learnt from: QuantumExplorer
Repo: dashpay/platform PR: 2257
File: packages/rs-drive-abci/src/mimic/test_quorum.rs:159-164
Timestamp: 2024-11-20T16:16:01.830Z
Learning: QuantumExplorer prefers not to receive auto-generated messages asking to post on social media.
📚 Learning: 2024-10-29T14:40:54.727Z
Learnt from: lklimek
Repo: dashpay/platform PR: 2277
File: packages/rs-sdk/src/core/transaction.rs:0-0
Timestamp: 2024-10-29T14:40:54.727Z
Learning: In `packages/rs-sdk/src/platform/document_query.rs` and `packages/rs-sdk/src/core/transaction.rs`, certain places don't implement `IntoInner`, so direct error mappings cannot be simplified using `IntoInner`. A TODO comment has been added to address this in a future PR.

Applied to files:

  • packages/rs-platform-wallet/src/platform_wallet_info/wallet_transaction_checker.rs
  • packages/rs-platform-wallet/src/platform_wallet_info/matured_transactions.rs
📚 Learning: 2025-10-09T15:59:28.329Z
Learnt from: lklimek
Repo: dashpay/platform PR: 2716
File: packages/rs-dapi/src/services/platform_service/broadcast_state_transition.rs:181-187
Timestamp: 2025-10-09T15:59:28.329Z
Learning: In `packages/rs-dapi/src/services/platform_service/broadcast_state_transition.rs`, the maintainer requires logging full state transition bytes (`tx_bytes = hex::encode(st_bytes)`) at debug level when a state transition passes CheckTx but is removed from the block by the proposer, to facilitate debugging of this rare edge case.

Applied to files:

  • packages/rs-platform-wallet/src/platform_wallet_info/wallet_transaction_checker.rs
📚 Learning: 2024-10-03T11:51:06.980Z
Learnt from: shumkov
Repo: dashpay/platform PR: 2201
File: packages/rs-platform-version/src/version/v2.rs:1186-1188
Timestamp: 2024-10-03T11:51:06.980Z
Learning: In the `IdentityTransitionVersions` structure within `packages/rs-platform-version/src/version/v2.rs`, the field `credit_withdrawal` does not need the `identity_` prefix since it is already encompassed within identity state transitions.

Applied to files:

  • packages/rs-platform-wallet/src/platform_wallet_info/wallet_transaction_checker.rs
  • packages/rs-platform-wallet/src/platform_wallet_info/matured_transactions.rs
  • packages/rs-platform-wallet/src/identity_manager/mod.rs
📚 Learning: 2025-03-11T09:39:23.071Z
Learnt from: shumkov
Repo: dashpay/platform PR: 2489
File: packages/rs-dpp/Cargo.toml:32-32
Timestamp: 2025-03-11T09:39:23.071Z
Learning: In the Dash Platform project, dependencies are currently managed using Git repository references with tags (repo+tag format in Cargo.toml) rather than published crates, as the team is not currently publishing crates to crates.io.

Applied to files:

  • packages/rs-dapi/Cargo.toml
  • packages/rs-platform-wallet/Cargo.toml
📚 Learning: 2025-09-03T16:37:11.605Z
Learnt from: QuantumExplorer
Repo: dashpay/platform PR: 2756
File: packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_list/update_state_masternode_list/v0/mod.rs:11-11
Timestamp: 2025-09-03T16:37:11.605Z
Learning: In packages/rs-dpp/Cargo.toml, the abci feature already includes core_rpc_client, and core_rpc_client is defined as ["dep:dashcore-rpc"]. This means rs-drive-abci can access dashcore-rpc types through dpp when using the abci feature.

Applied to files:

  • packages/rs-dapi/Cargo.toml
  • packages/rs-platform-wallet/Cargo.toml
📚 Learning: 2025-10-15T14:45:30.856Z
Learnt from: lklimek
Repo: dashpay/platform PR: 2716
File: packages/dashmate/src/test/constants/services.js:4-4
Timestamp: 2025-10-15T14:45:30.856Z
Learning: In the dashmate codebase (packages/dashmate), during the DAPI Rust migration (rs-dapi), the old service keys `dapi_api` and `dapi_core_streams` are intentionally kept in `generateEnvsFactory.js` for backward compatibility even though the test constants in `src/test/constants/services.js` have been updated to use `rs_dapi`. These deprecated keys will be removed in a future PR after the transition is complete.

Applied to files:

  • packages/rs-dapi/Cargo.toml
  • packages/rs-platform-wallet/Cargo.toml
📚 Learning: 2025-01-19T07:36:46.042Z
Learnt from: QuantumExplorer
Repo: dashpay/platform PR: 2431
File: packages/rs-drive/Cargo.toml:55-60
Timestamp: 2025-01-19T07:36:46.042Z
Learning: The grovedb dependencies in packages/rs-drive/Cargo.toml and related files are intentionally kept at specific revisions rather than using the latest stable version, with plans to update them at a later time.

Applied to files:

  • packages/rs-dapi/Cargo.toml
  • packages/rs-platform-wallet/Cargo.toml
📚 Learning: 2024-10-29T10:42:00.521Z
Learnt from: lklimek
Repo: dashpay/platform PR: 2277
File: packages/rs-dapi-client/Cargo.toml:22-22
Timestamp: 2024-10-29T10:42:00.521Z
Learning: In `packages/rs-dapi-client/Cargo.toml`, `backon` will not work without the `tokio-sleep` feature in our setup, so it's unnecessary to declare `tokio-sleep` as a separate feature in the `[features]` section.

Applied to files:

  • packages/rs-dapi/Cargo.toml
  • packages/rs-platform-wallet/Cargo.toml
📚 Learning: 2024-11-28T13:49:17.301Z
Learnt from: shumkov
Repo: dashpay/platform PR: 2317
File: packages/rs-dapi-client/src/address_list.rs:175-180
Timestamp: 2024-11-28T13:49:17.301Z
Learning: In Rust code in `packages/rs-dapi-client/src/address_list.rs`, do not change the interface of deprecated methods like `add_uri`, even to fix potential panics.

Applied to files:

  • packages/rs-dapi/Cargo.toml
📚 Learning: 2024-12-05T09:29:38.918Z
Learnt from: shumkov
Repo: dashpay/platform PR: 2375
File: packages/rs-drive-abci/Cargo.toml:61-63
Timestamp: 2024-12-05T09:29:38.918Z
Learning: In the `drive-abci` package, avoid adding unused dependencies like `hashbrown` to `Cargo.toml`. The team relies on CI to detect dependency version issues.

Applied to files:

  • packages/rs-dapi/Cargo.toml
📚 Learning: 2024-10-09T00:22:57.778Z
Learnt from: QuantumExplorer
Repo: dashpay/platform PR: 2215
File: packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_identities/create_owner_identity/v1/mod.rs:19-30
Timestamp: 2024-10-09T00:22:57.778Z
Learning: In the Rust file `packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_identities/create_owner_identity/v1/mod.rs`, within the `create_owner_identity_v1` function, the `add_public_keys` method of the `Identity` struct cannot fail and does not require explicit error handling.

Applied to files:

  • packages/rs-platform-wallet/src/platform_wallet_info/matured_transactions.rs
  • packages/rs-platform-wallet/src/identity_manager/mod.rs
📚 Learning: 2024-10-10T10:30:19.883Z
Learnt from: lklimek
Repo: dashpay/platform PR: 2232
File: packages/rs-sdk/src/mock/sdk.rs:90-95
Timestamp: 2024-10-10T10:30:19.883Z
Learning: In `packages/rs-sdk/src/mock/sdk.rs`, the `load_expectations` method in `MockDashPlatformSdk` remains asynchronous (`async`) for backward compatibility, even though it now delegates to the synchronous `load_expectations_sync` method.

Applied to files:

  • packages/rs-platform-wallet/src/platform_wallet_info/matured_transactions.rs
  • packages/rs-platform-wallet/Cargo.toml
📚 Learning: 2024-10-30T11:19:59.163Z
Learnt from: lklimek
Repo: dashpay/platform PR: 2277
File: packages/rs-sdk/tests/fetch/config.rs:233-233
Timestamp: 2024-10-30T11:19:59.163Z
Learning: In the Rust SDK's `rs-sdk/tests` integration tests (e.g., in `packages/rs-sdk/tests/fetch/config.rs`), we cannot create objects during tests because there is no support for object creation in this context. Therefore, hardcoded values for test identities must be used.

Applied to files:

  • packages/rs-platform-wallet/src/platform_wallet_info/contact_requests.rs
  • packages/rs-platform-wallet/src/identity_manager/mod.rs
📚 Learning: 2024-10-18T15:39:51.172Z
Learnt from: lklimek
Repo: dashpay/platform PR: 2254
File: packages/rs-sdk/src/sdk.rs:585-585
Timestamp: 2024-10-18T15:39:51.172Z
Learning: The 'platform' project uses Rust version 1.80, so code in 'packages/rs-sdk' can use features available in Rust 1.80, such as the `abs_diff()` method.

Applied to files:

  • packages/rs-platform-wallet/Cargo.toml
📚 Learning: 2025-09-07T22:18:50.883Z
Learnt from: CR
Repo: dashpay/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-07T22:18:50.883Z
Learning: Applies to packages/rs-sdk-ffi/**/*.{h,rs} : Follow unified SDK function prefixes: dash_core_sdk_* for Core, dash_sdk_* for Platform, dash_unified_sdk_* for unified APIs

Applied to files:

  • packages/rs-platform-wallet/Cargo.toml
📚 Learning: 2025-09-07T22:19:59.217Z
Learnt from: CR
Repo: dashpay/platform PR: 0
File: packages/swift-sdk/SwiftExampleApp/CLAUDE.md:0-0
Timestamp: 2025-09-07T22:19:59.217Z
Learning: Applies to packages/swift-sdk/SwiftExampleApp/**/*.swift : Use Core SDK functions with the dash_core_sdk_* prefix

Applied to files:

  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Services/WalletService.swift
  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift
📚 Learning: 2025-10-09T15:59:18.338Z
Learnt from: lklimek
Repo: dashpay/platform PR: 2716
File: packages/wallet-lib/src/plugins/Workers/TransactionsSyncWorker/TransactionsReader.js:325-334
Timestamp: 2025-10-09T15:59:18.338Z
Learning: In `packages/wallet-lib/src/plugins/Workers/TransactionsSyncWorker/TransactionsReader.js`, the continuous sync restart intentionally uses `Math.max(1, lastSyncedBlockHeight)` rather than `lastSyncedBlockHeight + 1` because the last block's processing status is uncertain at restart time. This conservative approach ensures no blocks are missed by reprocessing the last synced block, accepting potential duplicate processing for data completeness.

Applied to files:

  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Services/WalletService.swift
📚 Learning: 2025-09-07T22:19:59.217Z
Learnt from: CR
Repo: dashpay/platform PR: 0
File: packages/swift-sdk/SwiftExampleApp/CLAUDE.md:0-0
Timestamp: 2025-09-07T22:19:59.217Z
Learning: Applies to packages/swift-sdk/SwiftExampleApp/**/*View.swift : Use NavigationLink for drill-down navigation in the UI

Applied to files:

  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift
📚 Learning: 2025-09-07T22:19:59.217Z
Learnt from: CR
Repo: dashpay/platform PR: 0
File: packages/swift-sdk/SwiftExampleApp/CLAUDE.md:0-0
Timestamp: 2025-09-07T22:19:59.217Z
Learning: Applies to packages/swift-sdk/SwiftExampleApp/**/*View.swift : Implement proper loading and error states in UI screens

Applied to files:

  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift
📚 Learning: 2025-09-07T22:19:59.217Z
Learnt from: CR
Repo: dashpay/platform PR: 0
File: packages/swift-sdk/SwiftExampleApp/CLAUDE.md:0-0
Timestamp: 2025-09-07T22:19:59.217Z
Learning: Applies to packages/swift-sdk/SwiftExampleApp/**/*View.swift : Use SwiftUI with Query for reactive data in views

Applied to files:

  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift
📚 Learning: 2025-09-07T22:19:59.217Z
Learnt from: CR
Repo: dashpay/platform PR: 0
File: packages/swift-sdk/SwiftExampleApp/CLAUDE.md:0-0
Timestamp: 2025-09-07T22:19:59.217Z
Learning: Applies to packages/swift-sdk/SwiftExampleApp/**/*View.swift : Break complex SwiftUI views into smaller components to avoid compiler timeouts

Applied to files:

  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift
📚 Learning: 2025-09-07T22:19:59.217Z
Learnt from: CR
Repo: dashpay/platform PR: 0
File: packages/swift-sdk/SwiftExampleApp/CLAUDE.md:0-0
Timestamp: 2025-09-07T22:19:59.217Z
Learning: Applies to packages/swift-sdk/SwiftExampleApp/**/LocalDataContractsView*.swift : Use LocalDataContractsView to load data contracts from the network

Applied to files:

  • packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift
🧬 Code graph analysis (7)
packages/rs-platform-wallet/src/platform_wallet_info/wallet_transaction_checker.rs (1)
packages/rs-platform-wallet/src/platform_wallet_info/mod.rs (1)
  • identity_manager (41-43)
packages/rs-platform-wallet/src/established_contact.rs (2)
packages/rs-platform-wallet/src/contact_request.rs (1)
  • new (46-68)
packages/rs-platform-wallet/src/managed_identity/identity_ops.rs (1)
  • new (10-20)
packages/rs-platform-wallet/src/platform_wallet_info/matured_transactions.rs (4)
packages/rs-platform-wallet/src/established_contact.rs (1)
  • new (39-53)
packages/rs-platform-wallet/src/identity_manager/initializers.rs (2)
  • new (12-14)
  • sdk (31-33)
packages/rs-platform-wallet/src/platform_wallet_info/mod.rs (1)
  • new (26-31)
packages/rs-platform-wallet/src/identity_manager/accessors.rs (1)
  • managed_identity (62-64)
packages/rs-platform-wallet/src/platform_wallet_info/wallet_info_interface.rs (1)
packages/rs-platform-wallet/src/platform_wallet_info/mod.rs (1)
  • new (26-31)
packages/rs-platform-wallet/src/platform_wallet_info/contact_requests.rs (6)
packages/rs-platform-wallet/src/identity_manager/accessors.rs (2)
  • identity (13-15)
  • managed_identity (62-64)
packages/rs-platform-wallet/src/platform_wallet_info/accessors.rs (1)
  • identity (36-39)
packages/rs-platform-wallet/src/managed_identity/contact_requests.rs (4)
  • add_sent_contact_request (15-27)
  • add_incoming_contact_request (39-51)
  • accept_incoming_request (63-79)
  • create_contact_request (98-113)
packages/rs-sdk/src/platform/dashpay/contact_request.rs (5)
  • from (76-78)
  • from (82-84)
  • send_contact_request (378-456)
  • id (67-72)
  • create_contact_request (164-354)
packages/rs-platform-wallet/src/identity_manager/initializers.rs (2)
  • sdk (31-33)
  • new (12-14)
packages/rs-platform-wallet/src/contact_request.rs (1)
  • new (46-68)
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Services/WalletService.swift (1)
packages/swift-sdk/Sources/SwiftDashSDK/SPV/SPVClient.swift (3)
  • getBlocksWithTransactionsCount (528-531)
  • clearStorage (475-492)
  • clearSyncState (495-510)
packages/rs-platform-wallet/src/identity_manager/mod.rs (3)
packages/rs-platform-wallet/src/identity_manager/accessors.rs (3)
  • managed_identity (62-64)
  • identity (13-15)
  • identities (25-30)
packages/rs-platform-wallet/src/identity_manager/initializers.rs (1)
  • new (12-14)
packages/rs-platform-wallet/src/managed_identity/identity_ops.rs (2)
  • new (10-20)
  • id (23-25)
🪛 SwiftLint (0.57.0)
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift

[Warning] 325-325: Initializing an optional variable with nil is redundant

(redundant_optional_initialization)

🔇 Additional comments (23)
packages/rs-platform-wallet/src/platform_wallet_info/wallet_info_interface.rs (3)

18-30: LGTM! Constructors correctly initialize identity_managers.

The factory methods properly delegate to ManagedWalletInfo constructors and consistently initialize identity_managers as an empty BTreeMap, matching the pattern used in the new() method.


32-78: LGTM! Clean delegation pattern.

All property accessors and utxos() correctly delegate to the underlying wallet_info, following the facade pattern appropriately.


88-155: LGTM! Remaining delegations are correct.

All balance, transaction, account, and payment methods properly delegate to the underlying wallet_info with correct parameter passing and error handling.

packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Views/CoreContentView.swift (4)

125-131: LGTM! Clear button properly disabled during sync/initialization.

The disabled state is correctly implemented with appropriate visual feedback (gray background, reduced opacity) and matches the guard in clearSyncData.


174-180: LGTM! Navigation properly implemented.

The navigation destination for filter matches follows SwiftUI best practices, with environment objects correctly propagated to FilterMatchesView.

As per coding guidelines


305-313: LGTM! Defensive guard prevents unsafe operations.

The guard correctly prevents clearing sync data during sync or initialization, with a diagnostic print for debugging. This aligns with the UI disabled state and prevents potential data corruption.


336-352: LGTM! Conditional navigation elegantly implemented.

The conditional rendering based on navigationDestination is clean and follows SwiftUI patterns. When a destination is provided, only the label becomes tappable, which provides clear visual feedback for interactive elements.

As per coding guidelines

packages/swift-sdk/Sources/SwiftDashSDK/SPV/SPVClient.swift (2)

512-531: LGTM! Clean transaction query API.

The methods are well-documented, handle the uninitialized case appropriately, and provide a clean Swift API over the FFI boundary. The documentation clearly explains the persistence behavior and the meaning of a 0 return value.


230-233: Inconsistency with AI-generated summary.

The AI summary states: "These accessors/methods appear in two locations in the file, effectively duplicating the public API surface." However, I only observe a single occurrence of the clientHandle property and the transaction query methods in the provided code. The implementation does not appear to be duplicated.

Also applies to: 512-531

packages/rs-platform-wallet/Cargo.toml (2)

12-12: Verify that dash-sdk feature selection is complete.

The dash-sdk dependency explicitly disables default features and only enables ["dashpay-contract", "dpns-contract"]. Confirm that these two features are sufficient and that no other critical features (e.g., signing, persistence, or protocol features) are needed for platform wallet functionality.


12-13: Fix broken path dependencies that reference non-existent rust-dashcore directory.

Verification confirms that dependencies on lines 14, 15, and 20 reference paths that do not exist:

  • key-wallet = { path = "../../../rust-dashcore/key-wallet" }
  • key-wallet-manager = { path = "../../../rust-dashcore/key-wallet-manager", optional = true }
  • dashcore = { path = "../../../rust-dashcore/dash" }

The ../../../rust-dashcore/ directory is not present in the repository, and these crates do not exist as workspace members. This will cause immediate build failures.

These dependencies must either:

  1. Be sourced from crates.io with explicit versions, or
  2. Use Git repository references with tags (per established project pattern), or
  3. Be imported from a monorepo workspace structure that includes rust-dashcore as a member

Additionally, this pattern contradicts the established dependency management approach using Git refs with tags rather than local paths.

⛔ Skipped due to learnings
Learnt from: shumkov
Repo: dashpay/platform PR: 2489
File: packages/rs-dpp/Cargo.toml:32-32
Timestamp: 2025-03-11T09:39:23.071Z
Learning: In the Dash Platform project, dependencies are currently managed using Git repository references with tags (repo+tag format in Cargo.toml) rather than published crates, as the team is not currently publishing crates to crates.io.
Learnt from: QuantumExplorer
Repo: dashpay/platform PR: 2431
File: packages/rs-drive/Cargo.toml:55-60
Timestamp: 2025-01-19T07:36:46.042Z
Learning: The grovedb dependencies in packages/rs-drive/Cargo.toml and related files are intentionally kept at specific revisions rather than using the latest stable version, with plans to update them at a later time.
Learnt from: shumkov
Repo: dashpay/platform PR: 2375
File: packages/rs-drive-abci/Cargo.toml:61-63
Timestamp: 2024-12-05T09:29:38.918Z
Learning: In the `drive-abci` package, avoid adding unused dependencies like `hashbrown` to `Cargo.toml`. The team relies on CI to detect dependency version issues.
Learnt from: lklimek
Repo: dashpay/platform PR: 1924
File: packages/rs-sdk/src/sdk.rs:855-870
Timestamp: 2024-12-03T15:00:50.385Z
Learning: In `packages/rs-sdk/src/sdk.rs`, remember that `rustls_pemfile::certs` returns an iterator over `Result`, not a `Result` directly. Handle it accordingly in future code reviews.
Learnt from: lklimek
Repo: dashpay/platform PR: 2277
File: packages/rs-dapi-client/Cargo.toml:22-22
Timestamp: 2024-10-29T10:42:00.521Z
Learning: In `packages/rs-dapi-client/Cargo.toml`, `backon` will not work without the `tokio-sleep` feature in our setup, so it's unnecessary to declare `tokio-sleep` as a separate feature in the `[features]` section.
Learnt from: CR
Repo: dashpay/platform PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-09-07T22:18:50.883Z
Learning: Applies to packages/rs-sdk-ffi/**/*.{h,rs} : Follow unified SDK function prefixes: dash_core_sdk_* for Core, dash_sdk_* for Platform, dash_unified_sdk_* for unified APIs
Learnt from: lklimek
Repo: dashpay/platform PR: 2254
File: packages/rs-sdk/src/sdk.rs:585-585
Timestamp: 2024-10-18T15:39:51.172Z
Learning: The 'platform' project uses Rust version 1.80, so code in 'packages/rs-sdk' can use features available in Rust 1.80, such as the `abs_diff()` method.
Learnt from: lklimek
Repo: dashpay/platform PR: 2277
File: packages/rs-sdk/src/core/transaction.rs:0-0
Timestamp: 2024-10-29T14:40:54.727Z
Learning: In `packages/rs-sdk/src/platform/document_query.rs` and `packages/rs-sdk/src/core/transaction.rs`, certain places don't implement `IntoInner`, so direct error mappings cannot be simplified using `IntoInner`. A TODO comment has been added to address this in a future PR.
Learnt from: lklimek
Repo: dashpay/platform PR: 2716
File: packages/dashmate/src/test/constants/services.js:4-4
Timestamp: 2025-10-15T14:45:30.856Z
Learning: In the dashmate codebase (packages/dashmate), during the DAPI Rust migration (rs-dapi), the old service keys `dapi_api` and `dapi_core_streams` are intentionally kept in `generateEnvsFactory.js` for backward compatibility even though the test constants in `src/test/constants/services.js` have been updated to use `rs_dapi`. These deprecated keys will be removed in a future PR after the transition is complete.
Learnt from: QuantumExplorer
Repo: dashpay/platform PR: 2257
File: packages/rs-drive-abci/tests/strategy_tests/masternodes.rs:212-220
Timestamp: 2024-11-20T20:43:41.185Z
Learning: In `packages/rs-drive-abci/tests/strategy_tests/masternodes.rs`, the pattern of generating a `PrivateKey`, converting it to bytes, and reconstructing a `BlsPrivateKey` from those bytes is intentional. Avoid suggesting to simplify this code in future reviews.
Learnt from: QuantumExplorer
Repo: dashpay/platform PR: 2756
File: packages/rs-drive-abci/src/execution/platform_events/core_based_updates/update_masternode_list/update_state_masternode_list/v0/mod.rs:11-11
Timestamp: 2025-09-03T16:37:11.605Z
Learning: In packages/rs-dpp/Cargo.toml, the abci feature already includes core_rpc_client, and core_rpc_client is defined as ["dep:dashcore-rpc"]. This means rs-drive-abci can access dashcore-rpc types through dpp when using the abci feature.
Learnt from: CR
Repo: dashpay/platform PR: 0
File: packages/swift-sdk/SwiftExampleApp/CLAUDE.md:0-0
Timestamp: 2025-09-07T22:19:59.217Z
Learning: Applies to packages/swift-sdk/SwiftExampleApp/**/*.swift : Use Platform SDK functions with the dash_sdk_* prefix
Learnt from: lklimek
Repo: dashpay/platform PR: 2232
File: packages/rs-sdk/src/mock/sdk.rs:90-95
Timestamp: 2024-10-10T10:30:19.883Z
Learning: In `packages/rs-sdk/src/mock/sdk.rs`, the `load_expectations` method in `MockDashPlatformSdk` remains asynchronous (`async`) for backward compatibility, even though it now delegates to the synchronous `load_expectations_sync` method.
packages/rs-platform-wallet/src/established_contact.rs (5)

10-35: LGTM!

The struct definition is well-documented and appropriately models a bidirectional contact relationship. Public fields are suitable for the FFI context mentioned in the PR objectives.


37-53: LGTM!

The constructor correctly initializes all fields with sensible defaults. The implementation is clean and straightforward.


55-73: LGTM!

The alias and note management methods follow a consistent and idiomatic pattern for managing optional fields.


75-83: LGTM!

The visibility toggle methods are straightforward and correctly implemented.


98-215: LGTM!

The test module provides comprehensive coverage of all public API methods with clear, well-structured tests. The inclusion of edge case testing (e.g., duplicate account handling) demonstrates thorough validation.

packages/rs-platform-wallet/src/platform_wallet_info/wallet_transaction_checker.rs (3)

1-11: LGTM: Clean trait implementation setup.

The imports are well-organized and the async_trait implementation is appropriate for async trait methods.


12-24: LGTM: Correct delegation pattern.

The method correctly delegates to the underlying wallet_info while preserving all parameters and the async signature.


49-49: Good design: Original result preserved regardless of fetch outcome.

Returning the original result ensures that identity/contact fetching failures don't break the transaction check flow, which is the correct behavior for this post-processing hook.

packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Services/WalletService.swift (2)

246-318: LGTM! Proper initialization lifecycle management.

The initialization flow properly tracks state with the isInitializing flag and handles both success and error cases correctly. The restoration of persistent blocks hit count with overflow protection is well-implemented, and error handling ensures the flag is always reset even on failure.


469-474: LGTM! Proper state restoration on startup.

The persistent blocks hit count restoration is well-implemented with appropriate overflow protection and logging. This ensures UI counters reflect the actual wallet state from persistent storage rather than starting from zero.

packages/rs-platform-wallet/src/platform_wallet_info/matured_transactions.rs (1)

32-69: Single‑transaction wrapper is reasonable but depends on metadata never being needed

The helper builds an ImmatureTransaction with synthetic block metadata and delegates to the main routine. Given the comment that only the transaction body matters for identity discovery, this is fine for now, but if later logic starts using height/block_hash/maturity_confirmations, this wrapper will need to be revisited to pass through real values where available.

packages/rs-platform-wallet/src/platform_wallet_info/contact_requests.rs (1)

475-489: accept_incoming_request correctly delegates and normalizes errors

This method cleanly looks up the managed identity, delegates to ManagedIdentity::accept_incoming_request, and converts the Option into either the established contact or a ContactRequestNotFound error. The behavior is straightforward and matches the managed-identity API.

Comment on lines +88 to +89
dashcore-rpc = { path = "../../../rust-dashcore/rpc-client" }
dash-spv = { path = "../../../rust-dashcore/dash-spv" }
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify local path dependencies exist and are properly referenced.

The change from remote git references to local path dependencies (../../../rust-dashcore/rpc-client and ../../../rust-dashcore/dash-spv) represents a significant shift in dependency management. While this aligns with the PR's stated objective to prefer local path dependencies, cross-workspace references can be fragile.

Please verify:

  1. Both paths exist relative to the repository root and are correctly resolved
  2. The local rust-dashcore workspace is properly initialized and discoverable by Cargo
  3. No circular dependency chains are introduced
  4. Feature flags and crate visibility are preserved when using local paths vs. remote git references

Run the following script to confirm the paths are accessible:


🏁 Script executed:

#!/bin/bash
# Description: Verify local path dependencies exist and can be built

# Check if path dependencies exist
echo "=== Checking path existence ==="
test -d "rust-dashcore/rpc-client" && echo "✓ rpc-client path exists" || echo "✗ rpc-client path NOT found"
test -d "rust-dashcore/dash-spv" && echo "✓ dash-spv path exists" || echo "✗ dash-spv path NOT found"

# Verify Cargo can resolve the dependencies without errors
echo -e "\n=== Verifying Cargo can resolve dependencies ==="
cd packages/rs-dapi
cargo check --lib 2>&1 | head -20

Length of output: 245


</shell_calls>


🏁 Script executed:

#!/bin/bash
# Discover the actual repository structure and locate the missing directories

echo "=== Repository root structure ==="
ls -la | head -20

echo -e "\n=== Looking for 'rpc-client' directory anywhere in repo ==="
find . -maxdepth 3 -type d -name "rpc-client" 2>/dev/null | head -10

echo -e "\n=== Looking for 'dash-spv' directory anywhere in repo ==="
find . -maxdepth 3 -type d -name "dash-spv" 2>/dev/null | head -10

echo -e "\n=== Looking for 'rust-dashcore' directory ==="
find . -maxdepth 2 -type d -name "rust-dashcore" 2>/dev/null

echo -e "\n=== Content of packages/rs-dapi/Cargo.toml (lines 80-100) ==="
sed -n '80,100p' packages/rs-dapi/Cargo.toml

Length of output: 2049


🏁 Script executed:

#!/bin/bash
# Check repository root structure and workspace configuration

echo "=== Repository root listing ==="
ls -la

echo -e "\n=== Top-level Cargo.toml (if exists) ==="
if [ -f "Cargo.toml" ]; then
  cat Cargo.toml | head -30
else
  echo "No Cargo.toml at repository root"
fi

echo -e "\n=== Looking for all directories named 'rust-dashcore' ==="
find . -maxdepth 2 -type d -name "*dashcore*" 2>/dev/null

echo -e "\n=== Checking packages directory structure ==="
ls -la packages/ 2>/dev/null | head -20

echo -e "\n=== Looking for Cargo.toml files at top level and subdirs ==="
find . -maxdepth 2 -name "Cargo.toml" -type f 2>/dev/null | head -20

Length of output: 4539


Incorrect path references will cause build failures.

The local path dependencies in lines 88-89 reference non-existent directories:

  1. dashcore-rpc (line 88): Path ../../../rust-dashcore/rpc-client does not exist. The rpc-client directory is not present anywhere in the repository.

  2. dash-spv (line 89): While dash-spv exists in the repository at packages/dash-spv, the path is incorrectly specified as ../../../rust-dashcore/dash-spv. The correct relative path from packages/rs-dapi/ is ../dash-spv (matching the pattern used for other workspace dependencies like dpp and dapi-grpc).

Both dependencies will fail to resolve during build. Correct the paths to either:

  • Point to the actual locations within the workspace (e.g., ../dash-spv for dash-spv)
  • Or remove dashcore-rpc entirely if it cannot be sourced from the repository
🤖 Prompt for AI Agents
In packages/rs-dapi/Cargo.toml around lines 88 to 89, the two local path
dependencies point to non-existent locations; update dash-spv to the correct
workspace relative path and remove or fix dashcore-rpc: change the dash-spv path
from "../../../rust-dashcore/dash-spv" to "../dash-spv" to match the workspace
layout, and for dashcore-rpc either remove that dependency if the rpc-client
crate is not present in the repository or replace its path with the actual
relative path to the crate if it exists elsewhere in the repo; ensure Cargo.toml
paths are valid and run cargo metadata/build to verify resolution.

Comment on lines +6 to +7
#[allow(unused_imports)]
use crate::ContactRequest;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Remove the incorrect #[allow(unused_imports)] attribute.

The ContactRequest type is actively used in the struct fields (lines 19, 22) and throughout the test module. This attribute suppresses a warning that wouldn't occur.

Apply this diff to remove the unnecessary attribute:

-#[allow(unused_imports)]
 use crate::ContactRequest;
📝 Committable suggestion

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

Suggested change
#[allow(unused_imports)]
use crate::ContactRequest;
use crate::ContactRequest;
🤖 Prompt for AI Agents
In packages/rs-platform-wallet/src/established_contact.rs around lines 6 to 7,
remove the incorrect #[allow(unused_imports)] attribute applied to the
ContactRequest import; ContactRequest is used in struct fields and tests, so
simply delete the #[allow(unused_imports)] line so the import is a normal use
statement (use crate::ContactRequest;) and leave the rest of the file unchanged.

Comment on lines +151 to +297
pub(crate) fn add_incoming_contact_request(
&mut self,
wallet: &mut Wallet,
network: Network,
identity_id: &Identifier,
friend_identity: &Identity,
request: ContactRequest,
) -> Result<(), PlatformWalletError> {
if self
.identity_manager(network)
.and_then(|manager| manager.managed_identity(identity_id))
.is_none()
{
return Err(PlatformWalletError::IdentityNotFound(*identity_id));
}

if friend_identity.id() != request.sender_id {
return Err(PlatformWalletError::InvalidIdentityData(
"Incoming contact request sender does not match provided identity".to_string(),
));
}

let sender_key = friend_identity
.public_keys()
.get(&request.sender_key_index)
.ok_or_else(|| {
PlatformWalletError::InvalidIdentityData(
"Sender identity is missing the declared encryption key".to_string(),
)
})?;

if sender_key.purpose() != Purpose::ENCRYPTION {
return Err(PlatformWalletError::InvalidIdentityData(
"Sender key purpose must be ENCRYPTION".to_string(),
));
}

if self
.identity_manager(network)
.and_then(|manager| manager.managed_identity(identity_id))
.and_then(|managed| {
managed
.identity
.public_keys()
.get(&request.recipient_key_index)
})
.is_none()
{
return Err(PlatformWalletError::InvalidIdentityData(
"Recipient identity is missing the declared encryption key".to_string(),
));
}

let request_created_at = request.created_at;
let friend_identity_id = request.sender_id.to_buffer();
let friend_identity_identifier = Identifier::from(friend_identity_id);
let user_identity_id = identity_id.to_buffer();
let account_index = request.account_reference;
let encrypted_public_key = request.encrypted_public_key.clone();

let account_key = DashpayAccountKey {
index: account_index,
user_identity_id,
friend_identity_id,
};

let account_type = AccountType::DashpayExternalAccount {
index: account_index,
user_identity_id,
friend_identity_id,
};

let wallet_has_account = wallet
.accounts
.get(&network)
.and_then(|collection| collection.account_of_type(account_type))
.is_some();

if wallet_has_account {
return Err(PlatformWalletError::DashpayExternalAccountAlreadyExists {
identity: *identity_id,
contact: friend_identity_identifier,
network,
account_index,
});
}

let account_xpub = ExtendedPubKey::decode(&encrypted_public_key).map_err(|err| {
PlatformWalletError::InvalidIdentityData(format!(
"Failed to decode DashPay contact account xpub: {err}"
))
})?;

wallet
.add_account(account_type, network, Some(account_xpub))
.map_err(|err| {
PlatformWalletError::InvalidIdentityData(format!(
"Failed to add DashPay external account to wallet: {err}"
))
})?;

let managed_has_account = self
.wallet_info
.accounts(network)
.and_then(|collection| collection.dashpay_external_accounts.get(&account_key))
.is_some();

if managed_has_account {
return Err(PlatformWalletError::DashpayExternalAccountAlreadyExists {
identity: *identity_id,
contact: friend_identity_identifier,
network,
account_index,
});
}

self.wallet_info
.add_managed_account(wallet, account_type, network)
.map_err(|err| {
PlatformWalletError::InvalidIdentityData(format!(
"Failed to add managed DashPay external account: {err}"
))
})?;

let managed_account_collection = self
.wallet_info
.accounts_mut(network)
.ok_or(PlatformWalletError::NoAccountsForNetwork(network))?;

let managed_account = managed_account_collection
.dashpay_external_accounts
.get_mut(&account_key)
.ok_or_else(|| {
PlatformWalletError::InvalidIdentityData(
"Managed DashPay external account is missing".to_string(),
)
})?;

managed_account.metadata.last_used = Some(request_created_at);

self.identity_manager_mut(network)
.managed_identity_mut(identity_id)
.ok_or(PlatformWalletError::IdentityNotFound(*identity_id))?
.add_incoming_contact_request(request);

Ok(())
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

Encrypted contact account key is treated as a raw xpub – incoming requests will fail to attach accounts

This function does good validation on identities and key purposes, but the account-creation step appears inconsistent with how contact requests are produced:

  • parse_contact_request_document (in matured_transactions.rs) builds a ContactRequest by copying the document’s encryptedPublicKey bytes.
  • In the SDK (rs-sdk/src/platform/dashpay/contact_request.rs), that field is generated as an AES-encrypted extended public key (a fixed 96‑byte blob), not a plain BIP32 xpub.
  • Here, add_incoming_contact_request passes request.encrypted_public_key directly into ExtendedPubKey::decode, which expects a raw xpub encoding.

Unless you decrypt encrypted_public_key somewhere before calling this method, ExtendedPubKey::decode will consistently fail for real contact requests, and accepting an incoming request will never create the corresponding DashPay external account. You likely need a decryption step (via the SDK or platform-encryption) that turns the encrypted blob into the raw xpub bytes, or a different field in ContactRequest that holds the decrypted xpub for local use.

Also, tests currently only cover early‑exit error paths, so this mismatch wouldn’t be caught by them.


I'll verify the review comment by examining the relevant code sections and their interactions.


---

🏁 Script executed:

```shell
#!/bin/bash
# Find parse_contact_request_document and check how it handles encryptedPublicKey
rg -n "parse_contact_request_document" --type rs -A 30

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Find the ContactRequest struct definition
rg -n "struct ContactRequest" --type rs -A 10

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Find ExtendedPubKey::decode usage
rg -n "ExtendedPubKey::decode" --type rs -B 3 -A 3

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Search for encryption/decryption related to contact requests
rg -n "encrypted_public_key" --type rs -B 2 -A 2

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Find tests for add_incoming_contact_request
rg -n "add_incoming_contact_request" --type rs -B 2 -A 15

Length of output: 26


🏁 Script executed:

#!/bin/bash
# Search for platform-encryption usage with contact requests
rg -n "decrypt|Decrypt" --type rs | grep -i contact

Length of output: 26


Let me fix the ripgrep file type and search more broadly:


---

🏁 Script executed:

```shell
#!/bin/bash
# Search using correct rust type and without type restriction
rg -n "parse_contact_request_document" -A 30

Length of output: 9401


🏁 Script executed:

#!/bin/bash
# Find the ContactRequest struct definition
rg -n "struct ContactRequest" -A 10

Length of output: 5151


🏁 Script executed:

#!/bin/bash
# Find ExtendedPubKey::decode usage in contact context
rg -n "ExtendedPubKey::decode" -B 3 -A 3

Length of output: 802


🏁 Script executed:

#!/bin/bash
# Search for encrypted_public_key field references
rg -n "encrypted_public_key" -B 2 -A 2

Length of output: 16291


🏁 Script executed:

#!/bin/bash
# Find test files related to contact_requests
fd ".*test.*contact" --type f

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Find matured_transactions.rs file
fd "matured_transactions" --type f

Length of output: 76


Let me search for decryption logic and the context available in add_incoming_contact_request:Now let me search more specifically for the decrypt function in the contact request codebase:


---

🏁 Script executed:

```shell
#!/bin/bash
# Look for decrypt functions in contact request related code
rg -n "decrypt" packages/rs-sdk/src/platform/dashpay/contact_request.rs -B 3 -A 3

Length of output: 273


🏁 Script executed:

#!/bin/bash
# Look for ECDH key derivation or shared key generation in the wallet code
rg -n "shared_key\|derive.*key\|ecdh" packages/rs-platform-wallet/src/platform_wallet_info/contact_requests.rs -B 2 -A 2

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if there's a decrypt function being called anywhere in contact_requests.rs
rg -n "decrypt\|Decrypt" packages/rs-platform-wallet/src/platform_wallet_info/contact_requests.rs -B 3 -A 3

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for the full parse_contact_request_document implementation to see what it does
rg -n "fn parse_contact_request_document" packages/rs-platform-wallet/src/platform_wallet_info/matured_transactions.rs -A 100 | head -120

Length of output: 2917


Encrypted contact account key is treated as a raw xpub – incoming requests will fail to attach accounts

This function does good validation on identities and key purposes, but the account-creation step appears inconsistent with how contact requests are produced:

  • parse_contact_request_document (in matured_transactions.rs) builds a ContactRequest by copying the document's encryptedPublicKey bytes.
  • In the SDK (rs-sdk/src/platform/dashpay/contact_request.rs), that field is generated as an AES-encrypted extended public key (a fixed 96‑byte blob), not a plain BIP32 xpub.
  • Here, add_incoming_contact_request passes request.encrypted_public_key directly into ExtendedPubKey::decode, which expects a raw xpub encoding.

Unless you decrypt encrypted_public_key somewhere before calling this method, ExtendedPubKey::decode will consistently fail for real contact requests, and accepting an incoming request will never create the corresponding DashPay external account. You likely need a decryption step (via the SDK or platform-encryption) that turns the encrypted blob into the raw xpub bytes, or a different field in ContactRequest that holds the decrypted xpub for local use.

Also, tests currently only cover early‑exit error paths, so this mismatch wouldn't be caught by them.

.fetch_identity_and_contacts_for_asset_lock(wallet, network, tx)
.await
{
eprintln!("Failed to fetch identity for asset lock: {}", e);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Replace eprintln! with proper logging framework.

Using eprintln! for error logging is not suitable for production code. The Dash Platform codebase should use a proper logging framework like tracing for structured logging with appropriate log levels.

Apply this diff to use proper logging:

-                        {
-                            eprintln!("Failed to fetch identity for asset lock: {}", e);
-                        }
+                        {
+                            tracing::error!("Failed to fetch identity for asset lock: {}", e);
+                        }

Make sure tracing is added as a dependency if not already present.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
packages/rs-platform-wallet/src/platform_wallet_info/wallet_transaction_checker.rs
around line 42, replace the eprintln! call with the tracing logging macro:
import tracing (add use tracing::error; at top) and change eprintln!("Failed to
fetch identity for asset lock: {}", e); to error!("Failed to fetch identity for
asset lock: {}", e); and ensure tracing is added to Cargo.toml (e.g., tracing =
"0.1") and enabled in the crate so structured, leveled logging is used instead
of eprintln!.

Comment on lines +173 to +175
public var spvClientHandle: UnsafeMutablePointer<FFIDashSpvClient>? {
spvClient?.clientHandle
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Verify public exposure of raw FFI pointer.

Exposing a raw UnsafeMutablePointer<FFIDashSpvClient>? as a public API could be a safety concern. Misuse of this pointer by external consumers could lead to memory corruption or crashes.

Consider whether:

  1. External consumers truly need direct access to the FFI handle
  2. A safer abstraction (e.g., specific query methods) would be more appropriate
  3. At minimum, this should be documented with clear usage guidelines and safety warnings

Run the following script to check how this property is used across the codebase:


🏁 Script executed:

#!/bin/bash
# Description: Find all usages of spvClientHandle to verify access patterns

# Search for direct property access
rg -nP --type=swift -C3 '\bspvClientHandle\b'

# Search for FFIDashSpvClient usage patterns
rg -nP --type=swift -C3 '\bFFIDashSpvClient\b'

Length of output: 4669



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for all references to spvClientHandle including call sites
rg -nP --type=swift 'spvClientHandle' --no-heading

# Also check if it's referenced in comments or documentation
fd -e md -e txt | xargs grep -l 'spvClientHandle' 2>/dev/null || true

# Check the git history to understand intent
git log --oneline -S 'spvClientHandle' -- '*.swift' | head -20 || true

Length of output: 182


🏁 Script executed:

#!/bin/bash
# Check for any test files that might use spvClientHandle
fd -e swift 'test|spec|Test|Spec' | xargs grep -l 'spvClientHandle' 2>/dev/null || echo "No test files using spvClientHandle"

Length of output: 35


Remove unused spvClientHandle property or provide usage pattern.

spvClientHandle is defined but never used anywhere in the codebase, including tests. Since SPVClient.clientHandle already exposes the same raw FFI pointer publicly, this appears to be an unnecessary wrapper.

If this property is intended for external consumers, include a concrete usage example or documentation. If not actively needed, remove it to keep the public API surface clean.

🤖 Prompt for AI Agents
In
packages/swift-sdk/SwiftExampleApp/SwiftExampleApp/Core/Services/WalletService.swift
around lines 173-175, the public property spvClientHandle is an unused wrapper
around spvClient.clientHandle; remove the spvClientHandle property declaration
to shrink the public API surface, update any references if they exist (there
should be none), run the project build and tests to confirm no breakage, and
alternatively if this was intended for external consumers reintroduce it instead
with a documented usage example in the API comments and a unit test
demonstrating the intended access pattern.

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