Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ version = "0.40.0"
[patch.crates-io]
dashcore_hashes = { path = "hashes" }
# Use fixed version of elliptic-curve-tools with DefaultIsZeroes trait bound
elliptic-curve-tools = { git = "https://github.com/mikelodder7/elliptic-curve-tools", branch = "main" }
elliptic-curve-tools = { git = "https://github.com/mikelodder7/elliptic-curve-tools", rev = "8eaba09c9dc93ab367b3ad6fa85ed8e7ae8f0da5" }
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

[profile.release]
# Default to unwinding for most crates
Expand Down
35 changes: 26 additions & 9 deletions dash-spv-ffi/FFI_API.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This document provides a comprehensive reference for all FFI (Foreign Function I

**Auto-generated**: This documentation is automatically generated from the source code. Do not edit manually.

**Total Functions**: 70
**Total Functions**: 71

## Table of Contents

Expand Down Expand Up @@ -138,7 +138,7 @@ Functions: 2

### Utility Functions

Functions: 18
Functions: 19

| Function | Description | Module |
|----------|-------------|--------|
Expand All @@ -151,7 +151,7 @@ Functions: 18
| `dash_spv_ffi_client_get_stats` | Get current runtime statistics for the SPV client | client |
| `dash_spv_ffi_client_get_tip_hash` | Get the current chain tip hash (32 bytes) if available | client |
| `dash_spv_ffi_client_get_tip_height` | Get the current chain tip height (absolute) | client |
| `dash_spv_ffi_client_get_wallet_manager` | Get the wallet manager from the SPV client Returns an opaque pointer to FFIW... | client |
| `dash_spv_ffi_client_get_wallet_manager` | Get the wallet manager from the SPV client Returns a pointer to an `FFIWalle... | client |
| `dash_spv_ffi_client_record_send` | Record that we attempted to send a transaction by its txid | client |
| `dash_spv_ffi_client_rescan_blockchain` | Request a rescan of the blockchain from a given height (not yet implemented) | client |
| `dash_spv_ffi_enable_test_mode` | No description | utils |
Expand All @@ -160,6 +160,7 @@ Functions: 18
| `dash_spv_ffi_string_array_destroy` | Destroy an array of FFIString pointers (Vec<*mut FFIString>) and their contents | types |
| `dash_spv_ffi_string_destroy` | No description | types |
| `dash_spv_ffi_version` | No description | utils |
| `dash_spv_ffi_wallet_manager_free` | Release a wallet manager obtained from `dash_spv_ffi_client_get_wallet_manager` | client |

## Detailed Function Documentation

Expand Down Expand Up @@ -1117,14 +1118,14 @@ Get the current chain tip height (absolute). # Safety - `client` must be a vali
#### `dash_spv_ffi_client_get_wallet_manager`

```c
dash_spv_ffi_client_get_wallet_manager(client: *mut FFIDashSpvClient,) -> *mut c_void
dash_spv_ffi_client_get_wallet_manager(client: *mut FFIDashSpvClient,) -> *mut FFIWalletManager
```

**Description:**
Get the wallet manager from the SPV client Returns an opaque pointer to FFIWalletManager that contains a cloned Arc reference to the wallet manager. This allows direct interaction with the wallet manager without going through the client. # Safety The caller must ensure that: - The client pointer is valid - The returned pointer is freed using `wallet_manager_free` from key-wallet-ffi # Returns An opaque pointer (void*) to the wallet manager, or NULL if the client is not initialized. Swift should treat this as an OpaquePointer. Get a handle to the wallet manager owned by this client. # Safety - `client` must be a valid, non-null pointer.
Get the wallet manager from the SPV client Returns a pointer to an `FFIWalletManager` wrapper that clones the underlying `Arc<RwLock<WalletManager>>`. This allows direct interaction with the wallet manager without going back through the client for each call. # Safety The caller must ensure that: - The client pointer is valid - The returned pointer is released exactly once using `dash_spv_ffi_wallet_manager_free` # Returns A pointer to the wallet manager wrapper, or NULL if the client is not initialized.

**Safety:**
The caller must ensure that: - The client pointer is valid - The returned pointer is freed using `wallet_manager_free` from key-wallet-ffi
The caller must ensure that: - The client pointer is valid - The returned pointer is released exactly once using `dash_spv_ffi_wallet_manager_free`

**Module:** `client`

Expand Down Expand Up @@ -1237,6 +1238,22 @@ dash_spv_ffi_version() -> *const c_char

---

#### `dash_spv_ffi_wallet_manager_free`

```c
dash_spv_ffi_wallet_manager_free(manager: *mut FFIWalletManager) -> ()
```

**Description:**
Release a wallet manager obtained from `dash_spv_ffi_client_get_wallet_manager`. This simply forwards to `wallet_manager_free` in key-wallet-ffi so that lifetime management is consistent between direct key-wallet usage and the SPV client pathway. # Safety - `manager` must either be null or a pointer previously returned by `dash_spv_ffi_client_get_wallet_manager`.

**Safety:**
- `manager` must either be null or a pointer previously returned by `dash_spv_ffi_client_get_wallet_manager`.

**Module:** `client`

---

## Type Definitions

### Core Types
Expand Down Expand Up @@ -1266,7 +1283,7 @@ dash_spv_ffi_version() -> *const c_char
2. **Cleanup Required**: All returned pointers must be freed using the appropriate `_destroy` function
3. **Thread Safety**: The SPV client is thread-safe
4. **Error Handling**: Check return codes and use `dash_spv_ffi_get_last_error()` for details
5. **Opaque Pointers**: `dash_spv_ffi_client_get_wallet_manager()` returns `void*` for Swift compatibility
5. **Shared Ownership**: `dash_spv_ffi_client_get_wallet_manager()` returns `FFIWalletManager*` that must be released with `dash_spv_ffi_wallet_manager_free()`

## Usage Examples

Expand All @@ -1289,8 +1306,8 @@ if (result != 0) {
// Sync to chain tip
dash_spv_ffi_client_sync_to_tip(client, NULL, NULL);

// Get wallet manager (returns void* for Swift)
void* wallet_manager = dash_spv_ffi_client_get_wallet_manager(client);
// Get wallet manager (shares ownership with the client)
FFIWalletManager* wallet_manager = dash_spv_ffi_client_get_wallet_manager(client);

Comment on lines +1309 to 1311

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Example leaks the wallet manager; add a free call.

The “Basic SPV Client Usage” example obtains FFIWalletManager* but never frees it. This encourages leaks in client code. Add dash_spv_ffi_wallet_manager_free(wallet_manager); (before destroying the client or right after use).

Apply this diff to the example:

 // Get wallet manager (shares ownership with the client)
 FFIWalletManager* wallet_manager = dash_spv_ffi_client_get_wallet_manager(client);

+// Free the wallet manager when done
+dash_spv_ffi_wallet_manager_free(wallet_manager);
+wallet_manager = NULL;
📝 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 wallet manager (shares ownership with the client)
FFIWalletManager* wallet_manager = dash_spv_ffi_client_get_wallet_manager(client);
// Get wallet manager (shares ownership with the client)
FFIWalletManager* wallet_manager = dash_spv_ffi_client_get_wallet_manager(client);
// Free the wallet manager when done
dash_spv_ffi_wallet_manager_free(wallet_manager);
wallet_manager = NULL;
🤖 Prompt for AI Agents
In dash-spv-ffi/FFI_API.md around lines 1309 to 1311, the example obtains an
FFIWalletManager* via dash_spv_ffi_client_get_wallet_manager(client) but never
frees it; add a call to dash_spv_ffi_wallet_manager_free(wallet_manager) after
you finish using the wallet_manager (either immediately after its last use in
the example or before destroying the client) to avoid leaking the wallet
manager.

// Clean up
dash_spv_ffi_client_destroy(client);
Expand Down
28 changes: 18 additions & 10 deletions dash-spv-ffi/dash_spv_ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -531,27 +531,35 @@ int32_t dash_spv_ffi_client_enable_mempool_tracking(struct FFIDashSpvClient *cli
/**
* Get the wallet manager from the SPV client
*
* Returns an opaque pointer to FFIWalletManager that contains a cloned Arc reference to the wallet manager.
* This allows direct interaction with the wallet manager without going through the client.
* Returns a pointer to an `FFIWalletManager` wrapper that clones the underlying
* `Arc<RwLock<WalletManager>>`. This allows direct interaction with the wallet
* manager without going back through the client for each call.
*
* # Safety
*
* The caller must ensure that:
* - The client pointer is valid
* - The returned pointer is freed using `wallet_manager_free` from key-wallet-ffi
* - The returned pointer is released exactly once using
* `dash_spv_ffi_wallet_manager_free`
*
* # Returns
*
* An opaque pointer (void*) to the wallet manager, or NULL if the client is not initialized.
* Swift should treat this as an OpaquePointer.
* Get a handle to the wallet manager owned by this client.
* A pointer to the wallet manager wrapper, or NULL if the client is not initialized.
*/
FFIWalletManager *dash_spv_ffi_client_get_wallet_manager(struct FFIDashSpvClient *client) ;

Comment on lines +534 to +550

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Blocker: FFIWalletManager not declared in this header — switch prototypes to struct-qualified to avoid compile error

FFIWalletManager is used but no typedef/forward-declaration exists in this file. C/Swift consumers will see “unknown type name ‘FFIWalletManager’”. Fix locally by using struct-qualified pointers in the signatures. Apply:

- FFIWalletManager *dash_spv_ffi_client_get_wallet_manager(struct FFIDashSpvClient *client) ;
+ struct FFIWalletManager *dash_spv_ffi_client_get_wallet_manager(struct FFIDashSpvClient *client) ;

Optionally, also re-export the type in dash-spv-ffi (e.g., pub use key_wallet_ffi::FFIWalletManager;) so cbindgen emits a proper typedef on future generations. Based on learnings.

/**
* Release a wallet manager obtained from `dash_spv_ffi_client_get_wallet_manager`.
*
* This simply forwards to `wallet_manager_free` in key-wallet-ffi so that
* lifetime management is consistent between direct key-wallet usage and the
* SPV client pathway.
*
* # Safety
* - `client` must be a valid, non-null pointer.
* - `manager` must either be null or a pointer previously returned by
* `dash_spv_ffi_client_get_wallet_manager`.
*/

void *dash_spv_ffi_client_get_wallet_manager(struct FFIDashSpvClient *client)
;
void dash_spv_ffi_wallet_manager_free(FFIWalletManager *manager) ;

Comment on lines +551 to 563

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Make free signature struct-qualified as well for consistency and C-compat

Mirror the change in the free function to avoid the same undefined type:

- void dash_spv_ffi_wallet_manager_free(FFIWalletManager *manager) ;
+ void dash_spv_ffi_wallet_manager_free(struct FFIWalletManager *manager) ;

Also re-run header generation and sync to SDK after adjusting. Based on learnings.

Run to verify header health:


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Check FFIWalletManager usage:"
rg -n "FFIWalletManager" dash-spv-ffi/dash_spv_ffi.h | sed -n '1,200p'
echo "Ensure a declaration exists (typedef or struct-qualified usage):"
rg -nP 'typedef\s+struct\s+FFIWalletManager|struct\s+FFIWalletManager\s*\*' dash-spv-ffi/dash_spv_ffi.h

echo "Check FFINetwork is declared (required by many functions):"
rg -nP 'typedef\s+enum\s+FFINetwork|enum\s+FFINetwork\b' dash-spv-ffi/dash_spv_ffi.h

Length of output: 348


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Search for inline struct definition in header:"
rg -nP 'struct\s+FFIWalletManager\s*\{' dash-spv-ffi/dash_spv_ffi.h || echo "No inline struct definition found."
echo
echo "Search for typedef alias in header:"
rg -nP 'typedef\s+struct\s+FFIWalletManager\s+FFIWalletManager' dash-spv-ffi/dash_spv_ffi.h || echo "No typedef alias found."
echo
echo "Search entire dash-spv-ffi directory for any struct/typedef of FFIWalletManager:"
rg -nH -n 'FFIWalletManager' -C2 dash-spv-ffi

Length of output: 10333


Qualify FFIWalletManager as an opaque struct in the C header

In dash_spv_ffi_wallet_manager_free, change the parameter from a bare FFIWalletManager* to struct FFIWalletManager* to match other opaque types (e.g. struct FFIClientConfig*) and avoid undefined-type errors:

- void dash_spv_ffi_wallet_manager_free(FFIWalletManager *manager);
+ void dash_spv_ffi_wallet_manager_free(struct FFIWalletManager *manager);

After applying this, re-generate the C header and re-run ./sync-headers.sh to update the Swift SDK.

📝 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
/**
* Release a wallet manager obtained from `dash_spv_ffi_client_get_wallet_manager`.
*
* This simply forwards to `wallet_manager_free` in key-wallet-ffi so that
* lifetime management is consistent between direct key-wallet usage and the
* SPV client pathway.
*
* # Safety
* - `client` must be a valid, non-null pointer.
* - `manager` must either be null or a pointer previously returned by
* `dash_spv_ffi_client_get_wallet_manager`.
*/
void *dash_spv_ffi_client_get_wallet_manager(struct FFIDashSpvClient *client)
;
void dash_spv_ffi_wallet_manager_free(FFIWalletManager *manager) ;
/**
* Release a wallet manager obtained from `dash_spv_ffi_client_get_wallet_manager`.
*
* This simply forwards to `wallet_manager_free` in key-wallet-ffi so that
* lifetime management is consistent between direct key-wallet usage and the
* SPV client pathway.
*
* # Safety
* - `manager` must either be null or a pointer previously returned by
* `dash_spv_ffi_client_get_wallet_manager`.
*/
void dash_spv_ffi_wallet_manager_free(struct FFIWalletManager *manager);
🤖 Prompt for AI Agents
In dash-spv-ffi/dash_spv_ffi.h around lines 551 to 563, the function parameter
uses a bare FFIWalletManager* which is inconsistent with other opaque types and
can trigger undefined-type errors; change the signature to accept struct
FFIWalletManager* instead of FFIWalletManager* to match the header's opaque type
declarations, then re-generate the C header and run ./sync-headers.sh to
propagate the updated declaration to the Swift SDK.

struct FFIClientConfig *dash_spv_ffi_config_new(FFINetwork network) ;

Expand Down
20 changes: 10 additions & 10 deletions dash-spv-ffi/examples/wallet_manager_usage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
/// 2. No longer requires going through the client for each operation
/// 3. Cleaner and more efficient access to wallet functionality
use dash_spv_ffi::*;
use key_wallet_ffi::{wallet_manager_free, wallet_manager_wallet_count, FFIError};
use key_wallet_ffi::{wallet_manager_wallet_count, FFIError};

fn main() {
unsafe {
Expand All @@ -21,22 +21,22 @@ fn main() {
panic!("Failed to create client");
}

// Get the wallet manager - now returns void* for Swift compatibility
// This contains a cloned Arc to the wallet manager, allowing
// direct interaction without going through the client
let wallet_manager_ptr = dash_spv_ffi_client_get_wallet_manager(client);
if wallet_manager_ptr.is_null() {
// Get the wallet manager - this returns a strongly typed pointer that
// shares the Arc with the SPV client, allowing direct interaction
let wallet_manager = dash_spv_ffi_client_get_wallet_manager(client);
if wallet_manager.is_null() {
panic!("Failed to get wallet manager");
}
// Cast back to FFIWalletManager for use
let wallet_manager = wallet_manager_ptr as *mut key_wallet_ffi::FFIWalletManager;

// Now we can use the wallet manager directly
// No need to go through client -> inner -> spv_client -> wallet()

// Get the number of wallets (should be 0 initially)
let mut error = std::mem::zeroed::<FFIError>();
let wallet_count = wallet_manager_wallet_count(wallet_manager, &mut error);
let wallet_count = wallet_manager_wallet_count(
wallet_manager as *const key_wallet_ffi::FFIWalletManager,
&mut error,
);
println!("Number of wallets: {}", wallet_count);

// Note: To get total balance, you would need to iterate through wallets
Expand Down Expand Up @@ -66,7 +66,7 @@ fn main() {
// Clean up
// The wallet manager can now be independently destroyed
// It maintains its own Arc reference to the underlying wallet
wallet_manager_free(wallet_manager);
dash_spv_ffi_wallet_manager_free(wallet_manager);
dash_spv_ffi_client_destroy(client);
dash_spv_ffi_config_destroy(config);

Expand Down
28 changes: 18 additions & 10 deletions dash-spv-ffi/include/dash_spv_ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -531,27 +531,35 @@ int32_t dash_spv_ffi_client_enable_mempool_tracking(struct FFIDashSpvClient *cli
/**
* Get the wallet manager from the SPV client
*
* Returns an opaque pointer to FFIWalletManager that contains a cloned Arc reference to the wallet manager.
* This allows direct interaction with the wallet manager without going through the client.
* Returns a pointer to an `FFIWalletManager` wrapper that clones the underlying
* `Arc<RwLock<WalletManager>>`. This allows direct interaction with the wallet
* manager without going back through the client for each call.
*
* # Safety
*
* The caller must ensure that:
* - The client pointer is valid
* - The returned pointer is freed using `wallet_manager_free` from key-wallet-ffi
* - The returned pointer is released exactly once using
* `dash_spv_ffi_wallet_manager_free`
*
* # Returns
*
* An opaque pointer (void*) to the wallet manager, or NULL if the client is not initialized.
* Swift should treat this as an OpaquePointer.
* Get a handle to the wallet manager owned by this client.
* A pointer to the wallet manager wrapper, or NULL if the client is not initialized.
*/
FFIWalletManager *dash_spv_ffi_client_get_wallet_manager(struct FFIDashSpvClient *client) ;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

/**
* Release a wallet manager obtained from `dash_spv_ffi_client_get_wallet_manager`.
*
* This simply forwards to `wallet_manager_free` in key-wallet-ffi so that
* lifetime management is consistent between direct key-wallet usage and the
* SPV client pathway.
*
* # Safety
* - `client` must be a valid, non-null pointer.
* - `manager` must either be null or a pointer previously returned by
* `dash_spv_ffi_client_get_wallet_manager`.
*/

void *dash_spv_ffi_client_get_wallet_manager(struct FFIDashSpvClient *client)
;
void dash_spv_ffi_wallet_manager_free(FFIWalletManager *manager) ;

Comment thread
coderabbitai[bot] marked this conversation as resolved.
struct FFIClientConfig *dash_spv_ffi_config_new(FFINetwork network) ;

Expand Down
9 changes: 6 additions & 3 deletions dash-spv-ffi/scripts/generate_ffi_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,10 @@ def generate_markdown(functions: List[FFIFunction]) -> str:
md.append("2. **Cleanup Required**: All returned pointers must be freed using the appropriate `_destroy` function")
md.append("3. **Thread Safety**: The SPV client is thread-safe")
md.append("4. **Error Handling**: Check return codes and use `dash_spv_ffi_get_last_error()` for details")
md.append("5. **Opaque Pointers**: `dash_spv_ffi_client_get_wallet_manager()` returns `void*` for Swift compatibility")
md.append(
"5. **Shared Ownership**: `dash_spv_ffi_client_get_wallet_manager()` returns `FFIWalletManager*` "
"that must be released with `dash_spv_ffi_wallet_manager_free()`"
)
md.append("")

# Usage Examples
Expand All @@ -312,8 +315,8 @@ def generate_markdown(functions: List[FFIFunction]) -> str:
md.append("// Sync to chain tip")
md.append("dash_spv_ffi_client_sync_to_tip(client, NULL, NULL);")
md.append("")
md.append("// Get wallet manager (returns void* for Swift)")
md.append("void* wallet_manager = dash_spv_ffi_client_get_wallet_manager(client);")
md.append("// Get wallet manager (shares ownership with the client)")
md.append("FFIWalletManager* wallet_manager = dash_spv_ffi_client_get_wallet_manager(client);")
md.append("")
md.append("// Clean up")
md.append("dash_spv_ffi_client_destroy(client);")
Expand Down
37 changes: 26 additions & 11 deletions dash-spv-ffi/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1485,27 +1485,24 @@ pub unsafe extern "C" fn dash_spv_ffi_client_record_send(

/// Get the wallet manager from the SPV client
///
/// Returns an opaque pointer to FFIWalletManager that contains a cloned Arc reference to the wallet manager.
/// This allows direct interaction with the wallet manager without going through the client.
/// Returns a pointer to an `FFIWalletManager` wrapper that clones the underlying
/// `Arc<RwLock<WalletManager>>`. This allows direct interaction with the wallet
/// manager without going back through the client for each call.
///
/// # Safety
///
/// The caller must ensure that:
/// - The client pointer is valid
/// - The returned pointer is freed using `wallet_manager_free` from key-wallet-ffi
/// - The returned pointer is released exactly once using
/// `dash_spv_ffi_wallet_manager_free`
///
/// # Returns
///
/// An opaque pointer (void*) to the wallet manager, or NULL if the client is not initialized.
/// Swift should treat this as an OpaquePointer.
/// Get a handle to the wallet manager owned by this client.
///
/// # Safety
/// - `client` must be a valid, non-null pointer.
/// A pointer to the wallet manager wrapper, or NULL if the client is not initialized.
#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_client_get_wallet_manager(
client: *mut FFIDashSpvClient,
) -> *mut c_void {
) -> *mut FFIWalletManager {
null_check!(client, std::ptr::null_mut());

let client = &*client;
Expand All @@ -1519,9 +1516,27 @@ pub unsafe extern "C" fn dash_spv_ffi_client_get_wallet_manager(
// Create the FFIWalletManager with the cloned Arc
let manager = FFIWalletManager::from_arc(wallet_arc, runtime);

Box::into_raw(Box::new(manager)) as *mut c_void
Box::into_raw(Box::new(manager))
} else {
set_last_error("Client not initialized");
std::ptr::null_mut()
}
}

/// Release a wallet manager obtained from `dash_spv_ffi_client_get_wallet_manager`.
///
/// This simply forwards to `wallet_manager_free` in key-wallet-ffi so that
/// lifetime management is consistent between direct key-wallet usage and the
/// SPV client pathway.
///
/// # Safety
/// - `manager` must either be null or a pointer previously returned by
/// `dash_spv_ffi_client_get_wallet_manager`.
#[no_mangle]
pub unsafe extern "C" fn dash_spv_ffi_wallet_manager_free(manager: *mut FFIWalletManager) {
if manager.is_null() {
return;
}

key_wallet_ffi::wallet_manager::wallet_manager_free(manager);
}
Loading
Loading