feat(wallet-ffi): add path-free wallet create/open and default-path helpers

Wallet consumers (e.g. the Qt module and apps built on it) currently have to
reconstruct LEZ's wallet path layout themselves to call create/open, which
duplicates the logic in `wallet::helperfunctions` and silently drifts if the
default home or filenames change.

Add functions that resolve everything from LEZ's canonical home
(`LEE_WALLET_HOME_DIR` or `~/.lee/wallet`):

- `wallet_ffi_create_new_default(password)` — create at the default home
  (resolves paths, creates the directory), path-free `wallet_ffi_create_new`.
- `wallet_ffi_open_default()` — open the default wallet via `WalletCore::from_env`.
- `wallet_ffi_default_config_path()` / `wallet_ffi_default_storage_path()` —
  return the resolved paths (caller frees with `wallet_ffi_free_string`).
- `wallet_ffi_wallet_exists_default()` — whether a wallet already exists at the
  default home, for choosing between an open and a create flow.

Together these let consumers drop their own path handling entirely. The C
header (`wallet_ffi.h`) is regenerated by the cbindgen build step.
This commit is contained in:
r4bbit 2026-06-26 10:02:19 +02:00
parent 64f8444a67
commit e2d73820cf
No known key found for this signature in database
GPG Key ID: E95F1E9447DC91A9
2 changed files with 266 additions and 0 deletions

View File

@ -178,6 +178,192 @@ pub unsafe extern "C" fn wallet_ffi_open(
}
}
/// Resolve LEZ's canonical wallet paths (`LEE_WALLET_HOME_DIR` or `~/.lee/wallet`)
/// and ensure the home directory exists. Centralizes the path layout so callers
/// don't have to reconstruct it.
fn default_wallet_paths() -> Result<(PathBuf, PathBuf), WalletFfiError> {
let home = wallet::helperfunctions::get_home().map_err(|e| {
print_error(format!("Failed to resolve wallet home: {e}"));
WalletFfiError::InternalError
})?;
if let Err(e) = std::fs::create_dir_all(&home) {
print_error(format!(
"Failed to create wallet home {}: {e}",
home.display()
));
return Err(WalletFfiError::StorageError);
}
let config_path = wallet::helperfunctions::fetch_config_path().map_err(|e| {
print_error(format!("Failed to resolve config path: {e}"));
WalletFfiError::InternalError
})?;
let storage_path = wallet::helperfunctions::fetch_persistent_storage_path().map_err(|e| {
print_error(format!("Failed to resolve storage path: {e}"));
WalletFfiError::InternalError
})?;
Ok((config_path, storage_path))
}
/// Create a new wallet at LEZ's canonical home, deriving the paths from the
/// environment (`LEE_WALLET_HOME_DIR` or `~/.lee/wallet`) and creating the
/// directory if needed.
///
/// This is the path-free equivalent of [`wallet_ffi_create_new`]: callers that
/// just want the default location don't have to reconstruct LEZ's path layout.
///
/// # Parameters
/// - `password`: Password for encrypting the wallet seed
///
/// # Returns
/// - Result, which contains opaque wallet handle and mnemonic words on success
/// - Result with null pointers on error (call `wallet_ffi_get_last_error()` for details)
///
/// # Safety
/// `password` must be a valid null-terminated UTF-8 string.
#[no_mangle]
pub unsafe extern "C" fn wallet_ffi_create_new_default(
password: *const c_char,
) -> FfiCreateWalletOutput {
let Ok(password) = c_str_to_string(password, "password") else {
return FfiCreateWalletOutput::default();
};
let Ok((config_path, storage_path)) = default_wallet_paths() else {
return FfiCreateWalletOutput::default();
};
match WalletCore::new_init_storage(config_path, storage_path, None, &password) {
Ok((core, mnemonic)) => {
let wrapper = Box::new(WalletWrapper {
core: Mutex::new(core),
});
let handle = Box::into_raw(wrapper).cast::<WalletHandle>();
let Ok(c_mnemonic_string) = CString::new(mnemonic.to_string()) else {
return FfiCreateWalletOutput::default();
};
let raw_pointer = CString::into_raw(c_mnemonic_string);
FfiCreateWalletOutput {
wallet: handle,
mnemonic: raw_pointer,
}
}
Err(e) => {
print_error(format!("Failed to create wallet: {e}"));
FfiCreateWalletOutput::default()
}
}
}
/// Open the existing wallet at LEZ's canonical home, deriving the paths from the
/// environment (`LEE_WALLET_HOME_DIR` or `~/.lee/wallet`).
///
/// This is the path-free equivalent of [`wallet_ffi_open`].
///
/// # Returns
/// - Opaque wallet handle on success
/// - Null pointer on error (call `wallet_ffi_get_last_error()` for details)
///
/// # Safety
/// This function takes no pointer arguments and is always safe to call.
#[no_mangle]
pub unsafe extern "C" fn wallet_ffi_open_default() -> *mut WalletHandle {
match WalletCore::from_env() {
Ok(core) => {
let wrapper = Box::new(WalletWrapper {
core: Mutex::new(core),
});
Box::into_raw(wrapper).cast::<WalletHandle>()
}
Err(e) => {
print_error(format!("Failed to open wallet: {e}"));
ptr::null_mut()
}
}
}
/// Convert a resolved path into an owned C string for return across the FFI.
fn path_to_c_string(path: PathBuf) -> *mut c_char {
match CString::new(path.to_string_lossy().into_owned()) {
Ok(s) => s.into_raw(),
Err(e) => {
print_error(format!("Invalid path string: {e}"));
ptr::null_mut()
}
}
}
/// Return LEZ's canonical wallet config path (`LEE_WALLET_HOME_DIR` or
/// `~/.lee/wallet`, plus `wallet_config.json`).
///
/// Lets callers display or inspect the default location without reconstructing
/// LEZ's path layout themselves.
///
/// # Returns
/// - Pointer to null-terminated string on success (caller must free with
/// `wallet_ffi_free_string()`)
/// - Null pointer on error
///
/// # Safety
/// This function takes no pointer arguments and is always safe to call.
#[no_mangle]
pub unsafe extern "C" fn wallet_ffi_default_config_path() -> *mut c_char {
match wallet::helperfunctions::fetch_config_path() {
Ok(path) => path_to_c_string(path),
Err(e) => {
print_error(format!("Failed to resolve config path: {e}"));
ptr::null_mut()
}
}
}
/// Return LEZ's canonical wallet storage path (`LEE_WALLET_HOME_DIR` or
/// `~/.lee/wallet`, plus `storage.json`).
///
/// # Returns
/// - Pointer to null-terminated string on success (caller must free with
/// `wallet_ffi_free_string()`)
/// - Null pointer on error
///
/// # Safety
/// This function takes no pointer arguments and is always safe to call.
#[no_mangle]
pub unsafe extern "C" fn wallet_ffi_default_storage_path() -> *mut c_char {
match wallet::helperfunctions::fetch_persistent_storage_path() {
Ok(path) => path_to_c_string(path),
Err(e) => {
print_error(format!("Failed to resolve storage path: {e}"));
ptr::null_mut()
}
}
}
/// Whether a wallet already exists at LEZ's canonical home (i.e. its
/// `storage.json` is present). Lets callers decide between an open and a
/// create flow without touching the filesystem or knowing the path.
///
/// # Returns
/// - `true` if the default storage file exists, `false` otherwise (including
/// when the path can't be resolved)
///
/// # Safety
/// This function takes no pointer arguments and is always safe to call.
#[no_mangle]
pub unsafe extern "C" fn wallet_ffi_wallet_exists_default() -> bool {
match wallet::helperfunctions::fetch_persistent_storage_path() {
Ok(path) => path.exists(),
Err(e) => {
print_error(format!("Failed to resolve storage path: {e}"));
false
}
}
}
/// Destroy a wallet handle and free its resources.
///
/// After calling this function, the handle is invalid and must not be used.

