lssa/wallet-ffi/src/wallet.rs
2026-01-26 10:45:51 +01:00

280 lines
8.0 KiB
Rust

//! Wallet lifecycle management functions.
use std::{
ffi::{c_char, CStr},
path::PathBuf,
ptr,
sync::Mutex,
};
use wallet::WalletCore;
use crate::{
block_on,
error::{print_error, WalletFfiError},
types::WalletHandle,
};
/// Internal wrapper around WalletCore with mutex for thread safety.
pub(crate) struct WalletWrapper {
pub core: Mutex<WalletCore>,
}
/// Helper to get the wallet wrapper from an opaque handle.
pub(crate) fn get_wallet(
handle: *mut WalletHandle,
) -> Result<&'static WalletWrapper, WalletFfiError> {
if handle.is_null() {
print_error("Null wallet handle");
return Err(WalletFfiError::NullPointer);
}
Ok(unsafe { &*(handle as *mut WalletWrapper) })
}
/// Helper to get a mutable reference to the wallet wrapper.
#[allow(dead_code)]
pub(crate) fn get_wallet_mut(
handle: *mut WalletHandle,
) -> Result<&'static mut WalletWrapper, WalletFfiError> {
if handle.is_null() {
print_error("Null wallet handle");
return Err(WalletFfiError::NullPointer);
}
Ok(unsafe { &mut *(handle as *mut WalletWrapper) })
}
/// Helper to convert a C string to a Rust PathBuf.
fn c_str_to_path(ptr: *const c_char, name: &str) -> Result<PathBuf, WalletFfiError> {
if ptr.is_null() {
print_error(format!("Null pointer for {}", name));
return Err(WalletFfiError::NullPointer);
}
let c_str = unsafe { CStr::from_ptr(ptr) };
match c_str.to_str() {
Ok(s) => Ok(PathBuf::from(s)),
Err(e) => {
print_error(format!("Invalid UTF-8 in {}: {}", name, e));
Err(WalletFfiError::InvalidUtf8)
}
}
}
/// Helper to convert a C string to a Rust String.
fn c_str_to_string(ptr: *const c_char, name: &str) -> Result<String, WalletFfiError> {
if ptr.is_null() {
print_error(format!("Null pointer for {}", name));
return Err(WalletFfiError::NullPointer);
}
let c_str = unsafe { CStr::from_ptr(ptr) };
match c_str.to_str() {
Ok(s) => Ok(s.to_string()),
Err(e) => {
print_error(format!("Invalid UTF-8 in {}: {}", name, e));
Err(WalletFfiError::InvalidUtf8)
}
}
}
/// Create a new wallet with fresh storage.
///
/// This initializes a new wallet with a new seed derived from the password.
/// Use this for first-time wallet creation.
///
/// # Parameters
/// - `config_path`: Path to the wallet configuration file (JSON)
/// - `storage_path`: Path where wallet data will be stored
/// - `password`: Password for encrypting the wallet seed
///
/// # Returns
/// - Opaque wallet handle on success
/// - Null pointer on error (call `wallet_ffi_get_last_error()` for details)
///
/// # Safety
/// All string parameters must be valid null-terminated UTF-8 strings.
#[no_mangle]
pub unsafe extern "C" fn wallet_ffi_create_new(
config_path: *const c_char,
storage_path: *const c_char,
password: *const c_char,
) -> *mut WalletHandle {
let config_path = match c_str_to_path(config_path, "config_path") {
Ok(p) => p,
Err(_) => return ptr::null_mut(),
};
let storage_path = match c_str_to_path(storage_path, "storage_path") {
Ok(p) => p,
Err(_) => return ptr::null_mut(),
};
let password = match c_str_to_string(password, "password") {
Ok(s) => s,
Err(_) => return ptr::null_mut(),
};
match WalletCore::new_init_storage(config_path, storage_path, None, password) {
Ok(core) => {
let wrapper = Box::new(WalletWrapper {
core: Mutex::new(core),
});
Box::into_raw(wrapper) as *mut WalletHandle
}
Err(e) => {
print_error(format!("Failed to create wallet: {}", e));
ptr::null_mut()
}
}
}
/// Open an existing wallet from storage.
///
/// This loads a wallet that was previously created with `wallet_ffi_create_new()`.
///
/// # Parameters
/// - `config_path`: Path to the wallet configuration file (JSON)
/// - `storage_path`: Path where wallet data is stored
///
/// # Returns
/// - Opaque wallet handle on success
/// - Null pointer on error (call `wallet_ffi_get_last_error()` for details)
///
/// # Safety
/// All string parameters must be valid null-terminated UTF-8 strings.
#[no_mangle]
pub unsafe extern "C" fn wallet_ffi_open(
config_path: *const c_char,
storage_path: *const c_char,
) -> *mut WalletHandle {
let config_path = match c_str_to_path(config_path, "config_path") {
Ok(p) => p,
Err(_) => return ptr::null_mut(),
};
let storage_path = match c_str_to_path(storage_path, "storage_path") {
Ok(p) => p,
Err(_) => return ptr::null_mut(),
};
match WalletCore::new_update_chain(config_path, storage_path, None) {
Ok(core) => {
let wrapper = Box::new(WalletWrapper {
core: Mutex::new(core),
});
Box::into_raw(wrapper) as *mut WalletHandle
}
Err(e) => {
print_error(format!("Failed to open wallet: {}", e));
ptr::null_mut()
}
}
}
/// Destroy a wallet handle and free its resources.
///
/// After calling this function, the handle is invalid and must not be used.
///
/// # Safety
/// - The handle must be either null or a valid handle from `wallet_ffi_create_new()` or
/// `wallet_ffi_open()`.
/// - The handle must not be used after this call.
#[no_mangle]
pub unsafe extern "C" fn wallet_ffi_destroy(handle: *mut WalletHandle) {
if !handle.is_null() {
unsafe {
drop(Box::from_raw(handle as *mut WalletWrapper));
}
}
}
/// Save wallet state to persistent storage.
///
/// This should be called periodically or after important operations to ensure
/// wallet data is persisted to disk.
///
/// # Parameters
/// - `handle`: Valid wallet handle
///
/// # Returns
/// - `Success` on successful save
/// - Error code on failure
///
/// # Safety
/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open`
#[no_mangle]
pub unsafe extern "C" fn wallet_ffi_save(handle: *mut WalletHandle) -> WalletFfiError {
let wrapper = match get_wallet(handle) {
Ok(w) => w,
Err(e) => return e,
};
let wallet = match wrapper.core.lock() {
Ok(w) => w,
Err(e) => {
print_error(format!("Failed to lock wallet: {}", e));
return WalletFfiError::InternalError;
}
};
match block_on(wallet.store_persistent_data()) {
Ok(Ok(())) => WalletFfiError::Success,
Ok(Err(e)) => {
print_error(format!("Failed to save wallet: {}", e));
WalletFfiError::StorageError
}
Err(e) => e,
}
}
/// Get the sequencer address from the wallet configuration.
///
/// # Parameters
/// - `handle`: Valid wallet handle
///
/// # Returns
/// - Pointer to null-terminated string on success (caller must free with
/// `wallet_ffi_free_string()`)
/// - Null pointer on error
///
/// # Safety
/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open`
#[no_mangle]
pub unsafe extern "C" fn wallet_ffi_get_sequencer_addr(handle: *mut WalletHandle) -> *mut c_char {
let wrapper = match get_wallet(handle) {
Ok(w) => w,
Err(_) => return ptr::null_mut(),
};
let wallet = match wrapper.core.lock() {
Ok(w) => w,
Err(e) => {
print_error(format!("Failed to lock wallet: {}", e));
return ptr::null_mut();
}
};
let addr = wallet.config().sequencer_addr.clone();
match std::ffi::CString::new(addr) {
Ok(s) => s.into_raw(),
Err(e) => {
print_error(format!("Invalid sequencer address: {}", e));
ptr::null_mut()
}
}
}
/// Free a string returned by wallet FFI functions.
///
/// # Safety
/// The pointer must be either null or a valid string returned by an FFI function.
#[no_mangle]
pub unsafe extern "C" fn wallet_ffi_free_string(ptr: *mut c_char) {
if !ptr.is_null() {
unsafe {
drop(std::ffi::CString::from_raw(ptr));
}
}
}