From 17b36b924eabe2088165ce5515f69dee26075c32 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 21 Jan 2026 15:22:38 +0100 Subject: [PATCH 1/9] Vibecoded wallet-ffi --- Cargo.lock | 132 +++++++- Cargo.toml | 4 + wallet-ffi/Cargo.toml | 17 + wallet-ffi/build.rs | 13 + wallet-ffi/cbindgen.toml | 40 +++ wallet-ffi/src/account.rs | 381 ++++++++++++++++++++++ wallet-ffi/src/error.rs | 93 ++++++ wallet-ffi/src/keys.rs | 236 ++++++++++++++ wallet-ffi/src/lib.rs | 92 ++++++ wallet-ffi/src/sync.rs | 138 ++++++++ wallet-ffi/src/transfer.rs | 188 +++++++++++ wallet-ffi/src/types.rs | 167 ++++++++++ wallet-ffi/src/wallet.rs | 264 +++++++++++++++ wallet-ffi/wallet_ffi.h | 646 +++++++++++++++++++++++++++++++++++++ 14 files changed, 2399 insertions(+), 12 deletions(-) create mode 100644 wallet-ffi/Cargo.toml create mode 100644 wallet-ffi/build.rs create mode 100644 wallet-ffi/cbindgen.toml create mode 100644 wallet-ffi/src/account.rs create mode 100644 wallet-ffi/src/error.rs create mode 100644 wallet-ffi/src/keys.rs create mode 100644 wallet-ffi/src/lib.rs create mode 100644 wallet-ffi/src/sync.rs create mode 100644 wallet-ffi/src/transfer.rs create mode 100644 wallet-ffi/src/types.rs create mode 100644 wallet-ffi/src/wallet.rs create mode 100644 wallet-ffi/wallet_ffi.h diff --git a/Cargo.lock b/Cargo.lock index 40434dcc..b4784794 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -621,6 +621,17 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -881,6 +892,25 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cbindgen" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da6bc11b07529f16944307272d5bd9b22530bc7d05751717c9d416586cedab49" +dependencies = [ + "clap 3.2.25", + "heck 0.4.1", + "indexmap 1.9.3", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 1.0.109", + "tempfile", + "toml 0.5.11", +] + [[package]] name = "cc" version = "1.2.49" @@ -960,6 +990,21 @@ dependencies = [ "libloading", ] +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_lex 0.2.4", + "indexmap 1.9.3", + "strsim 0.10.0", + "termcolor", + "textwrap", +] + [[package]] name = "clap" version = "4.5.53" @@ -978,8 +1023,8 @@ checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", - "clap_lex", - "strsim", + "clap_lex 0.7.6", + "strsim 0.11.1", ] [[package]] @@ -988,12 +1033,21 @@ version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.111", ] +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "clap_lex" version = "0.7.6" @@ -1180,7 +1234,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.11.1", "syn 2.0.111", ] @@ -1194,7 +1248,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.11.1", "syn 2.0.111", ] @@ -1381,7 +1435,7 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e92f10a49176cbffacaedabfaa11d51db1ea0f80a83c26e1873b43cd1742c24" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "proc-macro2-diagnostics", ] @@ -1834,12 +1888,27 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.5.2" @@ -2289,7 +2358,7 @@ version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ - "hermit-abi", + "hermit-abi 0.5.2", "libc", "windows-sys 0.61.2", ] @@ -2859,6 +2928,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + [[package]] name = "parking_lot" version = "0.12.5" @@ -3030,7 +3105,7 @@ dependencies = [ name = "program_deployment" version = "0.1.0" dependencies = [ - "clap", + "clap 4.5.53", "nssa", "nssa_core", "tokio", @@ -3784,7 +3859,7 @@ dependencies = [ "strum", "tempfile", "thiserror", - "toml", + "toml 0.8.23", "yaml-rust2", ] @@ -3946,7 +4021,7 @@ dependencies = [ "actix", "actix-web", "anyhow", - "clap", + "clap 4.5.53", "common", "env_logger", "log", @@ -4191,6 +4266,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + [[package]] name = "strsim" version = "0.11.1" @@ -4212,7 +4293,7 @@ version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" dependencies = [ - "heck", + "heck 0.5.0", "proc-macro2", "quote", "syn 2.0.111", @@ -4330,6 +4411,12 @@ dependencies = [ "risc0-zkvm", ] +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" + [[package]] name = "thiserror" version = "2.0.17" @@ -4467,6 +4554,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "toml" version = "0.8.23" @@ -4750,7 +4846,7 @@ dependencies = [ "base64 0.22.1", "borsh", "bytemuck", - "clap", + "clap 4.5.53", "common", "env_logger", "futures", @@ -4770,6 +4866,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "wallet-ffi" +version = "0.1.0" +dependencies = [ + "cbindgen", + "common", + "nssa", + "once_cell", + "tokio", + "wallet", +] + [[package]] name = "want" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index ef1b881d..dffff452 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "sequencer_rpc", "mempool", "wallet", + "wallet-ffi", "sequencer_core", "common", "nssa", @@ -32,8 +33,11 @@ sequencer_core = { path = "sequencer_core" } sequencer_rpc = { path = "sequencer_rpc" } sequencer_runner = { path = "sequencer_runner" } wallet = { path = "wallet" } +wallet-ffi = { path = "wallet-ffi" } test_program_methods = { path = "test_program_methods" } +once_cell = "1.19" + tokio = { version = "1.28.2", features = [ "net", "rt-multi-thread", diff --git a/wallet-ffi/Cargo.toml b/wallet-ffi/Cargo.toml new file mode 100644 index 00000000..ae1ba57e --- /dev/null +++ b/wallet-ffi/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "wallet-ffi" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "staticlib"] + +[dependencies] +wallet.workspace = true +nssa.workspace = true +common.workspace = true +tokio.workspace = true +once_cell.workspace = true + +[build-dependencies] +cbindgen = "0.26" diff --git a/wallet-ffi/build.rs b/wallet-ffi/build.rs new file mode 100644 index 00000000..2d1b0cca --- /dev/null +++ b/wallet-ffi/build.rs @@ -0,0 +1,13 @@ +fn main() { + let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + + let config = cbindgen::Config::from_file("cbindgen.toml") + .expect("Unable to read cbindgen.toml"); + + cbindgen::Builder::new() + .with_crate(crate_dir) + .with_config(config) + .generate() + .expect("Unable to generate bindings") + .write_to_file("wallet_ffi.h"); +} diff --git a/wallet-ffi/cbindgen.toml b/wallet-ffi/cbindgen.toml new file mode 100644 index 00000000..42c46543 --- /dev/null +++ b/wallet-ffi/cbindgen.toml @@ -0,0 +1,40 @@ +language = "C" +header = """ +/** + * NSSA Wallet FFI Bindings + * + * Thread Safety: All functions are thread-safe. The wallet handle can be + * shared across threads, but operations are serialized internally. + * + * Memory Management: + * - Functions returning pointers allocate memory that must be freed + * - Use the corresponding wallet_ffi_free_* function to free memory + * - Never free memory returned by FFI using standard C free() + * + * Error Handling: + * - Functions return WalletFfiError codes + * - On error, call wallet_ffi_get_last_error() for detailed message + * - The error string must be freed with wallet_ffi_free_error_string() + * + * Initialization: + * 1. Call wallet_ffi_init_runtime() before any other function + * 2. Create wallet with wallet_ffi_create_new() or wallet_ffi_open() + * 3. Destroy wallet with wallet_ffi_destroy() when done + */ +""" + +include_guard = "WALLET_FFI_H" +include_version = true +no_includes = false + +[export] +include = ["Ffi.*", "WalletFfiError", "WalletHandle"] + +[enum] +rename_variants = "ScreamingSnakeCase" + +[fn] +rename_args = "None" + +[struct] +rename_fields = "None" diff --git a/wallet-ffi/src/account.rs b/wallet-ffi/src/account.rs new file mode 100644 index 00000000..53abb54f --- /dev/null +++ b/wallet-ffi/src/account.rs @@ -0,0 +1,381 @@ +//! Account management functions. + +use std::ptr; + +use nssa::AccountId; + +use crate::block_on; +use crate::error::{set_last_error, WalletFfiError}; +use crate::types::{ + split_u128, FfiAccount, FfiAccountList, FfiAccountListEntry, FfiBytes32, FfiProgramId, + WalletHandle, +}; +use crate::wallet::get_wallet; + +/// Create a new public account. +/// +/// Public accounts use standard transaction signing and are suitable for +/// non-private operations. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `out_account_id`: Output pointer for the new account ID (32 bytes) +/// +/// # Returns +/// - `Success` on successful creation +/// - Error code on failure +#[no_mangle] +pub extern "C" fn wallet_ffi_create_account_public( + handle: *mut WalletHandle, + out_account_id: *mut FfiBytes32, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + if out_account_id.is_null() { + set_last_error("Null output pointer for account_id"); + return WalletFfiError::NullPointer; + } + + let mut wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + set_last_error(format!("Failed to lock wallet: {}", e)); + return WalletFfiError::InternalError; + } + }; + + let (account_id, _chain_index) = wallet.create_new_account_public(None); + + unsafe { + (*out_account_id).data = *account_id.value(); + } + + WalletFfiError::Success +} + +/// Create a new private account. +/// +/// Private accounts use privacy-preserving transactions with nullifiers +/// and commitments. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `out_account_id`: Output pointer for the new account ID (32 bytes) +/// +/// # Returns +/// - `Success` on successful creation +/// - Error code on failure +#[no_mangle] +pub extern "C" fn wallet_ffi_create_account_private( + handle: *mut WalletHandle, + out_account_id: *mut FfiBytes32, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + if out_account_id.is_null() { + set_last_error("Null output pointer for account_id"); + return WalletFfiError::NullPointer; + } + + let mut wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + set_last_error(format!("Failed to lock wallet: {}", e)); + return WalletFfiError::InternalError; + } + }; + + let (account_id, _chain_index) = wallet.create_new_account_private(None); + + unsafe { + (*out_account_id).data = *account_id.value(); + } + + WalletFfiError::Success +} + +/// List all accounts in the wallet. +/// +/// Returns both public and private accounts managed by this wallet. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `out_list`: Output pointer for the account list +/// +/// # Returns +/// - `Success` on successful listing +/// - Error code on failure +/// +/// # Memory +/// The returned list must be freed with `wallet_ffi_free_account_list()`. +#[no_mangle] +pub extern "C" fn wallet_ffi_list_accounts( + handle: *mut WalletHandle, + out_list: *mut FfiAccountList, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + if out_list.is_null() { + set_last_error("Null output pointer for account list"); + return WalletFfiError::NullPointer; + } + + let wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + set_last_error(format!("Failed to lock wallet: {}", e)); + return WalletFfiError::InternalError; + } + }; + + let user_data = &wallet.storage().user_data; + let mut entries = Vec::new(); + + // Public accounts from default signing keys (preconfigured) + for account_id in user_data.default_pub_account_signing_keys.keys() { + entries.push(FfiAccountListEntry { + account_id: FfiBytes32::from_account_id(account_id), + is_public: true, + }); + } + + // Public accounts from key tree (generated) + for account_id in user_data.public_key_tree.account_id_map.keys() { + entries.push(FfiAccountListEntry { + account_id: FfiBytes32::from_account_id(account_id), + is_public: true, + }); + } + + // Private accounts from default accounts (preconfigured) + for account_id in user_data.default_user_private_accounts.keys() { + entries.push(FfiAccountListEntry { + account_id: FfiBytes32::from_account_id(account_id), + is_public: false, + }); + } + + // Private accounts from key tree (generated) + for account_id in user_data.private_key_tree.account_id_map.keys() { + entries.push(FfiAccountListEntry { + account_id: FfiBytes32::from_account_id(account_id), + is_public: false, + }); + } + + let count = entries.len(); + + if count == 0 { + unsafe { + (*out_list).entries = ptr::null_mut(); + (*out_list).count = 0; + } + } else { + let entries_boxed = entries.into_boxed_slice(); + let entries_ptr = Box::into_raw(entries_boxed) as *mut FfiAccountListEntry; + + unsafe { + (*out_list).entries = entries_ptr; + (*out_list).count = count; + } + } + + WalletFfiError::Success +} + +/// Free an account list returned by `wallet_ffi_list_accounts`. +/// +/// # Safety +/// The list must be either null or a valid list returned by `wallet_ffi_list_accounts`. +#[no_mangle] +pub extern "C" fn wallet_ffi_free_account_list(list: *mut FfiAccountList) { + if list.is_null() { + return; + } + + unsafe { + let list = &*list; + if !list.entries.is_null() && list.count > 0 { + let slice = std::slice::from_raw_parts_mut(list.entries, list.count); + drop(Box::from_raw(slice as *mut [FfiAccountListEntry])); + } + } +} + +/// Get account balance. +/// +/// For public accounts, this fetches the balance from the network. +/// For private accounts, this returns the locally cached balance. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `account_id`: The account ID (32 bytes) +/// - `is_public`: Whether this is a public account +/// - `out_balance_lo`: Output for lower 64 bits of balance +/// - `out_balance_hi`: Output for upper 64 bits of balance +/// +/// # Returns +/// - `Success` on successful query +/// - Error code on failure +#[no_mangle] +pub extern "C" fn wallet_ffi_get_balance( + handle: *mut WalletHandle, + account_id: *const FfiBytes32, + is_public: bool, + out_balance_lo: *mut u64, + out_balance_hi: *mut u64, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + if account_id.is_null() || out_balance_lo.is_null() || out_balance_hi.is_null() { + set_last_error("Null pointer argument"); + return WalletFfiError::NullPointer; + } + + let wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + set_last_error(format!("Failed to lock wallet: {}", e)); + return WalletFfiError::InternalError; + } + }; + + let account_id = AccountId::new(unsafe { (*account_id).data }); + + let balance = if is_public { + match block_on(wallet.get_account_balance(account_id)) { + Ok(Ok(b)) => b, + Ok(Err(e)) => { + set_last_error(format!("Failed to get balance: {}", e)); + return WalletFfiError::NetworkError; + } + Err(e) => return e, + } + } else { + match wallet.get_account_private(&account_id) { + Some(account) => account.balance, + None => { + set_last_error("Private account not found"); + return WalletFfiError::AccountNotFound; + } + } + }; + + let (lo, hi) = split_u128(balance); + unsafe { + *out_balance_lo = lo; + *out_balance_hi = hi; + } + + WalletFfiError::Success +} + +/// Get full public account data from the network. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `account_id`: The account ID (32 bytes) +/// - `out_account`: Output pointer for account data +/// +/// # Returns +/// - `Success` on successful query +/// - Error code on failure +/// +/// # Memory +/// The account data must be freed with `wallet_ffi_free_account_data()`. +#[no_mangle] +pub extern "C" fn wallet_ffi_get_account_public( + handle: *mut WalletHandle, + account_id: *const FfiBytes32, + out_account: *mut FfiAccount, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + if account_id.is_null() || out_account.is_null() { + set_last_error("Null pointer argument"); + return WalletFfiError::NullPointer; + } + + let wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + set_last_error(format!("Failed to lock wallet: {}", e)); + return WalletFfiError::InternalError; + } + }; + + let account_id = AccountId::new(unsafe { (*account_id).data }); + + let account = match block_on(wallet.get_account_public(account_id)) { + Ok(Ok(a)) => a, + Ok(Err(e)) => { + set_last_error(format!("Failed to get account: {}", e)); + return WalletFfiError::NetworkError; + } + Err(e) => return e, + }; + + // Convert account data to FFI type + let data_vec: Vec = account.data.into(); + let data_len = data_vec.len(); + let data_ptr = if data_len > 0 { + let data_boxed = data_vec.into_boxed_slice(); + Box::into_raw(data_boxed) as *const u8 + } else { + ptr::null() + }; + + let (balance_lo, balance_hi) = split_u128(account.balance); + let (nonce_lo, nonce_hi) = split_u128(account.nonce); + + let program_owner = FfiProgramId { + data: account.program_owner, + }; + + unsafe { + (*out_account).program_owner = program_owner; + (*out_account).balance_lo = balance_lo; + (*out_account).balance_hi = balance_hi; + (*out_account).nonce_lo = nonce_lo; + (*out_account).nonce_hi = nonce_hi; + (*out_account).data = data_ptr; + (*out_account).data_len = data_len; + } + + WalletFfiError::Success +} + +/// Free account data returned by `wallet_ffi_get_account_public`. +/// +/// # Safety +/// The account must be either null or a valid account returned by +/// `wallet_ffi_get_account_public`. +#[no_mangle] +pub extern "C" fn wallet_ffi_free_account_data(account: *mut FfiAccount) { + if account.is_null() { + return; + } + + unsafe { + let account = &*account; + if !account.data.is_null() && account.data_len > 0 { + let slice = std::slice::from_raw_parts_mut(account.data as *mut u8, account.data_len); + drop(Box::from_raw(slice as *mut [u8])); + } + } +} diff --git a/wallet-ffi/src/error.rs b/wallet-ffi/src/error.rs new file mode 100644 index 00000000..46646259 --- /dev/null +++ b/wallet-ffi/src/error.rs @@ -0,0 +1,93 @@ +//! Error handling for the FFI layer. +//! +//! Uses numeric error codes with a thread-local last error message. + +use std::cell::RefCell; +use std::ffi::{c_char, CString}; +use std::ptr; + +/// Error codes returned by FFI functions. +#[repr(C)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum WalletFfiError { + /// Operation completed successfully + Success = 0, + /// A null pointer was passed where a valid pointer was expected + NullPointer = 1, + /// Invalid UTF-8 string + InvalidUtf8 = 2, + /// Wallet handle is not initialized + WalletNotInitialized = 3, + /// Configuration error + ConfigError = 4, + /// Storage/persistence error + StorageError = 5, + /// Network/RPC error + NetworkError = 6, + /// Account not found + AccountNotFound = 7, + /// Key not found for account + KeyNotFound = 8, + /// Insufficient funds for operation + InsufficientFunds = 9, + /// Invalid account ID format + InvalidAccountId = 10, + /// Tokio runtime error + RuntimeError = 11, + /// Password required but not provided + PasswordRequired = 12, + /// Block synchronization error + SyncError = 13, + /// Serialization/deserialization error + SerializationError = 14, + /// Internal error (catch-all) + InternalError = 99, +} + +// Thread-local storage for the last error message +thread_local! { + static LAST_ERROR: RefCell> = const { RefCell::new(None) }; +} + +/// Set the last error message for the current thread. +pub fn set_last_error(msg: impl Into) { + LAST_ERROR.with(|e| { + *e.borrow_mut() = Some(msg.into()); + }); +} + +/// Clear the last error message. +pub fn clear_last_error() { + LAST_ERROR.with(|e| { + *e.borrow_mut() = None; + }); +} + +/// Get the last error message. +/// +/// Returns a pointer to a null-terminated string, or null if no error is set. +/// The caller owns the returned string and must free it with +/// `wallet_ffi_free_error_string`. +#[no_mangle] +pub extern "C" fn wallet_ffi_get_last_error() -> *mut c_char { + LAST_ERROR.with(|e| match e.borrow_mut().take() { + Some(msg) => CString::new(msg) + .map(|s| s.into_raw()) + .unwrap_or(ptr::null_mut()), + None => ptr::null_mut(), + }) +} + +/// Free an error string returned by `wallet_ffi_get_last_error`. +/// +/// # Safety +/// The pointer must be either null or a valid pointer returned by +/// `wallet_ffi_get_last_error`. +#[no_mangle] +pub extern "C" fn wallet_ffi_free_error_string(ptr: *mut c_char) { + if !ptr.is_null() { + unsafe { + drop(CString::from_raw(ptr)); + } + } +} diff --git a/wallet-ffi/src/keys.rs b/wallet-ffi/src/keys.rs new file mode 100644 index 00000000..d4a29e11 --- /dev/null +++ b/wallet-ffi/src/keys.rs @@ -0,0 +1,236 @@ +//! Key retrieval functions. + +use std::ptr; + +use nssa::{AccountId, PublicKey}; + +use crate::error::{set_last_error, WalletFfiError}; +use crate::types::{FfiBytes32, FfiPrivateAccountKeys, FfiPublicAccountKey, WalletHandle}; +use crate::wallet::get_wallet; + +/// Get the public key for a public account. +/// +/// This returns the public key derived from the account's signing key. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `account_id`: The account ID (32 bytes) +/// - `out_public_key`: Output pointer for the public key +/// +/// # Returns +/// - `Success` on successful retrieval +/// - `KeyNotFound` if the account's key is not in this wallet +/// - Error code on other failures +#[no_mangle] +pub extern "C" fn wallet_ffi_get_public_account_key( + handle: *mut WalletHandle, + account_id: *const FfiBytes32, + out_public_key: *mut FfiPublicAccountKey, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + if account_id.is_null() || out_public_key.is_null() { + set_last_error("Null pointer argument"); + return WalletFfiError::NullPointer; + } + + let wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + set_last_error(format!("Failed to lock wallet: {}", e)); + return WalletFfiError::InternalError; + } + }; + + let account_id = AccountId::new(unsafe { (*account_id).data }); + + let private_key = match wallet.get_account_public_signing_key(&account_id) { + Some(k) => k, + None => { + set_last_error("Public account key not found in wallet"); + return WalletFfiError::KeyNotFound; + } + }; + + let public_key = PublicKey::new_from_private_key(private_key); + + unsafe { + (*out_public_key).public_key.data = *public_key.value(); + } + + WalletFfiError::Success +} + +/// Get keys for a private account. +/// +/// Returns the nullifier public key (NPK) and incoming viewing public key (IPK) +/// for the specified private account. These keys are safe to share publicly. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `account_id`: The account ID (32 bytes) +/// - `out_keys`: Output pointer for the key data +/// +/// # Returns +/// - `Success` on successful retrieval +/// - `AccountNotFound` if the private account is not in this wallet +/// - Error code on other failures +/// +/// # Memory +/// The keys structure must be freed with `wallet_ffi_free_private_account_keys()`. +#[no_mangle] +pub extern "C" fn wallet_ffi_get_private_account_keys( + handle: *mut WalletHandle, + account_id: *const FfiBytes32, + out_keys: *mut FfiPrivateAccountKeys, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + if account_id.is_null() || out_keys.is_null() { + set_last_error("Null pointer argument"); + return WalletFfiError::NullPointer; + } + + let wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + set_last_error(format!("Failed to lock wallet: {}", e)); + return WalletFfiError::InternalError; + } + }; + + let account_id = AccountId::new(unsafe { (*account_id).data }); + + let (key_chain, _account) = match wallet.storage().user_data.get_private_account(&account_id) { + Some(k) => k, + None => { + set_last_error("Private account not found in wallet"); + return WalletFfiError::AccountNotFound; + } + }; + + // NPK is a 32-byte array + let npk_bytes = key_chain.nullifer_public_key.0; + + // IPK is a compressed secp256k1 point (33 bytes) + let ipk_bytes = key_chain.incoming_viewing_public_key.to_bytes(); + let ipk_len = ipk_bytes.len(); + let ipk_vec = ipk_bytes.to_vec(); + let ipk_boxed = ipk_vec.into_boxed_slice(); + let ipk_ptr = Box::into_raw(ipk_boxed) as *const u8; + + unsafe { + (*out_keys).nullifier_public_key.data = npk_bytes; + (*out_keys).incoming_viewing_public_key = ipk_ptr; + (*out_keys).incoming_viewing_public_key_len = ipk_len; + } + + WalletFfiError::Success +} + +/// Free private account keys returned by `wallet_ffi_get_private_account_keys`. +/// +/// # Safety +/// The keys must be either null or valid keys returned by +/// `wallet_ffi_get_private_account_keys`. +#[no_mangle] +pub extern "C" fn wallet_ffi_free_private_account_keys(keys: *mut FfiPrivateAccountKeys) { + if keys.is_null() { + return; + } + + unsafe { + let keys = &*keys; + if !keys.incoming_viewing_public_key.is_null() + && keys.incoming_viewing_public_key_len > 0 + { + let slice = std::slice::from_raw_parts_mut( + keys.incoming_viewing_public_key as *mut u8, + keys.incoming_viewing_public_key_len, + ); + drop(Box::from_raw(slice as *mut [u8])); + } + } +} + +/// Convert an account ID to a Base58 string. +/// +/// # Parameters +/// - `account_id`: The account ID (32 bytes) +/// +/// # Returns +/// - Pointer to null-terminated Base58 string on success +/// - Null pointer on error +/// +/// # Memory +/// The returned string must be freed with `wallet_ffi_free_string()`. +#[no_mangle] +pub extern "C" fn wallet_ffi_account_id_to_base58( + account_id: *const FfiBytes32, +) -> *mut std::ffi::c_char { + if account_id.is_null() { + set_last_error("Null account_id pointer"); + return ptr::null_mut(); + } + + let account_id = AccountId::new(unsafe { (*account_id).data }); + let base58_str = account_id.to_string(); + + match std::ffi::CString::new(base58_str) { + Ok(s) => s.into_raw(), + Err(e) => { + set_last_error(format!("Failed to create C string: {}", e)); + ptr::null_mut() + } + } +} + +/// Parse a Base58 string into an account ID. +/// +/// # Parameters +/// - `base58_str`: Null-terminated Base58 string +/// - `out_account_id`: Output pointer for the account ID (32 bytes) +/// +/// # Returns +/// - `Success` on successful parsing +/// - `InvalidAccountId` if the string is not valid Base58 +/// - Error code on other failures +#[no_mangle] +pub extern "C" fn wallet_ffi_account_id_from_base58( + base58_str: *const std::ffi::c_char, + out_account_id: *mut FfiBytes32, +) -> WalletFfiError { + if base58_str.is_null() || out_account_id.is_null() { + set_last_error("Null pointer argument"); + return WalletFfiError::NullPointer; + } + + let c_str = unsafe { std::ffi::CStr::from_ptr(base58_str) }; + let str_slice = match c_str.to_str() { + Ok(s) => s, + Err(e) => { + set_last_error(format!("Invalid UTF-8: {}", e)); + return WalletFfiError::InvalidUtf8; + } + }; + + let account_id: AccountId = match str_slice.parse() { + Ok(id) => id, + Err(e) => { + set_last_error(format!("Invalid Base58 account ID: {}", e)); + return WalletFfiError::InvalidAccountId; + } + }; + + unsafe { + (*out_account_id).data = *account_id.value(); + } + + WalletFfiError::Success +} diff --git a/wallet-ffi/src/lib.rs b/wallet-ffi/src/lib.rs new file mode 100644 index 00000000..2b5713b7 --- /dev/null +++ b/wallet-ffi/src/lib.rs @@ -0,0 +1,92 @@ +//! NSSA Wallet FFI Library +//! +//! This crate provides C-compatible bindings for the NSSA wallet functionality. +//! +//! # Usage +//! +//! 1. Initialize the runtime with `wallet_ffi_init_runtime()` +//! 2. Create or open a wallet with `wallet_ffi_create_new()` or `wallet_ffi_open()` +//! 3. Use the wallet functions to manage accounts and transfers +//! 4. Destroy the wallet with `wallet_ffi_destroy()` when done +//! +//! # Thread Safety +//! +//! All functions are thread-safe. The wallet handle uses internal locking +//! to ensure safe concurrent access. +//! +//! # Memory Management +//! +//! - Functions returning pointers allocate memory that must be freed +//! - Use the corresponding `wallet_ffi_free_*` function to free memory +//! - Never free memory returned by FFI using standard C `free()` + +pub mod account; +pub mod error; +pub mod keys; +pub mod sync; +pub mod transfer; +pub mod types; +pub mod wallet; + +use once_cell::sync::OnceCell; +use std::sync::Arc; +use tokio::runtime::Runtime; + +use crate::error::{set_last_error, WalletFfiError}; + +// Re-export public types for cbindgen +pub use error::WalletFfiError as FfiError; +pub use types::*; + +// Global Tokio runtime - initialized once +static RUNTIME: OnceCell> = OnceCell::new(); + +/// Get a reference to the global runtime. +pub(crate) fn get_runtime() -> Result<&'static Arc, WalletFfiError> { + RUNTIME.get().ok_or_else(|| { + set_last_error("Runtime not initialized. Call wallet_ffi_init_runtime() first."); + WalletFfiError::RuntimeError + }) +} + +/// Run an async future on the global runtime, blocking until completion. +pub(crate) fn block_on(future: F) -> Result { + let runtime = get_runtime()?; + Ok(runtime.block_on(future)) +} + +/// Initialize the global Tokio runtime. +/// +/// This must be called before any async operations (like network calls). +/// Safe to call multiple times - subsequent calls are no-ops. +/// +/// # Returns +/// - `Success` if the runtime was initialized or already exists +/// - `RuntimeError` if runtime creation failed +#[no_mangle] +pub extern "C" fn wallet_ffi_init_runtime() -> WalletFfiError { + let result = RUNTIME.get_or_try_init(|| { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .map(Arc::new) + }); + + match result { + Ok(_) => WalletFfiError::Success, + Err(e) => { + set_last_error(format!("Failed to initialize runtime: {}", e)); + WalletFfiError::RuntimeError + } + } +} + +/// Check if the runtime is initialized. +/// +/// # Returns +/// - `true` if the runtime is ready +/// - `false` if `wallet_ffi_init_runtime()` hasn't been called yet +#[no_mangle] +pub extern "C" fn wallet_ffi_runtime_initialized() -> bool { + RUNTIME.get().is_some() +} diff --git a/wallet-ffi/src/sync.rs b/wallet-ffi/src/sync.rs new file mode 100644 index 00000000..b4e9deb8 --- /dev/null +++ b/wallet-ffi/src/sync.rs @@ -0,0 +1,138 @@ +//! Block synchronization functions. + +use crate::block_on; +use crate::error::{set_last_error, WalletFfiError}; +use crate::types::WalletHandle; +use crate::wallet::get_wallet; + +/// Synchronize private accounts to a specific block. +/// +/// This scans the blockchain from the last synced block to the specified block, +/// updating private account balances based on any relevant transactions. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `block_id`: Target block number to sync to +/// +/// # Returns +/// - `Success` if synchronization completed +/// - `SyncError` if synchronization failed +/// - Error code on other failures +/// +/// # Note +/// This operation can take a while for large block ranges. The wallet +/// internally uses a progress bar which may output to stdout. +#[no_mangle] +pub extern "C" fn wallet_ffi_sync_to_block( + handle: *mut WalletHandle, + block_id: u64, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + let mut wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + set_last_error(format!("Failed to lock wallet: {}", e)); + return WalletFfiError::InternalError; + } + }; + + match block_on(wallet.sync_to_block(block_id)) { + Ok(Ok(())) => WalletFfiError::Success, + Ok(Err(e)) => { + set_last_error(format!("Sync failed: {}", e)); + WalletFfiError::SyncError + } + Err(e) => e, + } +} + +/// Get the last synced block number. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `out_block_id`: Output pointer for the block number +/// +/// # Returns +/// - `Success` on success +/// - Error code on failure +#[no_mangle] +pub extern "C" fn wallet_ffi_get_last_synced_block( + handle: *mut WalletHandle, + out_block_id: *mut u64, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + if out_block_id.is_null() { + set_last_error("Null output pointer"); + return WalletFfiError::NullPointer; + } + + let wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + set_last_error(format!("Failed to lock wallet: {}", e)); + return WalletFfiError::InternalError; + } + }; + + unsafe { + *out_block_id = wallet.last_synced_block; + } + + WalletFfiError::Success +} + +/// Get the current block height from the sequencer. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `out_block_height`: Output pointer for the current block height +/// +/// # Returns +/// - `Success` on success +/// - `NetworkError` if the sequencer is unreachable +/// - Error code on other failures +#[no_mangle] +pub extern "C" fn wallet_ffi_get_current_block_height( + handle: *mut WalletHandle, + out_block_height: *mut u64, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + if out_block_height.is_null() { + set_last_error("Null output pointer"); + return WalletFfiError::NullPointer; + } + + let wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + set_last_error(format!("Failed to lock wallet: {}", e)); + return WalletFfiError::InternalError; + } + }; + + match block_on(wallet.sequencer_client.get_last_block()) { + Ok(Ok(response)) => { + unsafe { + *out_block_height = response.last_block; + } + WalletFfiError::Success + } + Ok(Err(e)) => { + set_last_error(format!("Failed to get block height: {:?}", e)); + WalletFfiError::NetworkError + } + Err(e) => e, + } +} diff --git a/wallet-ffi/src/transfer.rs b/wallet-ffi/src/transfer.rs new file mode 100644 index 00000000..d52ee9a8 --- /dev/null +++ b/wallet-ffi/src/transfer.rs @@ -0,0 +1,188 @@ +//! Token transfer functions. + +use std::ffi::{CString, c_ulonglong}; +use std::ptr; + +use common::error::ExecutionFailureKind; +use nssa::AccountId; +use wallet::program_facades::native_token_transfer::NativeTokenTransfer; + +use crate::block_on; +use crate::error::{set_last_error, WalletFfiError}; +use crate::types::{combine_u128, FfiBytes32, FfiTransferResult, WalletHandle}; +use crate::wallet::get_wallet; + +/// Send a public token transfer. +/// +/// Transfers tokens from one public account to another on the network. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `from`: Source account ID (must be owned by this wallet) +/// - `to`: Destination account ID +/// - `amount_lo`: Lower 64 bits of amount to transfer +/// - `amount_hi`: Upper 64 bits of amount to transfer +/// - `out_result`: Output pointer for transfer result +/// +/// # Returns +/// - `Success` if the transfer was submitted successfully +/// - `InsufficientFunds` if the source account doesn't have enough balance +/// - `KeyNotFound` if the source account's signing key is not in this wallet +/// - Error code on other failures +/// +/// # Memory +/// The result must be freed with `wallet_ffi_free_transfer_result()`. +#[no_mangle] +pub extern "C" fn wallet_ffi_transfer_public( + handle: *mut WalletHandle, + from: *const FfiBytes32, + to: *const FfiBytes32, + amount_lo: u64, + amount_hi: u64, + out_result: *mut FfiTransferResult, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + if from.is_null() || to.is_null() || out_result.is_null() { + set_last_error("Null pointer argument"); + return WalletFfiError::NullPointer; + } + + let wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + set_last_error(format!("Failed to lock wallet: {}", e)); + return WalletFfiError::InternalError; + } + }; + + let from_id = AccountId::new(unsafe { (*from).data }); + let to_id = AccountId::new(unsafe { (*to).data }); + let amount = combine_u128(amount_lo, amount_hi); + + let transfer = NativeTokenTransfer(&wallet); + + match block_on(transfer.send_public_transfer(from_id, to_id, amount)) { + Ok(Ok(response)) => { + let tx_hash = CString::new(response.tx_hash) + .map(|s| s.into_raw()) + .unwrap_or(ptr::null_mut()); + + unsafe { + (*out_result).tx_hash = tx_hash; + (*out_result).success = true; + } + WalletFfiError::Success + } + Ok(Err(e)) => { + set_last_error(format!("Transfer failed: {:?}", e)); + unsafe { + (*out_result).tx_hash = ptr::null_mut(); + (*out_result).success = false; + } + match e { + ExecutionFailureKind::InsufficientFundsError => WalletFfiError::InsufficientFunds, + ExecutionFailureKind::KeyNotFoundError => WalletFfiError::KeyNotFound, + ExecutionFailureKind::SequencerError => WalletFfiError::NetworkError, + ExecutionFailureKind::SequencerClientError(_) => WalletFfiError::NetworkError, + _ => WalletFfiError::InternalError, + } + } + Err(e) => e, + } +} + +/// Register a public account on the network. +/// +/// This initializes a public account on the blockchain. The account must be +/// owned by this wallet. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `account_id`: Account ID to register +/// - `out_result`: Output pointer for registration result +/// +/// # Returns +/// - `Success` if the registration was submitted successfully +/// - Error code on failure +/// +/// # Memory +/// The result must be freed with `wallet_ffi_free_transfer_result()`. +#[no_mangle] +pub extern "C" fn wallet_ffi_register_public_account( + handle: *mut WalletHandle, + account_id: *const FfiBytes32, + out_result: *mut FfiTransferResult, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + if account_id.is_null() || out_result.is_null() { + set_last_error("Null pointer argument"); + return WalletFfiError::NullPointer; + } + + let wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + set_last_error(format!("Failed to lock wallet: {}", e)); + return WalletFfiError::InternalError; + } + }; + + let account_id = AccountId::new(unsafe { (*account_id).data }); + + let transfer = NativeTokenTransfer(&wallet); + + match block_on(transfer.register_account(account_id)) { + Ok(Ok(response)) => { + let tx_hash = CString::new(response.tx_hash) + .map(|s| s.into_raw()) + .unwrap_or(ptr::null_mut()); + + unsafe { + (*out_result).tx_hash = tx_hash; + (*out_result).success = true; + } + WalletFfiError::Success + } + Ok(Err(e)) => { + set_last_error(format!("Registration failed: {:?}", e)); + unsafe { + (*out_result).tx_hash = ptr::null_mut(); + (*out_result).success = false; + } + match e { + ExecutionFailureKind::KeyNotFoundError => WalletFfiError::KeyNotFound, + ExecutionFailureKind::SequencerError => WalletFfiError::NetworkError, + ExecutionFailureKind::SequencerClientError(_) => WalletFfiError::NetworkError, + _ => WalletFfiError::InternalError, + } + } + Err(e) => e, + } +} + +/// Free a transfer result returned by `wallet_ffi_transfer_public` or +/// `wallet_ffi_register_public_account`. +/// +/// # Safety +/// The result must be either null or a valid result from a transfer function. +#[no_mangle] +pub extern "C" fn wallet_ffi_free_transfer_result(result: *mut FfiTransferResult) { + if result.is_null() { + return; + } + + unsafe { + let result = &*result; + if !result.tx_hash.is_null() { + drop(CString::from_raw(result.tx_hash)); + } + } +} diff --git a/wallet-ffi/src/types.rs b/wallet-ffi/src/types.rs new file mode 100644 index 00000000..ab199d7c --- /dev/null +++ b/wallet-ffi/src/types.rs @@ -0,0 +1,167 @@ +//! C-compatible type definitions for the FFI layer. + +use std::ffi::c_char; + +/// Opaque pointer to the Wallet instance. +/// +/// This type is never instantiated directly - it's used as an opaque handle +/// to hide the internal wallet structure from C code. +#[repr(C)] +pub struct WalletHandle { + _private: [u8; 0], +} + +/// 32-byte array type for AccountId, keys, hashes, etc. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct FfiBytes32 { + pub data: [u8; 32], +} + +/// Program ID - 8 u32 values (32 bytes total). +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct FfiProgramId { + pub data: [u32; 8], +} + +/// Account data structure - C-compatible version of nssa Account. +/// +/// Note: `balance` and `nonce` are u128 values split into lo/hi u64 parts +/// since C doesn't have native u128 support. +#[repr(C)] +pub struct FfiAccount { + pub program_owner: FfiProgramId, + /// Lower 64 bits of balance (u128) + pub balance_lo: u64, + /// Upper 64 bits of balance (u128) + pub balance_hi: u64, + /// Pointer to account data bytes + pub data: *const u8, + /// Length of account data + pub data_len: usize, + /// Lower 64 bits of nonce (u128) + pub nonce_lo: u64, + /// Upper 64 bits of nonce (u128) + pub nonce_hi: u64, +} + +impl Default for FfiAccount { + fn default() -> Self { + Self { + program_owner: FfiProgramId::default(), + balance_lo: 0, + balance_hi: 0, + data: std::ptr::null(), + data_len: 0, + nonce_lo: 0, + nonce_hi: 0, + } + } +} + +/// Public keys for a private account (safe to expose). +#[repr(C)] +pub struct FfiPrivateAccountKeys { + /// Nullifier public key (32 bytes) + pub nullifier_public_key: FfiBytes32, + /// Incoming viewing public key (compressed secp256k1 point) + pub incoming_viewing_public_key: *const u8, + /// Length of incoming viewing public key (typically 33 bytes) + pub incoming_viewing_public_key_len: usize, +} + +impl Default for FfiPrivateAccountKeys { + fn default() -> Self { + Self { + nullifier_public_key: FfiBytes32::default(), + incoming_viewing_public_key: std::ptr::null(), + incoming_viewing_public_key_len: 0, + } + } +} + +/// Public key info for a public account. +#[repr(C)] +#[derive(Clone, Copy, Default)] +pub struct FfiPublicAccountKey { + pub public_key: FfiBytes32, +} + +/// Single entry in the account list. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct FfiAccountListEntry { + pub account_id: FfiBytes32, + pub is_public: bool, +} + +/// List of accounts returned by wallet_ffi_list_accounts. +#[repr(C)] +pub struct FfiAccountList { + pub entries: *mut FfiAccountListEntry, + pub count: usize, +} + +impl Default for FfiAccountList { + fn default() -> Self { + Self { + entries: std::ptr::null_mut(), + count: 0, + } + } +} + +/// Result of a transfer operation. +#[repr(C)] +pub struct FfiTransferResult { + /// Transaction hash (null-terminated string, or null on failure) + pub tx_hash: *mut c_char, + /// Whether the transfer succeeded + pub success: bool, +} + +impl Default for FfiTransferResult { + fn default() -> Self { + Self { + tx_hash: std::ptr::null_mut(), + success: false, + } + } +} + +// Helper functions to convert between Rust and FFI types + +impl FfiBytes32 { + /// Create from a 32-byte array. + pub fn from_bytes(bytes: [u8; 32]) -> Self { + Self { data: bytes } + } + + /// Create from an AccountId. + pub fn from_account_id(id: &nssa::AccountId) -> Self { + Self { data: *id.value() } + } +} + +impl From<&nssa::AccountId> for FfiBytes32 { + fn from(id: &nssa::AccountId) -> Self { + Self::from_account_id(id) + } +} + +impl From for nssa::AccountId { + fn from(bytes: FfiBytes32) -> Self { + nssa::AccountId::new(bytes.data) + } +} + +/// Helper to split a u128 into lo/hi u64 parts. +pub fn split_u128(value: u128) -> (u64, u64) { + (value as u64, (value >> 64) as u64) +} + +/// Helper to combine lo/hi u64 parts into a u128. +pub fn combine_u128(lo: u64, hi: u64) -> u128 { + (hi as u128) << 64 | (lo as u128) +} diff --git a/wallet-ffi/src/wallet.rs b/wallet-ffi/src/wallet.rs new file mode 100644 index 00000000..151bda94 --- /dev/null +++ b/wallet-ffi/src/wallet.rs @@ -0,0 +1,264 @@ +//! Wallet lifecycle management functions. + +use std::ffi::{c_char, CStr}; +use std::path::PathBuf; +use std::ptr; +use std::sync::Mutex; + +use wallet::WalletCore; + +use crate::block_on; +use crate::error::{set_last_error, WalletFfiError}; +use crate::types::WalletHandle; + +/// Internal wrapper around WalletCore with mutex for thread safety. +pub(crate) struct WalletWrapper { + pub core: Mutex, +} + +/// 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() { + set_last_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() { + set_last_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 { + if ptr.is_null() { + set_last_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) => { + set_last_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 { + if ptr.is_null() { + set_last_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) => { + set_last_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 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) => { + set_last_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 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) => { + set_last_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 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 +#[no_mangle] +pub 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) => { + set_last_error(format!("Failed to lock wallet: {}", e)); + return WalletFfiError::InternalError; + } + }; + + match block_on(wallet.store_persistent_data()) { + Ok(Ok(())) => WalletFfiError::Success, + Ok(Err(e)) => { + set_last_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 +#[no_mangle] +pub 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) => { + set_last_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) => { + set_last_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 extern "C" fn wallet_ffi_free_string(ptr: *mut c_char) { + if !ptr.is_null() { + unsafe { + drop(std::ffi::CString::from_raw(ptr)); + } + } +} diff --git a/wallet-ffi/wallet_ffi.h b/wallet-ffi/wallet_ffi.h new file mode 100644 index 00000000..c1f46653 --- /dev/null +++ b/wallet-ffi/wallet_ffi.h @@ -0,0 +1,646 @@ +/** + * NSSA Wallet FFI Bindings + * + * Thread Safety: All functions are thread-safe. The wallet handle can be + * shared across threads, but operations are serialized internally. + * + * Memory Management: + * - Functions returning pointers allocate memory that must be freed + * - Use the corresponding wallet_ffi_free_* function to free memory + * - Never free memory returned by FFI using standard C free() + * + * Error Handling: + * - Functions return WalletFfiError codes + * - On error, call wallet_ffi_get_last_error() for detailed message + * - The error string must be freed with wallet_ffi_free_error_string() + * + * Initialization: + * 1. Call wallet_ffi_init_runtime() before any other function + * 2. Create wallet with wallet_ffi_create_new() or wallet_ffi_open() + * 3. Destroy wallet with wallet_ffi_destroy() when done + */ + + +#ifndef WALLET_FFI_H +#define WALLET_FFI_H + +/* Generated with cbindgen:0.26.0 */ + +#include +#include +#include +#include + +/** + * Error codes returned by FFI functions. + */ +typedef enum WalletFfiError { + /** + * Operation completed successfully + */ + SUCCESS = 0, + /** + * A null pointer was passed where a valid pointer was expected + */ + NULL_POINTER = 1, + /** + * Invalid UTF-8 string + */ + INVALID_UTF8 = 2, + /** + * Wallet handle is not initialized + */ + WALLET_NOT_INITIALIZED = 3, + /** + * Configuration error + */ + CONFIG_ERROR = 4, + /** + * Storage/persistence error + */ + STORAGE_ERROR = 5, + /** + * Network/RPC error + */ + NETWORK_ERROR = 6, + /** + * Account not found + */ + ACCOUNT_NOT_FOUND = 7, + /** + * Key not found for account + */ + KEY_NOT_FOUND = 8, + /** + * Insufficient funds for operation + */ + INSUFFICIENT_FUNDS = 9, + /** + * Invalid account ID format + */ + INVALID_ACCOUNT_ID = 10, + /** + * Tokio runtime error + */ + RUNTIME_ERROR = 11, + /** + * Password required but not provided + */ + PASSWORD_REQUIRED = 12, + /** + * Block synchronization error + */ + SYNC_ERROR = 13, + /** + * Serialization/deserialization error + */ + SERIALIZATION_ERROR = 14, + /** + * Internal error (catch-all) + */ + INTERNAL_ERROR = 99, +} WalletFfiError; + +/** + * Opaque pointer to the Wallet instance. + * + * This type is never instantiated directly - it's used as an opaque handle + * to hide the internal wallet structure from C code. + */ +typedef struct WalletHandle { + uint8_t _private[0]; +} WalletHandle; + +/** + * 32-byte array type for AccountId, keys, hashes, etc. + */ +typedef struct FfiBytes32 { + uint8_t data[32]; +} FfiBytes32; + +/** + * Single entry in the account list. + */ +typedef struct FfiAccountListEntry { + struct FfiBytes32 account_id; + bool is_public; +} FfiAccountListEntry; + +/** + * List of accounts returned by wallet_ffi_list_accounts. + */ +typedef struct FfiAccountList { + struct FfiAccountListEntry *entries; + uintptr_t count; +} FfiAccountList; + +/** + * Program ID - 8 u32 values (32 bytes total). + */ +typedef struct FfiProgramId { + uint32_t data[8]; +} FfiProgramId; + +/** + * Account data structure - C-compatible version of nssa Account. + * + * Note: `balance` and `nonce` are u128 values split into lo/hi u64 parts + * since C doesn't have native u128 support. + */ +typedef struct FfiAccount { + struct FfiProgramId program_owner; + /** + * Lower 64 bits of balance (u128) + */ + uint64_t balance_lo; + /** + * Upper 64 bits of balance (u128) + */ + uint64_t balance_hi; + /** + * Pointer to account data bytes + */ + const uint8_t *data; + /** + * Length of account data + */ + uintptr_t data_len; + /** + * Lower 64 bits of nonce (u128) + */ + uint64_t nonce_lo; + /** + * Upper 64 bits of nonce (u128) + */ + uint64_t nonce_hi; +} FfiAccount; + +/** + * Public key info for a public account. + */ +typedef struct FfiPublicAccountKey { + struct FfiBytes32 public_key; +} FfiPublicAccountKey; + +/** + * Public keys for a private account (safe to expose). + */ +typedef struct FfiPrivateAccountKeys { + /** + * Nullifier public key (32 bytes) + */ + struct FfiBytes32 nullifier_public_key; + /** + * Incoming viewing public key (compressed secp256k1 point) + */ + const uint8_t *incoming_viewing_public_key; + /** + * Length of incoming viewing public key (typically 33 bytes) + */ + uintptr_t incoming_viewing_public_key_len; +} FfiPrivateAccountKeys; + +/** + * Result of a transfer operation. + */ +typedef struct FfiTransferResult { + /** + * Transaction hash (null-terminated string, or null on failure) + */ + char *tx_hash; + /** + * Whether the transfer succeeded + */ + bool success; +} FfiTransferResult; + +/** + * Initialize the global Tokio runtime. + * + * This must be called before any async operations (like network calls). + * Safe to call multiple times - subsequent calls are no-ops. + * + * # Returns + * - `Success` if the runtime was initialized or already exists + * - `RuntimeError` if runtime creation failed + */ +enum WalletFfiError wallet_ffi_init_runtime(void); + +/** + * Check if the runtime is initialized. + * + * # Returns + * - `true` if the runtime is ready + * - `false` if `wallet_ffi_init_runtime()` hasn't been called yet + */ +bool wallet_ffi_runtime_initialized(void); + +/** + * Create a new public account. + * + * Public accounts use standard transaction signing and are suitable for + * non-private operations. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `out_account_id`: Output pointer for the new account ID (32 bytes) + * + * # Returns + * - `Success` on successful creation + * - Error code on failure + */ +enum WalletFfiError wallet_ffi_create_account_public(struct WalletHandle *handle, + struct FfiBytes32 *out_account_id); + +/** + * Create a new private account. + * + * Private accounts use privacy-preserving transactions with nullifiers + * and commitments. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `out_account_id`: Output pointer for the new account ID (32 bytes) + * + * # Returns + * - `Success` on successful creation + * - Error code on failure + */ +enum WalletFfiError wallet_ffi_create_account_private(struct WalletHandle *handle, + struct FfiBytes32 *out_account_id); + +/** + * List all accounts in the wallet. + * + * Returns both public and private accounts managed by this wallet. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `out_list`: Output pointer for the account list + * + * # Returns + * - `Success` on successful listing + * - Error code on failure + * + * # Memory + * The returned list must be freed with `wallet_ffi_free_account_list()`. + */ +enum WalletFfiError wallet_ffi_list_accounts(struct WalletHandle *handle, + struct FfiAccountList *out_list); + +/** + * Free an account list returned by `wallet_ffi_list_accounts`. + * + * # Safety + * The list must be either null or a valid list returned by `wallet_ffi_list_accounts`. + */ +void wallet_ffi_free_account_list(struct FfiAccountList *list); + +/** + * Get account balance. + * + * For public accounts, this fetches the balance from the network. + * For private accounts, this returns the locally cached balance. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `account_id`: The account ID (32 bytes) + * - `is_public`: Whether this is a public account + * - `out_balance_lo`: Output for lower 64 bits of balance + * - `out_balance_hi`: Output for upper 64 bits of balance + * + * # Returns + * - `Success` on successful query + * - Error code on failure + */ +enum WalletFfiError wallet_ffi_get_balance(struct WalletHandle *handle, + const struct FfiBytes32 *account_id, + bool is_public, + uint64_t *out_balance_lo, + uint64_t *out_balance_hi); + +/** + * Get full public account data from the network. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `account_id`: The account ID (32 bytes) + * - `out_account`: Output pointer for account data + * + * # Returns + * - `Success` on successful query + * - Error code on failure + * + * # Memory + * The account data must be freed with `wallet_ffi_free_account_data()`. + */ +enum WalletFfiError wallet_ffi_get_account_public(struct WalletHandle *handle, + const struct FfiBytes32 *account_id, + struct FfiAccount *out_account); + +/** + * Free account data returned by `wallet_ffi_get_account_public`. + * + * # Safety + * The account must be either null or a valid account returned by + * `wallet_ffi_get_account_public`. + */ +void wallet_ffi_free_account_data(struct FfiAccount *account); + +/** + * Get the last error message. + * + * Returns a pointer to a null-terminated string, or null if no error is set. + * The caller owns the returned string and must free it with + * `wallet_ffi_free_error_string`. + */ +char *wallet_ffi_get_last_error(void); + +/** + * Free an error string returned by `wallet_ffi_get_last_error`. + * + * # Safety + * The pointer must be either null or a valid pointer returned by + * `wallet_ffi_get_last_error`. + */ +void wallet_ffi_free_error_string(char *ptr); + +/** + * Get the public key for a public account. + * + * This returns the public key derived from the account's signing key. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `account_id`: The account ID (32 bytes) + * - `out_public_key`: Output pointer for the public key + * + * # Returns + * - `Success` on successful retrieval + * - `KeyNotFound` if the account's key is not in this wallet + * - Error code on other failures + */ +enum WalletFfiError wallet_ffi_get_public_account_key(struct WalletHandle *handle, + const struct FfiBytes32 *account_id, + struct FfiPublicAccountKey *out_public_key); + +/** + * Get keys for a private account. + * + * Returns the nullifier public key (NPK) and incoming viewing public key (IPK) + * for the specified private account. These keys are safe to share publicly. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `account_id`: The account ID (32 bytes) + * - `out_keys`: Output pointer for the key data + * + * # Returns + * - `Success` on successful retrieval + * - `AccountNotFound` if the private account is not in this wallet + * - Error code on other failures + * + * # Memory + * The keys structure must be freed with `wallet_ffi_free_private_account_keys()`. + */ +enum WalletFfiError wallet_ffi_get_private_account_keys(struct WalletHandle *handle, + const struct FfiBytes32 *account_id, + struct FfiPrivateAccountKeys *out_keys); + +/** + * Free private account keys returned by `wallet_ffi_get_private_account_keys`. + * + * # Safety + * The keys must be either null or valid keys returned by + * `wallet_ffi_get_private_account_keys`. + */ +void wallet_ffi_free_private_account_keys(struct FfiPrivateAccountKeys *keys); + +/** + * Convert an account ID to a Base58 string. + * + * # Parameters + * - `account_id`: The account ID (32 bytes) + * + * # Returns + * - Pointer to null-terminated Base58 string on success + * - Null pointer on error + * + * # Memory + * The returned string must be freed with `wallet_ffi_free_string()`. + */ +char *wallet_ffi_account_id_to_base58(const struct FfiBytes32 *account_id); + +/** + * Parse a Base58 string into an account ID. + * + * # Parameters + * - `base58_str`: Null-terminated Base58 string + * - `out_account_id`: Output pointer for the account ID (32 bytes) + * + * # Returns + * - `Success` on successful parsing + * - `InvalidAccountId` if the string is not valid Base58 + * - Error code on other failures + */ +enum WalletFfiError wallet_ffi_account_id_from_base58(const char *base58_str, + struct FfiBytes32 *out_account_id); + +/** + * Synchronize private accounts to a specific block. + * + * This scans the blockchain from the last synced block to the specified block, + * updating private account balances based on any relevant transactions. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `block_id`: Target block number to sync to + * + * # Returns + * - `Success` if synchronization completed + * - `SyncError` if synchronization failed + * - Error code on other failures + * + * # Note + * This operation can take a while for large block ranges. The wallet + * internally uses a progress bar which may output to stdout. + */ +enum WalletFfiError wallet_ffi_sync_to_block(struct WalletHandle *handle, uint64_t block_id); + +/** + * Get the last synced block number. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `out_block_id`: Output pointer for the block number + * + * # Returns + * - `Success` on success + * - Error code on failure + */ +enum WalletFfiError wallet_ffi_get_last_synced_block(struct WalletHandle *handle, + uint64_t *out_block_id); + +/** + * Get the current block height from the sequencer. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `out_block_height`: Output pointer for the current block height + * + * # Returns + * - `Success` on success + * - `NetworkError` if the sequencer is unreachable + * - Error code on other failures + */ +enum WalletFfiError wallet_ffi_get_current_block_height(struct WalletHandle *handle, + uint64_t *out_block_height); + +/** + * Send a public token transfer. + * + * Transfers tokens from one public account to another on the network. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `from`: Source account ID (must be owned by this wallet) + * - `to`: Destination account ID + * - `amount_lo`: Lower 64 bits of amount to transfer + * - `amount_hi`: Upper 64 bits of amount to transfer + * - `out_result`: Output pointer for transfer result + * + * # Returns + * - `Success` if the transfer was submitted successfully + * - `InsufficientFunds` if the source account doesn't have enough balance + * - `KeyNotFound` if the source account's signing key is not in this wallet + * - Error code on other failures + * + * # Memory + * The result must be freed with `wallet_ffi_free_transfer_result()`. + */ +enum WalletFfiError wallet_ffi_transfer_public(struct WalletHandle *handle, + const struct FfiBytes32 *from, + const struct FfiBytes32 *to, + uint64_t amount_lo, + uint64_t amount_hi, + struct FfiTransferResult *out_result); + +/** + * Register a public account on the network. + * + * This initializes a public account on the blockchain. The account must be + * owned by this wallet. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `account_id`: Account ID to register + * - `out_result`: Output pointer for registration result + * + * # Returns + * - `Success` if the registration was submitted successfully + * - Error code on failure + * + * # Memory + * The result must be freed with `wallet_ffi_free_transfer_result()`. + */ +enum WalletFfiError wallet_ffi_register_public_account(struct WalletHandle *handle, + const struct FfiBytes32 *account_id, + struct FfiTransferResult *out_result); + +/** + * Free a transfer result returned by `wallet_ffi_transfer_public` or + * `wallet_ffi_register_public_account`. + * + * # Safety + * The result must be either null or a valid result from a transfer function. + */ +void wallet_ffi_free_transfer_result(struct FfiTransferResult *result); + +/** + * 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. + */ +struct WalletHandle *wallet_ffi_create_new(const char *config_path, + const char *storage_path, + const char *password); + +/** + * 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. + */ +struct WalletHandle *wallet_ffi_open(const char *config_path, const char *storage_path); + +/** + * 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. + */ +void wallet_ffi_destroy(struct WalletHandle *handle); + +/** + * 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 + */ +enum WalletFfiError wallet_ffi_save(struct WalletHandle *handle); + +/** + * 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 + */ +char *wallet_ffi_get_sequencer_addr(struct WalletHandle *handle); + +/** + * Free a string returned by wallet FFI functions. + * + * # Safety + * The pointer must be either null or a valid string returned by an FFI function. + */ +void wallet_ffi_free_string(char *ptr); + +#endif /* WALLET_FFI_H */ From ec6caee2dc792f81551b8dc00569a77581abb8b0 Mon Sep 17 00:00:00 2001 From: Daniel Date: Wed, 21 Jan 2026 15:38:50 +0100 Subject: [PATCH 2/9] Missing fmt --- wallet-ffi/build.rs | 4 ++-- wallet-ffi/src/keys.rs | 4 +--- wallet-ffi/src/transfer.rs | 2 +- wallet-ffi/src/wallet.rs | 8 ++++++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/wallet-ffi/build.rs b/wallet-ffi/build.rs index 2d1b0cca..63ee0d9e 100644 --- a/wallet-ffi/build.rs +++ b/wallet-ffi/build.rs @@ -1,8 +1,8 @@ fn main() { let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); - let config = cbindgen::Config::from_file("cbindgen.toml") - .expect("Unable to read cbindgen.toml"); + let config = + cbindgen::Config::from_file("cbindgen.toml").expect("Unable to read cbindgen.toml"); cbindgen::Builder::new() .with_crate(crate_dir) diff --git a/wallet-ffi/src/keys.rs b/wallet-ffi/src/keys.rs index d4a29e11..779dbce1 100644 --- a/wallet-ffi/src/keys.rs +++ b/wallet-ffi/src/keys.rs @@ -147,9 +147,7 @@ pub extern "C" fn wallet_ffi_free_private_account_keys(keys: *mut FfiPrivateAcco unsafe { let keys = &*keys; - if !keys.incoming_viewing_public_key.is_null() - && keys.incoming_viewing_public_key_len > 0 - { + if !keys.incoming_viewing_public_key.is_null() && keys.incoming_viewing_public_key_len > 0 { let slice = std::slice::from_raw_parts_mut( keys.incoming_viewing_public_key as *mut u8, keys.incoming_viewing_public_key_len, diff --git a/wallet-ffi/src/transfer.rs b/wallet-ffi/src/transfer.rs index d52ee9a8..3c0c8edb 100644 --- a/wallet-ffi/src/transfer.rs +++ b/wallet-ffi/src/transfer.rs @@ -1,6 +1,6 @@ //! Token transfer functions. -use std::ffi::{CString, c_ulonglong}; +use std::ffi::{c_ulonglong, CString}; use std::ptr; use common::error::ExecutionFailureKind; diff --git a/wallet-ffi/src/wallet.rs b/wallet-ffi/src/wallet.rs index 151bda94..7c70e19d 100644 --- a/wallet-ffi/src/wallet.rs +++ b/wallet-ffi/src/wallet.rs @@ -17,7 +17,9 @@ pub(crate) struct WalletWrapper { } /// Helper to get the wallet wrapper from an opaque handle. -pub(crate) fn get_wallet(handle: *mut WalletHandle) -> Result<&'static WalletWrapper, WalletFfiError> { +pub(crate) fn get_wallet( + handle: *mut WalletHandle, +) -> Result<&'static WalletWrapper, WalletFfiError> { if handle.is_null() { set_last_error("Null wallet handle"); return Err(WalletFfiError::NullPointer); @@ -27,7 +29,9 @@ pub(crate) fn get_wallet(handle: *mut WalletHandle) -> Result<&'static WalletWra /// 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> { +pub(crate) fn get_wallet_mut( + handle: *mut WalletHandle, +) -> Result<&'static mut WalletWrapper, WalletFfiError> { if handle.is_null() { set_last_error("Null wallet handle"); return Err(WalletFfiError::NullPointer); From 6d883d5528737ec10a7a3c440d9417af810d748c Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 26 Jan 2026 10:15:23 +0100 Subject: [PATCH 3/9] Use bytearray instead of hi/lo for u128 --- wallet-ffi/src/account.rs | 24 +++++++----------------- wallet-ffi/src/transfer.rs | 14 ++++++-------- wallet-ffi/src/types.rs | 31 ++++++++----------------------- wallet-ffi/wallet_ffi.h | 32 ++++++++++---------------------- 4 files changed, 31 insertions(+), 70 deletions(-) diff --git a/wallet-ffi/src/account.rs b/wallet-ffi/src/account.rs index 53abb54f..db668e75 100644 --- a/wallet-ffi/src/account.rs +++ b/wallet-ffi/src/account.rs @@ -7,8 +7,7 @@ use nssa::AccountId; use crate::block_on; use crate::error::{set_last_error, WalletFfiError}; use crate::types::{ - split_u128, FfiAccount, FfiAccountList, FfiAccountListEntry, FfiBytes32, FfiProgramId, - WalletHandle, + FfiAccount, FfiAccountList, FfiAccountListEntry, FfiBytes32, FfiProgramId, WalletHandle, }; use crate::wallet::get_wallet; @@ -220,8 +219,7 @@ pub extern "C" fn wallet_ffi_free_account_list(list: *mut FfiAccountList) { /// - `handle`: Valid wallet handle /// - `account_id`: The account ID (32 bytes) /// - `is_public`: Whether this is a public account -/// - `out_balance_lo`: Output for lower 64 bits of balance -/// - `out_balance_hi`: Output for upper 64 bits of balance +/// - `out_balance`: Output for balance as little-endian [u8; 16] /// /// # Returns /// - `Success` on successful query @@ -231,15 +229,14 @@ pub extern "C" fn wallet_ffi_get_balance( handle: *mut WalletHandle, account_id: *const FfiBytes32, is_public: bool, - out_balance_lo: *mut u64, - out_balance_hi: *mut u64, + out_balance: *mut [u8; 16], ) -> WalletFfiError { let wrapper = match get_wallet(handle) { Ok(w) => w, Err(e) => return e, }; - if account_id.is_null() || out_balance_lo.is_null() || out_balance_hi.is_null() { + if account_id.is_null() || out_balance.is_null() { set_last_error("Null pointer argument"); return WalletFfiError::NullPointer; } @@ -273,10 +270,8 @@ pub extern "C" fn wallet_ffi_get_balance( } }; - let (lo, hi) = split_u128(balance); unsafe { - *out_balance_lo = lo; - *out_balance_hi = hi; + *out_balance = balance.to_le_bytes(); } WalletFfiError::Success @@ -340,19 +335,14 @@ pub extern "C" fn wallet_ffi_get_account_public( ptr::null() }; - let (balance_lo, balance_hi) = split_u128(account.balance); - let (nonce_lo, nonce_hi) = split_u128(account.nonce); - let program_owner = FfiProgramId { data: account.program_owner, }; unsafe { (*out_account).program_owner = program_owner; - (*out_account).balance_lo = balance_lo; - (*out_account).balance_hi = balance_hi; - (*out_account).nonce_lo = nonce_lo; - (*out_account).nonce_hi = nonce_hi; + (*out_account).balance = account.balance.to_le_bytes(); + (*out_account).nonce = account.nonce.to_le_bytes(); (*out_account).data = data_ptr; (*out_account).data_len = data_len; } diff --git a/wallet-ffi/src/transfer.rs b/wallet-ffi/src/transfer.rs index 3c0c8edb..e11858da 100644 --- a/wallet-ffi/src/transfer.rs +++ b/wallet-ffi/src/transfer.rs @@ -1,6 +1,6 @@ //! Token transfer functions. -use std::ffi::{c_ulonglong, CString}; +use std::ffi::CString; use std::ptr; use common::error::ExecutionFailureKind; @@ -9,7 +9,7 @@ use wallet::program_facades::native_token_transfer::NativeTokenTransfer; use crate::block_on; use crate::error::{set_last_error, WalletFfiError}; -use crate::types::{combine_u128, FfiBytes32, FfiTransferResult, WalletHandle}; +use crate::types::{FfiBytes32, FfiTransferResult, WalletHandle}; use crate::wallet::get_wallet; /// Send a public token transfer. @@ -20,8 +20,7 @@ use crate::wallet::get_wallet; /// - `handle`: Valid wallet handle /// - `from`: Source account ID (must be owned by this wallet) /// - `to`: Destination account ID -/// - `amount_lo`: Lower 64 bits of amount to transfer -/// - `amount_hi`: Upper 64 bits of amount to transfer +/// - `amount`: Amount to transfer as little-endian [u8; 16] /// - `out_result`: Output pointer for transfer result /// /// # Returns @@ -37,8 +36,7 @@ pub extern "C" fn wallet_ffi_transfer_public( handle: *mut WalletHandle, from: *const FfiBytes32, to: *const FfiBytes32, - amount_lo: u64, - amount_hi: u64, + amount: *const [u8; 16], out_result: *mut FfiTransferResult, ) -> WalletFfiError { let wrapper = match get_wallet(handle) { @@ -46,7 +44,7 @@ pub extern "C" fn wallet_ffi_transfer_public( Err(e) => return e, }; - if from.is_null() || to.is_null() || out_result.is_null() { + if from.is_null() || to.is_null() || amount.is_null() || out_result.is_null() { set_last_error("Null pointer argument"); return WalletFfiError::NullPointer; } @@ -61,7 +59,7 @@ pub extern "C" fn wallet_ffi_transfer_public( let from_id = AccountId::new(unsafe { (*from).data }); let to_id = AccountId::new(unsafe { (*to).data }); - let amount = combine_u128(amount_lo, amount_hi); + let amount = u128::from_le_bytes(unsafe { *amount }); let transfer = NativeTokenTransfer(&wallet); diff --git a/wallet-ffi/src/types.rs b/wallet-ffi/src/types.rs index ab199d7c..9bb67f19 100644 --- a/wallet-ffi/src/types.rs +++ b/wallet-ffi/src/types.rs @@ -27,35 +27,29 @@ pub struct FfiProgramId { /// Account data structure - C-compatible version of nssa Account. /// -/// Note: `balance` and `nonce` are u128 values split into lo/hi u64 parts -/// since C doesn't have native u128 support. +/// Note: `balance` and `nonce` are u128 values represented as little-endian +/// byte arrays since C doesn't have native u128 support. #[repr(C)] pub struct FfiAccount { pub program_owner: FfiProgramId, - /// Lower 64 bits of balance (u128) - pub balance_lo: u64, - /// Upper 64 bits of balance (u128) - pub balance_hi: u64, + /// Balance as little-endian [u8; 16] + pub balance: [u8; 16], /// Pointer to account data bytes pub data: *const u8, /// Length of account data pub data_len: usize, - /// Lower 64 bits of nonce (u128) - pub nonce_lo: u64, - /// Upper 64 bits of nonce (u128) - pub nonce_hi: u64, + /// Nonce as little-endian [u8; 16] + pub nonce: [u8; 16], } impl Default for FfiAccount { fn default() -> Self { Self { program_owner: FfiProgramId::default(), - balance_lo: 0, - balance_hi: 0, + balance: [0u8; 16], data: std::ptr::null(), data_len: 0, - nonce_lo: 0, - nonce_hi: 0, + nonce: [0u8; 16], } } } @@ -156,12 +150,3 @@ impl From for nssa::AccountId { } } -/// Helper to split a u128 into lo/hi u64 parts. -pub fn split_u128(value: u128) -> (u64, u64) { - (value as u64, (value >> 64) as u64) -} - -/// Helper to combine lo/hi u64 parts into a u128. -pub fn combine_u128(lo: u64, hi: u64) -> u128 { - (hi as u128) << 64 | (lo as u128) -} diff --git a/wallet-ffi/wallet_ffi.h b/wallet-ffi/wallet_ffi.h index c1f46653..6465ac25 100644 --- a/wallet-ffi/wallet_ffi.h +++ b/wallet-ffi/wallet_ffi.h @@ -144,19 +144,15 @@ typedef struct FfiProgramId { /** * Account data structure - C-compatible version of nssa Account. * - * Note: `balance` and `nonce` are u128 values split into lo/hi u64 parts - * since C doesn't have native u128 support. + * Note: `balance` and `nonce` are u128 values represented as little-endian + * byte arrays since C doesn't have native u128 support. */ typedef struct FfiAccount { struct FfiProgramId program_owner; /** - * Lower 64 bits of balance (u128) + * Balance as little-endian [u8; 16] */ - uint64_t balance_lo; - /** - * Upper 64 bits of balance (u128) - */ - uint64_t balance_hi; + uint8_t balance[16]; /** * Pointer to account data bytes */ @@ -166,13 +162,9 @@ typedef struct FfiAccount { */ uintptr_t data_len; /** - * Lower 64 bits of nonce (u128) + * Nonce as little-endian [u8; 16] */ - uint64_t nonce_lo; - /** - * Upper 64 bits of nonce (u128) - */ - uint64_t nonce_hi; + uint8_t nonce[16]; } FfiAccount; /** @@ -306,8 +298,7 @@ void wallet_ffi_free_account_list(struct FfiAccountList *list); * - `handle`: Valid wallet handle * - `account_id`: The account ID (32 bytes) * - `is_public`: Whether this is a public account - * - `out_balance_lo`: Output for lower 64 bits of balance - * - `out_balance_hi`: Output for upper 64 bits of balance + * - `out_balance`: Output for balance as little-endian [u8; 16] * * # Returns * - `Success` on successful query @@ -316,8 +307,7 @@ void wallet_ffi_free_account_list(struct FfiAccountList *list); enum WalletFfiError wallet_ffi_get_balance(struct WalletHandle *handle, const struct FfiBytes32 *account_id, bool is_public, - uint64_t *out_balance_lo, - uint64_t *out_balance_hi); + uint8_t (*out_balance)[16]); /** * Get full public account data from the network. @@ -505,8 +495,7 @@ enum WalletFfiError wallet_ffi_get_current_block_height(struct WalletHandle *han * - `handle`: Valid wallet handle * - `from`: Source account ID (must be owned by this wallet) * - `to`: Destination account ID - * - `amount_lo`: Lower 64 bits of amount to transfer - * - `amount_hi`: Upper 64 bits of amount to transfer + * - `amount`: Amount to transfer as little-endian [u8; 16] * - `out_result`: Output pointer for transfer result * * # Returns @@ -521,8 +510,7 @@ enum WalletFfiError wallet_ffi_get_current_block_height(struct WalletHandle *han enum WalletFfiError wallet_ffi_transfer_public(struct WalletHandle *handle, const struct FfiBytes32 *from, const struct FfiBytes32 *to, - uint64_t amount_lo, - uint64_t amount_hi, + const uint8_t (*amount)[16], struct FfiTransferResult *out_result); /** From a97066c41dda1917c02f4c77f1c7eb4e8c5d1777 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 26 Jan 2026 10:29:37 +0100 Subject: [PATCH 4/9] Re-structure errors and clippy happy --- wallet-ffi/src/account.rs | 64 ++++++++++++++++++---------- wallet-ffi/src/error.rs | 55 ++---------------------- wallet-ffi/src/keys.rs | 51 ++++++++++++++-------- wallet-ffi/src/lib.rs | 6 +-- wallet-ffi/src/sync.rs | 33 ++++++++++----- wallet-ffi/src/transfer.rs | 32 +++++++++----- wallet-ffi/src/wallet.rs | 44 ++++++++++--------- wallet-ffi/wallet_ffi.h | 86 ++++++++++++++++++++++++++++++-------- 8 files changed, 221 insertions(+), 150 deletions(-) diff --git a/wallet-ffi/src/account.rs b/wallet-ffi/src/account.rs index db668e75..a058acc7 100644 --- a/wallet-ffi/src/account.rs +++ b/wallet-ffi/src/account.rs @@ -5,7 +5,7 @@ use std::ptr; use nssa::AccountId; use crate::block_on; -use crate::error::{set_last_error, WalletFfiError}; +use crate::error::{print_error, WalletFfiError}; use crate::types::{ FfiAccount, FfiAccountList, FfiAccountListEntry, FfiBytes32, FfiProgramId, WalletHandle, }; @@ -23,8 +23,12 @@ use crate::wallet::get_wallet; /// # Returns /// - `Success` on successful creation /// - Error code on failure +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `out_account_id` must be a valid pointer to a `FfiBytes32` struct #[no_mangle] -pub extern "C" fn wallet_ffi_create_account_public( +pub unsafe extern "C" fn wallet_ffi_create_account_public( handle: *mut WalletHandle, out_account_id: *mut FfiBytes32, ) -> WalletFfiError { @@ -34,14 +38,14 @@ pub extern "C" fn wallet_ffi_create_account_public( }; if out_account_id.is_null() { - set_last_error("Null output pointer for account_id"); + print_error("Null output pointer for account_id"); return WalletFfiError::NullPointer; } let mut wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -67,8 +71,12 @@ pub extern "C" fn wallet_ffi_create_account_public( /// # Returns /// - `Success` on successful creation /// - Error code on failure +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `out_account_id` must be a valid pointer to a `FfiBytes32` struct #[no_mangle] -pub extern "C" fn wallet_ffi_create_account_private( +pub unsafe extern "C" fn wallet_ffi_create_account_private( handle: *mut WalletHandle, out_account_id: *mut FfiBytes32, ) -> WalletFfiError { @@ -78,14 +86,14 @@ pub extern "C" fn wallet_ffi_create_account_private( }; if out_account_id.is_null() { - set_last_error("Null output pointer for account_id"); + print_error("Null output pointer for account_id"); return WalletFfiError::NullPointer; } let mut wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -113,8 +121,12 @@ pub extern "C" fn wallet_ffi_create_account_private( /// /// # Memory /// The returned list must be freed with `wallet_ffi_free_account_list()`. +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `out_list` must be a valid pointer to a `FfiAccountList` struct #[no_mangle] -pub extern "C" fn wallet_ffi_list_accounts( +pub unsafe extern "C" fn wallet_ffi_list_accounts( handle: *mut WalletHandle, out_list: *mut FfiAccountList, ) -> WalletFfiError { @@ -124,14 +136,14 @@ pub extern "C" fn wallet_ffi_list_accounts( }; if out_list.is_null() { - set_last_error("Null output pointer for account list"); + print_error("Null output pointer for account list"); return WalletFfiError::NullPointer; } let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -196,7 +208,7 @@ pub extern "C" fn wallet_ffi_list_accounts( /// # Safety /// The list must be either null or a valid list returned by `wallet_ffi_list_accounts`. #[no_mangle] -pub extern "C" fn wallet_ffi_free_account_list(list: *mut FfiAccountList) { +pub unsafe extern "C" fn wallet_ffi_free_account_list(list: *mut FfiAccountList) { if list.is_null() { return; } @@ -224,8 +236,13 @@ pub extern "C" fn wallet_ffi_free_account_list(list: *mut FfiAccountList) { /// # Returns /// - `Success` on successful query /// - Error code on failure +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `account_id` must be a valid pointer to a `FfiBytes32` struct +/// - `out_balance` must be a valid pointer to a `[u8; 16]` array #[no_mangle] -pub extern "C" fn wallet_ffi_get_balance( +pub unsafe extern "C" fn wallet_ffi_get_balance( handle: *mut WalletHandle, account_id: *const FfiBytes32, is_public: bool, @@ -237,14 +254,14 @@ pub extern "C" fn wallet_ffi_get_balance( }; if account_id.is_null() || out_balance.is_null() { - set_last_error("Null pointer argument"); + print_error("Null pointer argument"); return WalletFfiError::NullPointer; } let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -255,7 +272,7 @@ pub extern "C" fn wallet_ffi_get_balance( match block_on(wallet.get_account_balance(account_id)) { Ok(Ok(b)) => b, Ok(Err(e)) => { - set_last_error(format!("Failed to get balance: {}", e)); + print_error(format!("Failed to get balance: {}", e)); return WalletFfiError::NetworkError; } Err(e) => return e, @@ -264,7 +281,7 @@ pub extern "C" fn wallet_ffi_get_balance( match wallet.get_account_private(&account_id) { Some(account) => account.balance, None => { - set_last_error("Private account not found"); + print_error("Private account not found"); return WalletFfiError::AccountNotFound; } } @@ -290,8 +307,13 @@ pub extern "C" fn wallet_ffi_get_balance( /// /// # Memory /// The account data must be freed with `wallet_ffi_free_account_data()`. +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `account_id` must be a valid pointer to a `FfiBytes32` struct +/// - `out_account` must be a valid pointer to a `FfiAccount` struct #[no_mangle] -pub extern "C" fn wallet_ffi_get_account_public( +pub unsafe extern "C" fn wallet_ffi_get_account_public( handle: *mut WalletHandle, account_id: *const FfiBytes32, out_account: *mut FfiAccount, @@ -302,14 +324,14 @@ pub extern "C" fn wallet_ffi_get_account_public( }; if account_id.is_null() || out_account.is_null() { - set_last_error("Null pointer argument"); + print_error("Null pointer argument"); return WalletFfiError::NullPointer; } let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -319,7 +341,7 @@ pub extern "C" fn wallet_ffi_get_account_public( let account = match block_on(wallet.get_account_public(account_id)) { Ok(Ok(a)) => a, Ok(Err(e)) => { - set_last_error(format!("Failed to get account: {}", e)); + print_error(format!("Failed to get account: {}", e)); return WalletFfiError::NetworkError; } Err(e) => return e, @@ -356,7 +378,7 @@ pub extern "C" fn wallet_ffi_get_account_public( /// The account must be either null or a valid account returned by /// `wallet_ffi_get_account_public`. #[no_mangle] -pub extern "C" fn wallet_ffi_free_account_data(account: *mut FfiAccount) { +pub unsafe extern "C" fn wallet_ffi_free_account_data(account: *mut FfiAccount) { if account.is_null() { return; } diff --git a/wallet-ffi/src/error.rs b/wallet-ffi/src/error.rs index 46646259..ea366475 100644 --- a/wallet-ffi/src/error.rs +++ b/wallet-ffi/src/error.rs @@ -1,10 +1,6 @@ //! Error handling for the FFI layer. //! -//! Uses numeric error codes with a thread-local last error message. - -use std::cell::RefCell; -use std::ffi::{c_char, CString}; -use std::ptr; +//! Uses numeric error codes with error messages printed to stderr. /// Error codes returned by FFI functions. #[repr(C)] @@ -44,50 +40,7 @@ pub enum WalletFfiError { InternalError = 99, } -// Thread-local storage for the last error message -thread_local! { - static LAST_ERROR: RefCell> = const { RefCell::new(None) }; -} - -/// Set the last error message for the current thread. -pub fn set_last_error(msg: impl Into) { - LAST_ERROR.with(|e| { - *e.borrow_mut() = Some(msg.into()); - }); -} - -/// Clear the last error message. -pub fn clear_last_error() { - LAST_ERROR.with(|e| { - *e.borrow_mut() = None; - }); -} - -/// Get the last error message. -/// -/// Returns a pointer to a null-terminated string, or null if no error is set. -/// The caller owns the returned string and must free it with -/// `wallet_ffi_free_error_string`. -#[no_mangle] -pub extern "C" fn wallet_ffi_get_last_error() -> *mut c_char { - LAST_ERROR.with(|e| match e.borrow_mut().take() { - Some(msg) => CString::new(msg) - .map(|s| s.into_raw()) - .unwrap_or(ptr::null_mut()), - None => ptr::null_mut(), - }) -} - -/// Free an error string returned by `wallet_ffi_get_last_error`. -/// -/// # Safety -/// The pointer must be either null or a valid pointer returned by -/// `wallet_ffi_get_last_error`. -#[no_mangle] -pub extern "C" fn wallet_ffi_free_error_string(ptr: *mut c_char) { - if !ptr.is_null() { - unsafe { - drop(CString::from_raw(ptr)); - } - } +/// Log an error message to stderr. +pub fn print_error(msg: impl Into) { + eprintln!("[wallet-ffi] {}", msg.into()); } diff --git a/wallet-ffi/src/keys.rs b/wallet-ffi/src/keys.rs index 779dbce1..27989c0e 100644 --- a/wallet-ffi/src/keys.rs +++ b/wallet-ffi/src/keys.rs @@ -4,7 +4,7 @@ use std::ptr; use nssa::{AccountId, PublicKey}; -use crate::error::{set_last_error, WalletFfiError}; +use crate::error::{print_error, WalletFfiError}; use crate::types::{FfiBytes32, FfiPrivateAccountKeys, FfiPublicAccountKey, WalletHandle}; use crate::wallet::get_wallet; @@ -21,8 +21,13 @@ use crate::wallet::get_wallet; /// - `Success` on successful retrieval /// - `KeyNotFound` if the account's key is not in this wallet /// - Error code on other failures +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `account_id` must be a valid pointer to a `FfiBytes32` struct +/// - `out_public_key` must be a valid pointer to a `FfiPublicAccountKey` struct #[no_mangle] -pub extern "C" fn wallet_ffi_get_public_account_key( +pub unsafe extern "C" fn wallet_ffi_get_public_account_key( handle: *mut WalletHandle, account_id: *const FfiBytes32, out_public_key: *mut FfiPublicAccountKey, @@ -33,14 +38,14 @@ pub extern "C" fn wallet_ffi_get_public_account_key( }; if account_id.is_null() || out_public_key.is_null() { - set_last_error("Null pointer argument"); + print_error("Null pointer argument"); return WalletFfiError::NullPointer; } let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -50,7 +55,7 @@ pub extern "C" fn wallet_ffi_get_public_account_key( let private_key = match wallet.get_account_public_signing_key(&account_id) { Some(k) => k, None => { - set_last_error("Public account key not found in wallet"); + print_error("Public account key not found in wallet"); return WalletFfiError::KeyNotFound; } }; @@ -81,8 +86,13 @@ pub extern "C" fn wallet_ffi_get_public_account_key( /// /// # Memory /// The keys structure must be freed with `wallet_ffi_free_private_account_keys()`. +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `account_id` must be a valid pointer to a `FfiBytes32` struct +/// - `out_keys` must be a valid pointer to a `FfiPrivateAccountKeys` struct #[no_mangle] -pub extern "C" fn wallet_ffi_get_private_account_keys( +pub unsafe extern "C" fn wallet_ffi_get_private_account_keys( handle: *mut WalletHandle, account_id: *const FfiBytes32, out_keys: *mut FfiPrivateAccountKeys, @@ -93,14 +103,14 @@ pub extern "C" fn wallet_ffi_get_private_account_keys( }; if account_id.is_null() || out_keys.is_null() { - set_last_error("Null pointer argument"); + print_error("Null pointer argument"); return WalletFfiError::NullPointer; } let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -110,7 +120,7 @@ pub extern "C" fn wallet_ffi_get_private_account_keys( let (key_chain, _account) = match wallet.storage().user_data.get_private_account(&account_id) { Some(k) => k, None => { - set_last_error("Private account not found in wallet"); + print_error("Private account not found in wallet"); return WalletFfiError::AccountNotFound; } }; @@ -140,7 +150,7 @@ pub extern "C" fn wallet_ffi_get_private_account_keys( /// The keys must be either null or valid keys returned by /// `wallet_ffi_get_private_account_keys`. #[no_mangle] -pub extern "C" fn wallet_ffi_free_private_account_keys(keys: *mut FfiPrivateAccountKeys) { +pub unsafe extern "C" fn wallet_ffi_free_private_account_keys(keys: *mut FfiPrivateAccountKeys) { if keys.is_null() { return; } @@ -168,12 +178,15 @@ pub extern "C" fn wallet_ffi_free_private_account_keys(keys: *mut FfiPrivateAcco /// /// # Memory /// The returned string must be freed with `wallet_ffi_free_string()`. +/// +/// # Safety +/// - `account_id` must be a valid pointer to a `FfiBytes32` struct #[no_mangle] -pub extern "C" fn wallet_ffi_account_id_to_base58( +pub unsafe extern "C" fn wallet_ffi_account_id_to_base58( account_id: *const FfiBytes32, ) -> *mut std::ffi::c_char { if account_id.is_null() { - set_last_error("Null account_id pointer"); + print_error("Null account_id pointer"); return ptr::null_mut(); } @@ -183,7 +196,7 @@ pub extern "C" fn wallet_ffi_account_id_to_base58( match std::ffi::CString::new(base58_str) { Ok(s) => s.into_raw(), Err(e) => { - set_last_error(format!("Failed to create C string: {}", e)); + print_error(format!("Failed to create C string: {}", e)); ptr::null_mut() } } @@ -199,13 +212,17 @@ pub extern "C" fn wallet_ffi_account_id_to_base58( /// - `Success` on successful parsing /// - `InvalidAccountId` if the string is not valid Base58 /// - Error code on other failures +/// +/// # Safety +/// - `base58_str` must be a valid pointer to a null-terminated C string +/// - `out_account_id` must be a valid pointer to a `FfiBytes32` struct #[no_mangle] -pub extern "C" fn wallet_ffi_account_id_from_base58( +pub unsafe extern "C" fn wallet_ffi_account_id_from_base58( base58_str: *const std::ffi::c_char, out_account_id: *mut FfiBytes32, ) -> WalletFfiError { if base58_str.is_null() || out_account_id.is_null() { - set_last_error("Null pointer argument"); + print_error("Null pointer argument"); return WalletFfiError::NullPointer; } @@ -213,7 +230,7 @@ pub extern "C" fn wallet_ffi_account_id_from_base58( let str_slice = match c_str.to_str() { Ok(s) => s, Err(e) => { - set_last_error(format!("Invalid UTF-8: {}", e)); + print_error(format!("Invalid UTF-8: {}", e)); return WalletFfiError::InvalidUtf8; } }; @@ -221,7 +238,7 @@ pub extern "C" fn wallet_ffi_account_id_from_base58( let account_id: AccountId = match str_slice.parse() { Ok(id) => id, Err(e) => { - set_last_error(format!("Invalid Base58 account ID: {}", e)); + print_error(format!("Invalid Base58 account ID: {}", e)); return WalletFfiError::InvalidAccountId; } }; diff --git a/wallet-ffi/src/lib.rs b/wallet-ffi/src/lib.rs index 2b5713b7..093c2f21 100644 --- a/wallet-ffi/src/lib.rs +++ b/wallet-ffi/src/lib.rs @@ -32,7 +32,7 @@ use once_cell::sync::OnceCell; use std::sync::Arc; use tokio::runtime::Runtime; -use crate::error::{set_last_error, WalletFfiError}; +use crate::error::{print_error, WalletFfiError}; // Re-export public types for cbindgen pub use error::WalletFfiError as FfiError; @@ -44,7 +44,7 @@ static RUNTIME: OnceCell> = OnceCell::new(); /// Get a reference to the global runtime. pub(crate) fn get_runtime() -> Result<&'static Arc, WalletFfiError> { RUNTIME.get().ok_or_else(|| { - set_last_error("Runtime not initialized. Call wallet_ffi_init_runtime() first."); + print_error("Runtime not initialized. Call wallet_ffi_init_runtime() first."); WalletFfiError::RuntimeError }) } @@ -75,7 +75,7 @@ pub extern "C" fn wallet_ffi_init_runtime() -> WalletFfiError { match result { Ok(_) => WalletFfiError::Success, Err(e) => { - set_last_error(format!("Failed to initialize runtime: {}", e)); + print_error(format!("Failed to initialize runtime: {}", e)); WalletFfiError::RuntimeError } } diff --git a/wallet-ffi/src/sync.rs b/wallet-ffi/src/sync.rs index b4e9deb8..d1d7d560 100644 --- a/wallet-ffi/src/sync.rs +++ b/wallet-ffi/src/sync.rs @@ -1,7 +1,7 @@ //! Block synchronization functions. use crate::block_on; -use crate::error::{set_last_error, WalletFfiError}; +use crate::error::{print_error, WalletFfiError}; use crate::types::WalletHandle; use crate::wallet::get_wallet; @@ -22,8 +22,11 @@ use crate::wallet::get_wallet; /// # Note /// This operation can take a while for large block ranges. The wallet /// internally uses a progress bar which may output to stdout. +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` #[no_mangle] -pub extern "C" fn wallet_ffi_sync_to_block( +pub unsafe extern "C" fn wallet_ffi_sync_to_block( handle: *mut WalletHandle, block_id: u64, ) -> WalletFfiError { @@ -35,7 +38,7 @@ pub extern "C" fn wallet_ffi_sync_to_block( let mut wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -43,7 +46,7 @@ pub extern "C" fn wallet_ffi_sync_to_block( match block_on(wallet.sync_to_block(block_id)) { Ok(Ok(())) => WalletFfiError::Success, Ok(Err(e)) => { - set_last_error(format!("Sync failed: {}", e)); + print_error(format!("Sync failed: {}", e)); WalletFfiError::SyncError } Err(e) => e, @@ -59,8 +62,12 @@ pub extern "C" fn wallet_ffi_sync_to_block( /// # Returns /// - `Success` on success /// - Error code on failure +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `out_block_id` must be a valid pointer to a `u64` #[no_mangle] -pub extern "C" fn wallet_ffi_get_last_synced_block( +pub unsafe extern "C" fn wallet_ffi_get_last_synced_block( handle: *mut WalletHandle, out_block_id: *mut u64, ) -> WalletFfiError { @@ -70,14 +77,14 @@ pub extern "C" fn wallet_ffi_get_last_synced_block( }; if out_block_id.is_null() { - set_last_error("Null output pointer"); + print_error("Null output pointer"); return WalletFfiError::NullPointer; } let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -99,8 +106,12 @@ pub extern "C" fn wallet_ffi_get_last_synced_block( /// - `Success` on success /// - `NetworkError` if the sequencer is unreachable /// - Error code on other failures +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `out_block_height` must be a valid pointer to a `u64` #[no_mangle] -pub extern "C" fn wallet_ffi_get_current_block_height( +pub unsafe extern "C" fn wallet_ffi_get_current_block_height( handle: *mut WalletHandle, out_block_height: *mut u64, ) -> WalletFfiError { @@ -110,14 +121,14 @@ pub extern "C" fn wallet_ffi_get_current_block_height( }; if out_block_height.is_null() { - set_last_error("Null output pointer"); + print_error("Null output pointer"); return WalletFfiError::NullPointer; } let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -130,7 +141,7 @@ pub extern "C" fn wallet_ffi_get_current_block_height( WalletFfiError::Success } Ok(Err(e)) => { - set_last_error(format!("Failed to get block height: {:?}", e)); + print_error(format!("Failed to get block height: {:?}", e)); WalletFfiError::NetworkError } Err(e) => e, diff --git a/wallet-ffi/src/transfer.rs b/wallet-ffi/src/transfer.rs index e11858da..c8357dcf 100644 --- a/wallet-ffi/src/transfer.rs +++ b/wallet-ffi/src/transfer.rs @@ -8,7 +8,7 @@ use nssa::AccountId; use wallet::program_facades::native_token_transfer::NativeTokenTransfer; use crate::block_on; -use crate::error::{set_last_error, WalletFfiError}; +use crate::error::{print_error, WalletFfiError}; use crate::types::{FfiBytes32, FfiTransferResult, WalletHandle}; use crate::wallet::get_wallet; @@ -31,8 +31,15 @@ use crate::wallet::get_wallet; /// /// # Memory /// The result must be freed with `wallet_ffi_free_transfer_result()`. +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `from` must be a valid pointer to a `FfiBytes32` struct +/// - `to` must be a valid pointer to a `FfiBytes32` struct +/// - `amount` must be a valid pointer to a `[u8; 16]` array +/// - `out_result` must be a valid pointer to a `FfiTransferResult` struct #[no_mangle] -pub extern "C" fn wallet_ffi_transfer_public( +pub unsafe extern "C" fn wallet_ffi_transfer_public( handle: *mut WalletHandle, from: *const FfiBytes32, to: *const FfiBytes32, @@ -45,14 +52,14 @@ pub extern "C" fn wallet_ffi_transfer_public( }; if from.is_null() || to.is_null() || amount.is_null() || out_result.is_null() { - set_last_error("Null pointer argument"); + print_error("Null pointer argument"); return WalletFfiError::NullPointer; } let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -76,7 +83,7 @@ pub extern "C" fn wallet_ffi_transfer_public( WalletFfiError::Success } Ok(Err(e)) => { - set_last_error(format!("Transfer failed: {:?}", e)); + print_error(format!("Transfer failed: {:?}", e)); unsafe { (*out_result).tx_hash = ptr::null_mut(); (*out_result).success = false; @@ -109,8 +116,13 @@ pub extern "C" fn wallet_ffi_transfer_public( /// /// # Memory /// The result must be freed with `wallet_ffi_free_transfer_result()`. +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `account_id` must be a valid pointer to a `FfiBytes32` struct +/// - `out_result` must be a valid pointer to a `FfiTransferResult` struct #[no_mangle] -pub extern "C" fn wallet_ffi_register_public_account( +pub unsafe extern "C" fn wallet_ffi_register_public_account( handle: *mut WalletHandle, account_id: *const FfiBytes32, out_result: *mut FfiTransferResult, @@ -121,14 +133,14 @@ pub extern "C" fn wallet_ffi_register_public_account( }; if account_id.is_null() || out_result.is_null() { - set_last_error("Null pointer argument"); + print_error("Null pointer argument"); return WalletFfiError::NullPointer; } let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -150,7 +162,7 @@ pub extern "C" fn wallet_ffi_register_public_account( WalletFfiError::Success } Ok(Err(e)) => { - set_last_error(format!("Registration failed: {:?}", e)); + print_error(format!("Registration failed: {:?}", e)); unsafe { (*out_result).tx_hash = ptr::null_mut(); (*out_result).success = false; @@ -172,7 +184,7 @@ pub extern "C" fn wallet_ffi_register_public_account( /// # Safety /// The result must be either null or a valid result from a transfer function. #[no_mangle] -pub extern "C" fn wallet_ffi_free_transfer_result(result: *mut FfiTransferResult) { +pub unsafe extern "C" fn wallet_ffi_free_transfer_result(result: *mut FfiTransferResult) { if result.is_null() { return; } diff --git a/wallet-ffi/src/wallet.rs b/wallet-ffi/src/wallet.rs index 7c70e19d..6c17d930 100644 --- a/wallet-ffi/src/wallet.rs +++ b/wallet-ffi/src/wallet.rs @@ -8,7 +8,7 @@ use std::sync::Mutex; use wallet::WalletCore; use crate::block_on; -use crate::error::{set_last_error, WalletFfiError}; +use crate::error::{print_error, WalletFfiError}; use crate::types::WalletHandle; /// Internal wrapper around WalletCore with mutex for thread safety. @@ -21,7 +21,7 @@ pub(crate) fn get_wallet( handle: *mut WalletHandle, ) -> Result<&'static WalletWrapper, WalletFfiError> { if handle.is_null() { - set_last_error("Null wallet handle"); + print_error("Null wallet handle"); return Err(WalletFfiError::NullPointer); } Ok(unsafe { &*(handle as *mut WalletWrapper) }) @@ -33,7 +33,7 @@ pub(crate) fn get_wallet_mut( handle: *mut WalletHandle, ) -> Result<&'static mut WalletWrapper, WalletFfiError> { if handle.is_null() { - set_last_error("Null wallet handle"); + print_error("Null wallet handle"); return Err(WalletFfiError::NullPointer); } Ok(unsafe { &mut *(handle as *mut WalletWrapper) }) @@ -42,7 +42,7 @@ pub(crate) fn get_wallet_mut( /// Helper to convert a C string to a Rust PathBuf. fn c_str_to_path(ptr: *const c_char, name: &str) -> Result { if ptr.is_null() { - set_last_error(format!("Null pointer for {}", name)); + print_error(format!("Null pointer for {}", name)); return Err(WalletFfiError::NullPointer); } @@ -50,7 +50,7 @@ fn c_str_to_path(ptr: *const c_char, name: &str) -> Result Ok(PathBuf::from(s)), Err(e) => { - set_last_error(format!("Invalid UTF-8 in {}: {}", name, e)); + print_error(format!("Invalid UTF-8 in {}: {}", name, e)); Err(WalletFfiError::InvalidUtf8) } } @@ -59,7 +59,7 @@ fn c_str_to_path(ptr: *const c_char, name: &str) -> Result Result { if ptr.is_null() { - set_last_error(format!("Null pointer for {}", name)); + print_error(format!("Null pointer for {}", name)); return Err(WalletFfiError::NullPointer); } @@ -67,7 +67,7 @@ fn c_str_to_string(ptr: *const c_char, name: &str) -> Result Ok(s.to_string()), Err(e) => { - set_last_error(format!("Invalid UTF-8 in {}: {}", name, e)); + print_error(format!("Invalid UTF-8 in {}: {}", name, e)); Err(WalletFfiError::InvalidUtf8) } } @@ -90,7 +90,7 @@ fn c_str_to_string(ptr: *const c_char, name: &str) -> Result { - set_last_error(format!("Failed to create wallet: {}", e)); + print_error(format!("Failed to create wallet: {}", e)); ptr::null_mut() } } @@ -139,7 +139,7 @@ pub extern "C" fn wallet_ffi_create_new( /// # Safety /// All string parameters must be valid null-terminated UTF-8 strings. #[no_mangle] -pub extern "C" fn wallet_ffi_open( +pub unsafe extern "C" fn wallet_ffi_open( config_path: *const c_char, storage_path: *const c_char, ) -> *mut WalletHandle { @@ -161,7 +161,7 @@ pub extern "C" fn wallet_ffi_open( Box::into_raw(wrapper) as *mut WalletHandle } Err(e) => { - set_last_error(format!("Failed to open wallet: {}", e)); + print_error(format!("Failed to open wallet: {}", e)); ptr::null_mut() } } @@ -176,7 +176,7 @@ pub extern "C" fn wallet_ffi_open( /// or `wallet_ffi_open()`. /// - The handle must not be used after this call. #[no_mangle] -pub extern "C" fn wallet_ffi_destroy(handle: *mut WalletHandle) { +pub unsafe extern "C" fn wallet_ffi_destroy(handle: *mut WalletHandle) { if !handle.is_null() { unsafe { drop(Box::from_raw(handle as *mut WalletWrapper)); @@ -195,8 +195,11 @@ pub extern "C" fn wallet_ffi_destroy(handle: *mut WalletHandle) { /// # 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 extern "C" fn wallet_ffi_save(handle: *mut WalletHandle) -> WalletFfiError { +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, @@ -205,7 +208,7 @@ pub extern "C" fn wallet_ffi_save(handle: *mut WalletHandle) -> WalletFfiError { let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return WalletFfiError::InternalError; } }; @@ -213,7 +216,7 @@ pub extern "C" fn wallet_ffi_save(handle: *mut WalletHandle) -> WalletFfiError { match block_on(wallet.store_persistent_data()) { Ok(Ok(())) => WalletFfiError::Success, Ok(Err(e)) => { - set_last_error(format!("Failed to save wallet: {}", e)); + print_error(format!("Failed to save wallet: {}", e)); WalletFfiError::StorageError } Err(e) => e, @@ -228,8 +231,11 @@ pub extern "C" fn wallet_ffi_save(handle: *mut WalletHandle) -> WalletFfiError { /// # 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 extern "C" fn wallet_ffi_get_sequencer_addr(handle: *mut WalletHandle) -> *mut c_char { +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(), @@ -238,7 +244,7 @@ pub extern "C" fn wallet_ffi_get_sequencer_addr(handle: *mut WalletHandle) -> *m let wallet = match wrapper.core.lock() { Ok(w) => w, Err(e) => { - set_last_error(format!("Failed to lock wallet: {}", e)); + print_error(format!("Failed to lock wallet: {}", e)); return ptr::null_mut(); } }; @@ -248,7 +254,7 @@ pub extern "C" fn wallet_ffi_get_sequencer_addr(handle: *mut WalletHandle) -> *m match std::ffi::CString::new(addr) { Ok(s) => s.into_raw(), Err(e) => { - set_last_error(format!("Invalid sequencer address: {}", e)); + print_error(format!("Invalid sequencer address: {}", e)); ptr::null_mut() } } @@ -259,7 +265,7 @@ pub extern "C" fn wallet_ffi_get_sequencer_addr(handle: *mut WalletHandle) -> *m /// # Safety /// The pointer must be either null or a valid string returned by an FFI function. #[no_mangle] -pub extern "C" fn wallet_ffi_free_string(ptr: *mut c_char) { +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)); diff --git a/wallet-ffi/wallet_ffi.h b/wallet-ffi/wallet_ffi.h index 6465ac25..7ae524ba 100644 --- a/wallet-ffi/wallet_ffi.h +++ b/wallet-ffi/wallet_ffi.h @@ -240,6 +240,10 @@ bool wallet_ffi_runtime_initialized(void); * # Returns * - `Success` on successful creation * - Error code on failure + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `out_account_id` must be a valid pointer to a `FfiBytes32` struct */ enum WalletFfiError wallet_ffi_create_account_public(struct WalletHandle *handle, struct FfiBytes32 *out_account_id); @@ -257,6 +261,10 @@ enum WalletFfiError wallet_ffi_create_account_public(struct WalletHandle *handle * # Returns * - `Success` on successful creation * - Error code on failure + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `out_account_id` must be a valid pointer to a `FfiBytes32` struct */ enum WalletFfiError wallet_ffi_create_account_private(struct WalletHandle *handle, struct FfiBytes32 *out_account_id); @@ -276,6 +284,10 @@ enum WalletFfiError wallet_ffi_create_account_private(struct WalletHandle *handl * * # Memory * The returned list must be freed with `wallet_ffi_free_account_list()`. + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `out_list` must be a valid pointer to a `FfiAccountList` struct */ enum WalletFfiError wallet_ffi_list_accounts(struct WalletHandle *handle, struct FfiAccountList *out_list); @@ -303,6 +315,11 @@ void wallet_ffi_free_account_list(struct FfiAccountList *list); * # Returns * - `Success` on successful query * - Error code on failure + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `account_id` must be a valid pointer to a `FfiBytes32` struct + * - `out_balance` must be a valid pointer to a `[u8; 16]` array */ enum WalletFfiError wallet_ffi_get_balance(struct WalletHandle *handle, const struct FfiBytes32 *account_id, @@ -323,6 +340,11 @@ enum WalletFfiError wallet_ffi_get_balance(struct WalletHandle *handle, * * # Memory * The account data must be freed with `wallet_ffi_free_account_data()`. + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `account_id` must be a valid pointer to a `FfiBytes32` struct + * - `out_account` must be a valid pointer to a `FfiAccount` struct */ enum WalletFfiError wallet_ffi_get_account_public(struct WalletHandle *handle, const struct FfiBytes32 *account_id, @@ -337,24 +359,6 @@ enum WalletFfiError wallet_ffi_get_account_public(struct WalletHandle *handle, */ void wallet_ffi_free_account_data(struct FfiAccount *account); -/** - * Get the last error message. - * - * Returns a pointer to a null-terminated string, or null if no error is set. - * The caller owns the returned string and must free it with - * `wallet_ffi_free_error_string`. - */ -char *wallet_ffi_get_last_error(void); - -/** - * Free an error string returned by `wallet_ffi_get_last_error`. - * - * # Safety - * The pointer must be either null or a valid pointer returned by - * `wallet_ffi_get_last_error`. - */ -void wallet_ffi_free_error_string(char *ptr); - /** * Get the public key for a public account. * @@ -369,6 +373,11 @@ void wallet_ffi_free_error_string(char *ptr); * - `Success` on successful retrieval * - `KeyNotFound` if the account's key is not in this wallet * - Error code on other failures + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `account_id` must be a valid pointer to a `FfiBytes32` struct + * - `out_public_key` must be a valid pointer to a `FfiPublicAccountKey` struct */ enum WalletFfiError wallet_ffi_get_public_account_key(struct WalletHandle *handle, const struct FfiBytes32 *account_id, @@ -392,6 +401,11 @@ enum WalletFfiError wallet_ffi_get_public_account_key(struct WalletHandle *handl * * # Memory * The keys structure must be freed with `wallet_ffi_free_private_account_keys()`. + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `account_id` must be a valid pointer to a `FfiBytes32` struct + * - `out_keys` must be a valid pointer to a `FfiPrivateAccountKeys` struct */ enum WalletFfiError wallet_ffi_get_private_account_keys(struct WalletHandle *handle, const struct FfiBytes32 *account_id, @@ -418,6 +432,9 @@ void wallet_ffi_free_private_account_keys(struct FfiPrivateAccountKeys *keys); * * # Memory * The returned string must be freed with `wallet_ffi_free_string()`. + * + * # Safety + * - `account_id` must be a valid pointer to a `FfiBytes32` struct */ char *wallet_ffi_account_id_to_base58(const struct FfiBytes32 *account_id); @@ -432,6 +449,10 @@ char *wallet_ffi_account_id_to_base58(const struct FfiBytes32 *account_id); * - `Success` on successful parsing * - `InvalidAccountId` if the string is not valid Base58 * - Error code on other failures + * + * # Safety + * - `base58_str` must be a valid pointer to a null-terminated C string + * - `out_account_id` must be a valid pointer to a `FfiBytes32` struct */ enum WalletFfiError wallet_ffi_account_id_from_base58(const char *base58_str, struct FfiBytes32 *out_account_id); @@ -454,6 +475,9 @@ enum WalletFfiError wallet_ffi_account_id_from_base58(const char *base58_str, * # Note * This operation can take a while for large block ranges. The wallet * internally uses a progress bar which may output to stdout. + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` */ enum WalletFfiError wallet_ffi_sync_to_block(struct WalletHandle *handle, uint64_t block_id); @@ -467,6 +491,10 @@ enum WalletFfiError wallet_ffi_sync_to_block(struct WalletHandle *handle, uint64 * # Returns * - `Success` on success * - Error code on failure + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `out_block_id` must be a valid pointer to a `u64` */ enum WalletFfiError wallet_ffi_get_last_synced_block(struct WalletHandle *handle, uint64_t *out_block_id); @@ -482,6 +510,10 @@ enum WalletFfiError wallet_ffi_get_last_synced_block(struct WalletHandle *handle * - `Success` on success * - `NetworkError` if the sequencer is unreachable * - Error code on other failures + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `out_block_height` must be a valid pointer to a `u64` */ enum WalletFfiError wallet_ffi_get_current_block_height(struct WalletHandle *handle, uint64_t *out_block_height); @@ -506,6 +538,13 @@ enum WalletFfiError wallet_ffi_get_current_block_height(struct WalletHandle *han * * # Memory * The result must be freed with `wallet_ffi_free_transfer_result()`. + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `from` must be a valid pointer to a `FfiBytes32` struct + * - `to` must be a valid pointer to a `FfiBytes32` struct + * - `amount` must be a valid pointer to a `[u8; 16]` array + * - `out_result` must be a valid pointer to a `FfiTransferResult` struct */ enum WalletFfiError wallet_ffi_transfer_public(struct WalletHandle *handle, const struct FfiBytes32 *from, @@ -530,6 +569,11 @@ enum WalletFfiError wallet_ffi_transfer_public(struct WalletHandle *handle, * * # Memory * The result must be freed with `wallet_ffi_free_transfer_result()`. + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `account_id` must be a valid pointer to a `FfiBytes32` struct + * - `out_result` must be a valid pointer to a `FfiTransferResult` struct */ enum WalletFfiError wallet_ffi_register_public_account(struct WalletHandle *handle, const struct FfiBytes32 *account_id, @@ -608,6 +652,9 @@ void wallet_ffi_destroy(struct WalletHandle *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` */ enum WalletFfiError wallet_ffi_save(struct WalletHandle *handle); @@ -620,6 +667,9 @@ enum WalletFfiError wallet_ffi_save(struct WalletHandle *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` */ char *wallet_ffi_get_sequencer_addr(struct WalletHandle *handle); From c44e8d6a892290b135e27c871abd40aa87304e1c Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 26 Jan 2026 10:36:14 +0100 Subject: [PATCH 5/9] Refactor runtime initialization --- wallet-ffi/src/lib.rs | 33 ++++++--------------------------- wallet-ffi/wallet_ffi.h | 9 --------- 2 files changed, 6 insertions(+), 36 deletions(-) diff --git a/wallet-ffi/src/lib.rs b/wallet-ffi/src/lib.rs index 093c2f21..d4fa4051 100644 --- a/wallet-ffi/src/lib.rs +++ b/wallet-ffi/src/lib.rs @@ -28,9 +28,7 @@ pub mod transfer; pub mod types; pub mod wallet; -use once_cell::sync::OnceCell; -use std::sync::Arc; -use tokio::runtime::Runtime; +use tokio::runtime::{Handle}; use crate::error::{print_error, WalletFfiError}; @@ -38,15 +36,9 @@ use crate::error::{print_error, WalletFfiError}; pub use error::WalletFfiError as FfiError; pub use types::*; -// Global Tokio runtime - initialized once -static RUNTIME: OnceCell> = OnceCell::new(); - /// Get a reference to the global runtime. -pub(crate) fn get_runtime() -> Result<&'static Arc, WalletFfiError> { - RUNTIME.get().ok_or_else(|| { - print_error("Runtime not initialized. Call wallet_ffi_init_runtime() first."); - WalletFfiError::RuntimeError - }) +pub(crate) fn get_runtime() -> Result { + Handle::try_current().map_err(|_| WalletFfiError::RuntimeError) } /// Run an async future on the global runtime, blocking until completion. @@ -65,12 +57,9 @@ pub(crate) fn block_on(future: F) -> Result WalletFfiError { - let result = RUNTIME.get_or_try_init(|| { - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .map(Arc::new) - }); + let result = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build(); match result { Ok(_) => WalletFfiError::Success, @@ -80,13 +69,3 @@ pub extern "C" fn wallet_ffi_init_runtime() -> WalletFfiError { } } } - -/// Check if the runtime is initialized. -/// -/// # Returns -/// - `true` if the runtime is ready -/// - `false` if `wallet_ffi_init_runtime()` hasn't been called yet -#[no_mangle] -pub extern "C" fn wallet_ffi_runtime_initialized() -> bool { - RUNTIME.get().is_some() -} diff --git a/wallet-ffi/wallet_ffi.h b/wallet-ffi/wallet_ffi.h index 7ae524ba..4158110b 100644 --- a/wallet-ffi/wallet_ffi.h +++ b/wallet-ffi/wallet_ffi.h @@ -218,15 +218,6 @@ typedef struct FfiTransferResult { */ enum WalletFfiError wallet_ffi_init_runtime(void); -/** - * Check if the runtime is initialized. - * - * # Returns - * - `true` if the runtime is ready - * - `false` if `wallet_ffi_init_runtime()` hasn't been called yet - */ -bool wallet_ffi_runtime_initialized(void); - /** * Create a new public account. * From 26a39bb005cd1cfadb311d4d712288317dd07d80 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 26 Jan 2026 10:36:30 +0100 Subject: [PATCH 6/9] Fmt happy --- wallet-ffi/src/lib.rs | 2 +- wallet-ffi/src/types.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/wallet-ffi/src/lib.rs b/wallet-ffi/src/lib.rs index d4fa4051..3058d8b5 100644 --- a/wallet-ffi/src/lib.rs +++ b/wallet-ffi/src/lib.rs @@ -28,7 +28,7 @@ pub mod transfer; pub mod types; pub mod wallet; -use tokio::runtime::{Handle}; +use tokio::runtime::Handle; use crate::error::{print_error, WalletFfiError}; diff --git a/wallet-ffi/src/types.rs b/wallet-ffi/src/types.rs index 9bb67f19..3bcfd9fd 100644 --- a/wallet-ffi/src/types.rs +++ b/wallet-ffi/src/types.rs @@ -149,4 +149,3 @@ impl From for nssa::AccountId { nssa::AccountId::new(bytes.data) } } - From 272cbfe4006e5947c2cc696c491f54f46aaf5d45 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 26 Jan 2026 10:43:46 +0100 Subject: [PATCH 7/9] Remove unused dependency once_cell from Cargo.toml --- Cargo.lock | 1 - wallet-ffi/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b4784794..61c7f64a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4873,7 +4873,6 @@ dependencies = [ "cbindgen", "common", "nssa", - "once_cell", "tokio", "wallet", ] diff --git a/wallet-ffi/Cargo.toml b/wallet-ffi/Cargo.toml index ae1ba57e..bc989fea 100644 --- a/wallet-ffi/Cargo.toml +++ b/wallet-ffi/Cargo.toml @@ -11,7 +11,6 @@ wallet.workspace = true nssa.workspace = true common.workspace = true tokio.workspace = true -once_cell.workspace = true [build-dependencies] cbindgen = "0.26" From addc65933f1c63f9cfa33b0ec1c193f191a80c5f Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 26 Jan 2026 10:45:51 +0100 Subject: [PATCH 8/9] Fmt happy in nightly --- wallet-ffi/src/account.rs | 12 +++++++----- wallet-ffi/src/keys.rs | 8 +++++--- wallet-ffi/src/lib.rs | 7 +++---- wallet-ffi/src/sync.rs | 10 ++++++---- wallet-ffi/src/transfer.rs | 13 +++++++------ wallet-ffi/src/wallet.rs | 25 +++++++++++++++---------- 6 files changed, 43 insertions(+), 32 deletions(-) diff --git a/wallet-ffi/src/account.rs b/wallet-ffi/src/account.rs index a058acc7..b99d10cf 100644 --- a/wallet-ffi/src/account.rs +++ b/wallet-ffi/src/account.rs @@ -4,12 +4,14 @@ use std::ptr; use nssa::AccountId; -use crate::block_on; -use crate::error::{print_error, WalletFfiError}; -use crate::types::{ - FfiAccount, FfiAccountList, FfiAccountListEntry, FfiBytes32, FfiProgramId, WalletHandle, +use crate::{ + block_on, + error::{print_error, WalletFfiError}, + types::{ + FfiAccount, FfiAccountList, FfiAccountListEntry, FfiBytes32, FfiProgramId, WalletHandle, + }, + wallet::get_wallet, }; -use crate::wallet::get_wallet; /// Create a new public account. /// diff --git a/wallet-ffi/src/keys.rs b/wallet-ffi/src/keys.rs index 27989c0e..e8309a81 100644 --- a/wallet-ffi/src/keys.rs +++ b/wallet-ffi/src/keys.rs @@ -4,9 +4,11 @@ use std::ptr; use nssa::{AccountId, PublicKey}; -use crate::error::{print_error, WalletFfiError}; -use crate::types::{FfiBytes32, FfiPrivateAccountKeys, FfiPublicAccountKey, WalletHandle}; -use crate::wallet::get_wallet; +use crate::{ + error::{print_error, WalletFfiError}, + types::{FfiBytes32, FfiPrivateAccountKeys, FfiPublicAccountKey, WalletHandle}, + wallet::get_wallet, +}; /// Get the public key for a public account. /// diff --git a/wallet-ffi/src/lib.rs b/wallet-ffi/src/lib.rs index 3058d8b5..75032300 100644 --- a/wallet-ffi/src/lib.rs +++ b/wallet-ffi/src/lib.rs @@ -28,14 +28,13 @@ pub mod transfer; pub mod types; pub mod wallet; -use tokio::runtime::Handle; - -use crate::error::{print_error, WalletFfiError}; - // Re-export public types for cbindgen pub use error::WalletFfiError as FfiError; +use tokio::runtime::Handle; pub use types::*; +use crate::error::{print_error, WalletFfiError}; + /// Get a reference to the global runtime. pub(crate) fn get_runtime() -> Result { Handle::try_current().map_err(|_| WalletFfiError::RuntimeError) diff --git a/wallet-ffi/src/sync.rs b/wallet-ffi/src/sync.rs index d1d7d560..3979f935 100644 --- a/wallet-ffi/src/sync.rs +++ b/wallet-ffi/src/sync.rs @@ -1,9 +1,11 @@ //! Block synchronization functions. -use crate::block_on; -use crate::error::{print_error, WalletFfiError}; -use crate::types::WalletHandle; -use crate::wallet::get_wallet; +use crate::{ + block_on, + error::{print_error, WalletFfiError}, + types::WalletHandle, + wallet::get_wallet, +}; /// Synchronize private accounts to a specific block. /// diff --git a/wallet-ffi/src/transfer.rs b/wallet-ffi/src/transfer.rs index c8357dcf..055f0c32 100644 --- a/wallet-ffi/src/transfer.rs +++ b/wallet-ffi/src/transfer.rs @@ -1,16 +1,17 @@ //! Token transfer functions. -use std::ffi::CString; -use std::ptr; +use std::{ffi::CString, ptr}; use common::error::ExecutionFailureKind; use nssa::AccountId; use wallet::program_facades::native_token_transfer::NativeTokenTransfer; -use crate::block_on; -use crate::error::{print_error, WalletFfiError}; -use crate::types::{FfiBytes32, FfiTransferResult, WalletHandle}; -use crate::wallet::get_wallet; +use crate::{ + block_on, + error::{print_error, WalletFfiError}, + types::{FfiBytes32, FfiTransferResult, WalletHandle}, + wallet::get_wallet, +}; /// Send a public token transfer. /// diff --git a/wallet-ffi/src/wallet.rs b/wallet-ffi/src/wallet.rs index 6c17d930..6f817f8e 100644 --- a/wallet-ffi/src/wallet.rs +++ b/wallet-ffi/src/wallet.rs @@ -1,15 +1,19 @@ //! Wallet lifecycle management functions. -use std::ffi::{c_char, CStr}; -use std::path::PathBuf; -use std::ptr; -use std::sync::Mutex; +use std::{ + ffi::{c_char, CStr}, + path::PathBuf, + ptr, + sync::Mutex, +}; use wallet::WalletCore; -use crate::block_on; -use crate::error::{print_error, WalletFfiError}; -use crate::types::WalletHandle; +use crate::{ + block_on, + error::{print_error, WalletFfiError}, + types::WalletHandle, +}; /// Internal wrapper around WalletCore with mutex for thread safety. pub(crate) struct WalletWrapper { @@ -172,8 +176,8 @@ pub unsafe extern "C" fn wallet_ffi_open( /// 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 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) { @@ -229,7 +233,8 @@ pub unsafe extern "C" fn wallet_ffi_save(handle: *mut WalletHandle) -> WalletFfi /// - `handle`: Valid wallet handle /// /// # Returns -/// - Pointer to null-terminated string on success (caller must free with `wallet_ffi_free_string()`) +/// - Pointer to null-terminated string on success (caller must free with +/// `wallet_ffi_free_string()`) /// - Null pointer on error /// /// # Safety From 82d5ce403c6c077b08bbf3f2e83740ae313c97d6 Mon Sep 17 00:00:00 2001 From: Daniel Date: Mon, 26 Jan 2026 10:53:17 +0100 Subject: [PATCH 9/9] Remove oncecell dep from workspace Cargo.toml --- Cargo.toml | 65 ++++++++++++++++++++--------------------- wallet-ffi/wallet_ffi.h | 7 +++-- 2 files changed, 35 insertions(+), 37 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index dffff452..7e174ecf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,25 +1,25 @@ [workspace] resolver = "3" members = [ - "integration_tests", - "sequencer_runner", - "storage", - "key_protocol", - "sequencer_rpc", - "mempool", - "wallet", - "wallet-ffi", - "sequencer_core", - "common", - "nssa", - "nssa/core", - "program_methods", - "program_methods/guest", - "test_program_methods", - "test_program_methods/guest", - "examples/program_deployment", - "examples/program_deployment/methods", - "examples/program_deployment/methods/guest", + "integration_tests", + "sequencer_runner", + "storage", + "key_protocol", + "sequencer_rpc", + "mempool", + "wallet", + "wallet-ffi", + "sequencer_core", + "common", + "nssa", + "nssa/core", + "program_methods", + "program_methods/guest", + "test_program_methods", + "test_program_methods/guest", + "examples/program_deployment", + "examples/program_deployment/methods", + "examples/program_deployment/methods/guest", ] [workspace.dependencies] @@ -35,14 +35,11 @@ sequencer_runner = { path = "sequencer_runner" } wallet = { path = "wallet" } wallet-ffi = { path = "wallet-ffi" } test_program_methods = { path = "test_program_methods" } - -once_cell = "1.19" - tokio = { version = "1.28.2", features = [ - "net", - "rt-multi-thread", - "sync", - "fs", + "net", + "rt-multi-thread", + "sync", + "fs", ] } risc0-zkvm = { version = "3.0.3", features = ['std'] } risc0-build = "3.0.3" @@ -81,20 +78,20 @@ base58 = "0.2.0" itertools = "0.14.0" rocksdb = { version = "0.24.0", default-features = false, features = [ - "snappy", - "bindgen-runtime", + "snappy", + "bindgen-runtime", ] } rand = { version = "0.8.5", features = ["std", "std_rng", "getrandom"] } k256 = { version = "0.13.3", features = [ - "ecdsa-core", - "arithmetic", - "expose-field", - "serde", - "pem", + "ecdsa-core", + "arithmetic", + "expose-field", + "serde", + "pem", ] } elliptic-curve = { version = "0.13.8", features = ["arithmetic"] } actix-web = { version = "=4.1.0", default-features = false, features = [ - "macros", + "macros", ] } clap = { version = "4.5.42", features = ["derive", "env"] } reqwest = { version = "0.11.16", features = ["json"] } diff --git a/wallet-ffi/wallet_ffi.h b/wallet-ffi/wallet_ffi.h index 4158110b..4786e5cc 100644 --- a/wallet-ffi/wallet_ffi.h +++ b/wallet-ffi/wallet_ffi.h @@ -625,8 +625,8 @@ struct WalletHandle *wallet_ffi_open(const char *config_path, const char *storag * 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 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. */ void wallet_ffi_destroy(struct WalletHandle *handle); @@ -656,7 +656,8 @@ enum WalletFfiError wallet_ffi_save(struct WalletHandle *handle); * - `handle`: Valid wallet handle * * # Returns - * - Pointer to null-terminated string on success (caller must free with `wallet_ffi_free_string()`) + * - Pointer to null-terminated string on success (caller must free with + * `wallet_ffi_free_string()`) * - Null pointer on error * * # Safety