feat(wallet_ffi): labels added

This commit is contained in:
Pravdyvy 2026-06-25 18:29:35 +03:00
parent 217daff4a2
commit 860746dac9
5 changed files with 461 additions and 1 deletions

315
lez/wallet-ffi/src/label.rs Normal file
View File

@ -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
}

View File

@ -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;

View File

@ -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<FfiProgramId> for ProgramId {
}
}
#[repr(C)]
#[derive(Default)]
pub struct FfiAccountIdWithPrivacy {
pub account_id: FfiBytes32,
pub is_private: bool,
}
impl From<AccountIdWithPrivacy> 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<FfiAccountIdWithPrivacy> 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};

View File

@ -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.
*

View File

@ -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 {