From 860746dac9b201b1d59268536daf840870057136 Mon Sep 17 00:00:00 2001 From: Pravdyvy Date: Thu, 25 Jun 2026 18:29:35 +0300 Subject: [PATCH] feat(wallet_ffi): labels added --- lez/wallet-ffi/src/label.rs | 315 ++++++++++++++++++++++++++++++++++++ lez/wallet-ffi/src/lib.rs | 1 + lez/wallet-ffi/src/types.rs | 34 +++- lez/wallet-ffi/wallet_ffi.h | 107 ++++++++++++ lez/wallet/src/account.rs | 5 + 5 files changed, 461 insertions(+), 1 deletion(-) create mode 100644 lez/wallet-ffi/src/label.rs diff --git a/lez/wallet-ffi/src/label.rs b/lez/wallet-ffi/src/label.rs new file mode 100644 index 00000000..7a3025cd --- /dev/null +++ b/lez/wallet-ffi/src/label.rs @@ -0,0 +1,315 @@ +use std::{ + ffi::{c_char, CString}, + str::FromStr as _, +}; + +use crate::{ + c_str_to_string, + error::{print_error, WalletFfiError}, + wallet::get_wallet, + FfiAccountIdWithPrivacy, WalletHandle, +}; + +#[repr(C)] +pub struct LabelAvailability { + is_available: bool, + error: WalletFfiError, +} + +impl LabelAvailability { + #[must_use] + pub const fn availability(is_available: bool) -> Self { + Self { + is_available, + error: WalletFfiError::Success, + } + } + + #[must_use] + pub const fn error(error: WalletFfiError) -> Self { + Self { + is_available: false, + error, + } + } +} + +#[repr(C)] +pub struct AccountIdResolvedFromLabel { + account_id: FfiAccountIdWithPrivacy, + error: WalletFfiError, +} + +impl AccountIdResolvedFromLabel { + #[must_use] + pub const fn account_id(account_id: FfiAccountIdWithPrivacy) -> Self { + Self { + account_id, + error: WalletFfiError::Success, + } + } + + #[must_use] + pub fn error(error: WalletFfiError) -> Self { + Self { + account_id: FfiAccountIdWithPrivacy::default(), + error, + } + } +} + +#[repr(C)] +pub struct LabelList { + labels_data: *mut *const c_char, + labels_size: usize, + error: WalletFfiError, +} + +impl LabelList { + #[must_use] + pub fn from_labels(labels: Vec<*const c_char>) -> Self { + let labels_size = labels.len(); + let boxed_slice = labels.into_boxed_slice(); + let labels_data = Box::into_raw(boxed_slice).cast::<*const c_char>(); + + Self { + labels_data, + labels_size, + error: WalletFfiError::Success, + } + } + + #[must_use] + pub const fn error(error: WalletFfiError) -> Self { + Self { + labels_data: std::ptr::null_mut(), + labels_size: 0, + error, + } + } +} + +/// Check if label is available. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `label`: Input null terminated C string for a label +/// +/// # Returns +/// - `LabelAvailability` struct +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `label` must be a valid pointer to a null-terminated C string +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_check_label_available( + handle: *mut WalletHandle, + label: *const c_char, +) -> LabelAvailability { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return LabelAvailability::error(e), + }; + + let label = match c_str_to_string(label, "label") { + Ok(value) => value, + Err(e) => return LabelAvailability::error(e), + }; + + let wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + print_error(format!("Failed to lock wallet: {e}")); + return LabelAvailability::error(WalletFfiError::InternalError); + } + }; + + let is_available = wallet + .storage() + .check_label_availability(&label.into()) + .is_ok(); + + LabelAvailability::availability(is_available) +} + +/// Add new label. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `label`: Input null terminated C string for a label +/// - `account_id_with_privacy`: The account ID (32 bytes) and its privacy. +/// +/// # 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` +/// - `label` must be a valid pointer to a null-terminated C string +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_add_label( + handle: *mut WalletHandle, + label: *const c_char, + account_id_with_privacy: FfiAccountIdWithPrivacy, +) -> WalletFfiError { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return e, + }; + + let label = match c_str_to_string(label, "label") { + Ok(value) => value, + Err(e) => return e, + }; + + let mut wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + print_error(format!("Failed to lock wallet: {e}")); + return WalletFfiError::InternalError; + } + }; + + match wallet + .storage_mut() + .add_label(label.into(), account_id_with_privacy.into()) + { + Ok(()) => WalletFfiError::Success, + Err(err) => { + print_error(format!("Failed to add label : {err}")); + WalletFfiError::InternalError + } + } +} + +/// Resolve a label. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `label`: Input null terminated C string for a label +/// +/// # Returns +/// - `AccountIdResolvedFromLabel` struct +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +/// - `label` must be a valid pointer to a null-terminated C string +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_resolve_label( + handle: *mut WalletHandle, + label: *const c_char, +) -> AccountIdResolvedFromLabel { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return AccountIdResolvedFromLabel::error(e), + }; + + let label = match c_str_to_string(label, "label") { + Ok(value) => value, + Err(e) => return AccountIdResolvedFromLabel::error(e), + }; + + let mut wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + print_error(format!("Failed to lock wallet: {e}")); + return AccountIdResolvedFromLabel::error(WalletFfiError::InternalError); + } + }; + + wallet + .storage_mut() + .resolve_label(&label.into()) + .map_or_else( + || { + print_error("Failed to resolve label"); + AccountIdResolvedFromLabel::error(WalletFfiError::InternalError) + }, + |acc_id| AccountIdResolvedFromLabel::account_id(acc_id.into()), + ) +} + +/// Get all labels for account. +/// +/// # Parameters +/// - `handle`: Valid wallet handle +/// - `account_id_with_privacy`: The account ID (32 bytes) and its privacy. +/// +/// # Returns +/// - `LabelList` struct +/// +/// # Safety +/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_get_all_labels_for_account( + handle: *mut WalletHandle, + account_id_with_privacy: FfiAccountIdWithPrivacy, +) -> LabelList { + let wrapper = match get_wallet(handle) { + Ok(w) => w, + Err(e) => return LabelList::error(e), + }; + + let wallet = match wrapper.core.lock() { + Ok(w) => w, + Err(e) => { + print_error(format!("Failed to lock wallet: {e}")); + return LabelList::error(WalletFfiError::InternalError); + } + }; + + let mut labels = vec![]; + + for label in wallet + .storage() + .labels_for_account(account_id_with_privacy.into()) + { + let Ok(label_c) = CString::from_str(label.inner()) else { + print_error(format!("Failed to cast label into C string: {label}")); + return LabelList::error(WalletFfiError::InternalError); + }; + + let label_raw = label_c.into_raw().cast_const(); + + labels.push(label_raw); + } + + LabelList::from_labels(labels) +} + +/// Free label list. +/// +/// # Parameters +/// - `label_list`: Input list of labels +/// +/// # Returns +/// - `Success` on successful query +/// - Error code on failure +/// +/// # Safety +/// - `label_list` must be a valid pointer to `LabelList`, received from +/// `wallet_ffi_get_all_labels_for_account` +#[no_mangle] +pub unsafe extern "C" fn wallet_ffi_free_label_list(label_list: *mut LabelList) -> WalletFfiError { + if label_list.is_null() { + return WalletFfiError::NullPointer; + } + + let labels_raw = unsafe { &*label_list }; + + if !labels_raw.labels_data.is_null() && labels_raw.labels_size > 0 { + let labels_slice = + std::slice::from_raw_parts_mut(labels_raw.labels_data, labels_raw.labels_size); + + for label_ptr in labels_slice.iter() { + if !(*label_ptr).is_null() { + drop(CString::from_raw((*label_ptr).cast_mut())); + } + } + + let boxed_slice = Box::from_raw(std::ptr::from_mut::<[*const c_char]>(labels_slice)); + drop(boxed_slice); + } + + WalletFfiError::Success +} diff --git a/lez/wallet-ffi/src/lib.rs b/lez/wallet-ffi/src/lib.rs index 6c86b0c8..361ae672 100644 --- a/lez/wallet-ffi/src/lib.rs +++ b/lez/wallet-ffi/src/lib.rs @@ -46,6 +46,7 @@ pub mod bridge; pub mod error; pub mod generic_transaction; pub mod keys; +pub mod label; pub mod pinata; pub mod program_deployment; pub mod sync; diff --git a/lez/wallet-ffi/src/types.rs b/lez/wallet-ffi/src/types.rs index cbe9fab7..9930334f 100644 --- a/lez/wallet-ffi/src/types.rs +++ b/lez/wallet-ffi/src/types.rs @@ -9,7 +9,7 @@ use std::{ use lee::{Data, ProgramId, SharedSecretKey}; use lee_core::{encryption::MlKem768EncapsulationKey, NullifierPublicKey}; -use wallet::AccountIdentity; +use wallet::{account::AccountIdWithPrivacy, AccountIdentity}; use crate::error::WalletFfiError; @@ -593,6 +593,38 @@ impl From for ProgramId { } } +#[repr(C)] +#[derive(Default)] +pub struct FfiAccountIdWithPrivacy { + pub account_id: FfiBytes32, + pub is_private: bool, +} + +impl From for FfiAccountIdWithPrivacy { + fn from(value: AccountIdWithPrivacy) -> Self { + match value { + AccountIdWithPrivacy::Public(acc) => Self { + account_id: acc.into(), + is_private: false, + }, + AccountIdWithPrivacy::Private(acc) => Self { + account_id: acc.into(), + is_private: true, + }, + } + } +} + +impl From for AccountIdWithPrivacy { + fn from(value: FfiAccountIdWithPrivacy) -> Self { + if value.is_private { + Self::Private(value.account_id.into()) + } else { + Self::Public(value.account_id.into()) + } + } +} + #[cfg(test)] mod tests { use lee::{AccountId, PrivateKey, PublicKey}; diff --git a/lez/wallet-ffi/wallet_ffi.h b/lez/wallet-ffi/wallet_ffi.h index d83c520e..8f15a888 100644 --- a/lez/wallet-ffi/wallet_ffi.h +++ b/lez/wallet-ffi/wallet_ffi.h @@ -299,6 +299,27 @@ typedef struct FfiPublicAccountKey { struct FfiBytes32 public_key; } FfiPublicAccountKey; +typedef struct LabelAvailability { + bool is_available; + enum WalletFfiError error; +} LabelAvailability; + +typedef struct FfiAccountIdWithPrivacy { + struct FfiBytes32 account_id; + bool is_private; +} FfiAccountIdWithPrivacy; + +typedef struct AccountIdResolvedFromLabel { + struct FfiAccountIdWithPrivacy account_id; + enum WalletFfiError error; +} AccountIdResolvedFromLabel; + +typedef struct LabelList { + const char **labels_data; + uintptr_t labels_size; + enum WalletFfiError error; +} LabelList; + typedef struct FfiCreateWalletOutput { struct WalletHandle *wallet; /** @@ -807,6 +828,92 @@ enum WalletFfiError wallet_ffi_resolve_private_account(struct WalletHandle *hand */ void wallet_ffi_free_account_identity(struct FfiAccountIdentity *account_identity); +/** + * Check if label is available. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `label`: Input null terminated C string for a label + * + * # Returns + * - `LabelAvailability` struct + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `label` must be a valid pointer to a null-terminated C string + */ +struct LabelAvailability wallet_ffi_check_label_available(struct WalletHandle *handle, + const char *label); + +/** + * Add new label. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `label`: Input null terminated C string for a label + * - `account_id_with_privacy`: The account ID (32 bytes) and its privacy. + * + * # 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` + * - `label` must be a valid pointer to a null-terminated C string + */ +enum WalletFfiError wallet_ffi_add_label(struct WalletHandle *handle, + const char *label, + struct FfiAccountIdWithPrivacy account_id_with_privacy); + +/** + * Resolve a label. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `label`: Input null terminated C string for a label + * + * # Returns + * - `AccountIdResolvedFromLabel` struct + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + * - `label` must be a valid pointer to a null-terminated C string + */ +struct AccountIdResolvedFromLabel wallet_ffi_resolve_label(struct WalletHandle *handle, + const char *label); + +/** + * Get all labels for account. + * + * # Parameters + * - `handle`: Valid wallet handle + * - `account_id_with_privacy`: The account ID (32 bytes) and its privacy. + * + * # Returns + * - `LabelList` struct + * + * # Safety + * - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open` + */ +struct LabelList wallet_ffi_get_all_labels_for_account(struct WalletHandle *handle, + struct FfiAccountIdWithPrivacy account_id_with_privacy); + +/** + * Free label list. + * + * # Parameters + * - `label_list`: Input list of labels + * + * # Returns + * - `Success` on successful query + * - Error code on failure + * + * # Safety + * - `label_list` must be a valid pointer to `LabelList`, received from + * `wallet_ffi_get_all_labels_for_account` + */ +enum WalletFfiError wallet_ffi_free_label_list(struct LabelList *label_list); + /** * Claim a pinata reward using a public transaction. * diff --git a/lez/wallet/src/account.rs b/lez/wallet/src/account.rs index 64eee575..28070a6e 100644 --- a/lez/wallet/src/account.rs +++ b/lez/wallet/src/account.rs @@ -19,6 +19,11 @@ impl Label { pub fn new(label: impl ToString) -> Self { Self(label.to_string()) } + + #[must_use] + pub fn inner(&self) -> &str { + &self.0 + } } impl FromStr for Label {