diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a8c588b0..ed0de65d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,6 +2,7 @@ on: push: branches: - main + - dev paths-ignore: - "**.md" - "!.github/workflows/*.yml" @@ -94,7 +95,7 @@ jobs: uses: Swatinem/rust-cache@v2 with: shared-key: ci-rust-cache - save-if: ${{ github.ref == 'refs/heads/main' }} + save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' }} - name: Lint workspace env: @@ -125,7 +126,7 @@ jobs: uses: Swatinem/rust-cache@v2 with: shared-key: ci-rust-cache - save-if: ${{ github.ref == 'refs/heads/main' }} + save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' }} - name: Install nextest run: cargo install --locked cargo-nextest @@ -156,7 +157,7 @@ jobs: uses: Swatinem/rust-cache@v2 with: shared-key: ci-rust-cache - save-if: ${{ github.ref == 'refs/heads/main' }} + save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' }} - name: Install nextest run: cargo install --locked cargo-nextest @@ -245,7 +246,7 @@ jobs: uses: Swatinem/rust-cache@v2 with: shared-key: ci-rust-cache - save-if: ${{ github.ref == 'refs/heads/main' }} + save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' }} - name: Test valid proof env: @@ -268,7 +269,7 @@ jobs: uses: Swatinem/rust-cache@v2 with: shared-key: ci-rust-cache - save-if: ${{ github.ref == 'refs/heads/main' }} + save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev' }} - name: Install just run: cargo install --locked just diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 284e3798..b15693b8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,12 +57,16 @@ Before merging a PR, consider squashing non-meaningful commits. E.g.: Could be squashed to an empty commit if they belong to the same PR. +## Default branch + +By default all PRs must be directed into the `dev` branch. This helps us to keep releases stable. + ## Branch workflow -When bringing your feature branch up to date, prefer rebasing on top of `main`. +When bringing your feature branch up to date, prefer rebasing on top of `dev`. -- Preferred: `git rebase main` -- Avoid: `git merge main` in feature branches +- Preferred: `git rebase dev` +- Avoid: `git merge dev` in feature branches This keeps commit history cleaner and makes reviews easier. diff --git a/integration_tests/tests/wallet_ffi.rs b/integration_tests/tests/wallet_ffi.rs index 0ef53592..57f1afa4 100644 --- a/integration_tests/tests/wallet_ffi.rs +++ b/integration_tests/tests/wallet_ffi.rs @@ -16,6 +16,7 @@ use std::{ ffi::{CStr, CString, c_char}, io::Write as _, path::Path, + str::FromStr as _, time::Duration, }; @@ -30,9 +31,11 @@ use log::info; use tempfile::tempdir; use wallet::{account::HumanReadableAccount, program_facades::vault::Vault}; use wallet_ffi::{ - FfiAccount, FfiAccountIdentity, FfiAccountList, FfiBytes32, FfiPrivateAccountKeys, - FfiProgramId, FfiPublicAccountKey, FfiTransferResult, FfiU128, WalletHandle, error, + FfiAccount, FfiAccountIdWithPrivacy, FfiAccountIdentity, FfiAccountList, FfiBytes32, + FfiPrivateAccountKeys, FfiProgramId, FfiPublicAccountKey, FfiTransferResult, FfiU128, + WalletHandle, error, generic_transaction::{FfiProgramWithDependencies, FfiTransactionResult}, + label::{AccountIdResolvedFromLabel, LabelAvailability, LabelList}, wallet::FfiCreateWalletOutput, }; @@ -257,6 +260,29 @@ unsafe extern "C" { fn wallet_ffi_free_transaction_result(result: *mut FfiTransactionResult); fn wallet_ffi_free_account_identity(account_identity: *mut FfiAccountIdentity); + + fn wallet_ffi_check_label_available( + handle: *mut WalletHandle, + label: *const c_char, + ) -> LabelAvailability; + + fn wallet_ffi_add_label( + handle: *mut WalletHandle, + label: *const c_char, + account_id_with_privacy: FfiAccountIdWithPrivacy, + ) -> error::WalletFfiError; + + fn wallet_ffi_resolve_label( + handle: *mut WalletHandle, + label: *const c_char, + ) -> AccountIdResolvedFromLabel; + + fn wallet_ffi_get_all_labels_for_account( + handle: *mut WalletHandle, + account_id_with_privacy: FfiAccountIdWithPrivacy, + ) -> LabelList; + + fn wallet_ffi_free_label_list(label_list: *mut LabelList) -> error::WalletFfiError; } fn new_wallet_ffi_with_test_context_config( @@ -1926,3 +1952,125 @@ fn test_wallet_ffi_vault_balance_and_claim_private() -> Result<()> { Ok(()) } + +#[test] +fn test_wallet_ffi_single_label() -> Result<()> { + let ctx = BlockingTestContext::new()?; + let home = tempfile::tempdir()?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + + let mut out_account_id_1 = FfiBytes32::from_bytes([0; 32]); + unsafe { + wallet_ffi_create_account_public(wallet_ffi_handle, &raw mut out_account_id_1).unwrap(); + } + + info!("Waiting for next block creation"); + std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)); + + let lab_1 = CString::from_str("LABEL1").unwrap().into_raw(); + + let lab_1_availability = unsafe { wallet_ffi_check_label_available(wallet_ffi_handle, lab_1) }; + + assert_eq!(lab_1_availability.error, error::WalletFfiError::Success); + assert!(lab_1_availability.is_available); + + let acc_1_id_with_privacy = FfiAccountIdWithPrivacy { + account_id: out_account_id_1, + is_private: false, + }; + + let err = unsafe { wallet_ffi_add_label(wallet_ffi_handle, lab_1, acc_1_id_with_privacy) }; + + assert_eq!(err, error::WalletFfiError::Success); + + let lab_1_availability = unsafe { wallet_ffi_check_label_available(wallet_ffi_handle, lab_1) }; + + assert!(!lab_1_availability.is_available); + + let acc_resolved = unsafe { wallet_ffi_resolve_label(wallet_ffi_handle, lab_1) }; + + assert_eq!(acc_resolved.account_id, acc_1_id_with_privacy); + + unsafe { + wallet_ffi_free_string(lab_1); + wallet_ffi_destroy(wallet_ffi_handle); + } + + Ok(()) +} + +#[test] +fn test_wallet_ffi_more_labels() -> Result<()> { + let ctx = BlockingTestContext::new()?; + let home = tempfile::tempdir()?; + let FfiCreateWalletOutput { + wallet: wallet_ffi_handle, + mnemonic: _, + } = new_wallet_ffi_with_test_context_config(&ctx, home.path())?; + + let mut out_account_id_1 = FfiBytes32::from_bytes([0; 32]); + unsafe { + wallet_ffi_create_account_public(wallet_ffi_handle, &raw mut out_account_id_1).unwrap(); + } + + info!("Waiting for next block creation"); + std::thread::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)); + + let lab_1 = CString::from_str("LABEL1").unwrap().into_raw(); + let lab_2 = CString::from_str("LABEL2").unwrap().into_raw(); + let lab_3 = CString::from_str("LABEL3").unwrap().into_raw(); + + let acc_1_id_with_privacy = FfiAccountIdWithPrivacy { + account_id: out_account_id_1, + is_private: false, + }; + + let err = unsafe { wallet_ffi_add_label(wallet_ffi_handle, lab_1, acc_1_id_with_privacy) }; + + assert_eq!(err, error::WalletFfiError::Success); + + let err = unsafe { wallet_ffi_add_label(wallet_ffi_handle, lab_2, acc_1_id_with_privacy) }; + + assert_eq!(err, error::WalletFfiError::Success); + + let err = unsafe { wallet_ffi_add_label(wallet_ffi_handle, lab_3, acc_1_id_with_privacy) }; + + assert_eq!(err, error::WalletFfiError::Success); + + let mut label_list_for_out_acc = + unsafe { wallet_ffi_get_all_labels_for_account(wallet_ffi_handle, acc_1_id_with_privacy) }; + + assert_eq!(label_list_for_out_acc.error, error::WalletFfiError::Success); + assert_eq!(label_list_for_out_acc.labels_size, 3); + + let lab_ref_1 = unsafe { &*label_list_for_out_acc.labels_data.add(0) }; + let lab_ref_c_str_1 = unsafe { CStr::from_ptr(*lab_ref_1) }; + + assert_eq!(lab_ref_c_str_1.to_str().unwrap(), "LABEL1"); + + let lab_ref_2 = unsafe { &*label_list_for_out_acc.labels_data.add(1) }; + let lab_ref_c_str_2 = unsafe { CStr::from_ptr(*lab_ref_2) }; + + assert_eq!(lab_ref_c_str_2.to_str().unwrap(), "LABEL2"); + + let lab_ref_3 = unsafe { &*label_list_for_out_acc.labels_data.add(2) }; + let lab_ref_c_str_3 = unsafe { CStr::from_ptr(*lab_ref_3) }; + + assert_eq!(lab_ref_c_str_3.to_str().unwrap(), "LABEL3"); + + let err = unsafe { wallet_ffi_free_label_list(&raw mut label_list_for_out_acc) }; + + assert_eq!(err, error::WalletFfiError::Success); + + unsafe { + wallet_ffi_free_string(lab_1); + wallet_ffi_free_string(lab_2); + wallet_ffi_free_string(lab_3); + wallet_ffi_destroy(wallet_ffi_handle); + } + + Ok(()) +} diff --git a/lez/wallet-ffi/src/label.rs b/lez/wallet-ffi/src/label.rs new file mode 100644 index 00000000..1f6f1236 --- /dev/null +++ b/lez/wallet-ffi/src/label.rs @@ -0,0 +1,316 @@ +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 { + pub is_available: bool, + pub 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)] +#[derive(Debug, Clone, Copy)] +pub struct AccountIdResolvedFromLabel { + pub account_id: FfiAccountIdWithPrivacy, + pub 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 { + pub labels_data: *mut *const c_char, + pub labels_size: usize, + pub 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.as_ref()) 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..98590619 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; @@ -24,7 +24,7 @@ pub struct WalletHandle { /// 32-byte array type for `AccountId`, keys, hashes, etc. #[repr(C)] -#[derive(Clone, Copy, Default)] +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)] pub struct FfiBytes32 { pub data: [u8; 32], } @@ -593,6 +593,38 @@ impl From for ProgramId { } } +#[repr(C)] +#[derive(Default, PartialEq, Eq, Debug, Clone, Copy)] +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..8caa7366 100644 --- a/lez/wallet/src/account.rs +++ b/lez/wallet/src/account.rs @@ -21,6 +21,12 @@ impl Label { } } +impl AsRef for Label { + fn as_ref(&self) -> &str { + &self.0 + } +} + impl FromStr for Label { type Err = std::convert::Infallible;