View File

@ -1488,6 +1488,86 @@ struct FfiCreateWalletOutput wallet_ffi_create_new(const char *config_path,
*/
struct WalletHandle *wallet_ffi_open(const char *config_path, const char *storage_path);
/**
* Create a new wallet at LEZ's canonical home, deriving the paths from the
* environment (`LEE_WALLET_HOME_DIR` or `~/.lee/wallet`) and creating the
* directory if needed.
*
* This is the path-free equivalent of [`wallet_ffi_create_new`]: callers that
* just want the default location don't have to reconstruct LEZ's path layout.
*
* # Parameters
* - `password`: Password for encrypting the wallet seed
*
* # Returns
* - Result, which contains opaque wallet handle and mnemonic words on success
* - Result with null pointers on error (call `wallet_ffi_get_last_error()` for details)
*
* # Safety
* `password` must be a valid null-terminated UTF-8 string.
*/
struct FfiCreateWalletOutput wallet_ffi_create_new_default(const char *password);
/**
* Open the existing wallet at LEZ's canonical home, deriving the paths from the
* environment (`LEE_WALLET_HOME_DIR` or `~/.lee/wallet`).
*
* This is the path-free equivalent of [`wallet_ffi_open`].
*
* # Returns
* - Opaque wallet handle on success
* - Null pointer on error (call `wallet_ffi_get_last_error()` for details)
*
* # Safety
* This function takes no pointer arguments and is always safe to call.
*/
struct WalletHandle *wallet_ffi_open_default(void);
/**
* Return LEZ's canonical wallet config path (`LEE_WALLET_HOME_DIR` or
* `~/.lee/wallet`, plus `wallet_config.json`).
*
* Lets callers display or inspect the default location without reconstructing
* LEZ's path layout themselves.
*
* # Returns
* - Pointer to null-terminated string on success (caller must free with
* `wallet_ffi_free_string()`)
* - Null pointer on error
*
* # Safety
* This function takes no pointer arguments and is always safe to call.
*/
char *wallet_ffi_default_config_path(void);
/**
* Return LEZ's canonical wallet storage path (`LEE_WALLET_HOME_DIR` or
* `~/.lee/wallet`, plus `storage.json`).
*
* # Returns
* - Pointer to null-terminated string on success (caller must free with
* `wallet_ffi_free_string()`)
* - Null pointer on error
*
* # Safety
* This function takes no pointer arguments and is always safe to call.
*/
char *wallet_ffi_default_storage_path(void);
/**
* Whether a wallet already exists at LEZ's canonical home (i.e. its
* `storage.json` is present). Lets callers decide between an open and a
* create flow without touching the filesystem or knowing the path.
*
* # Returns
* - `true` if the default storage file exists, `false` otherwise (including
* when the path can't be resolved)
*
* # Safety
* This function takes no pointer arguments and is always safe to call.
*/
bool wallet_ffi_wallet_exists_default(void);
/**
* Destroy a wallet handle and free its resources.
*