mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-02-14 02:13:09 +00:00
Merge pull request #301 from logos-blockchain/dsq/wallet-ffi
feat(wallet-ffi): Add wallet-ffi crate
This commit is contained in:
commit
e4634b6edb
135
Cargo.lock
generated
135
Cargo.lock
generated
@ -821,6 +821,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"
|
||||
@ -1170,6 +1181,25 @@ dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[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"
|
||||
@ -1266,6 +1296,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"
|
||||
@ -1284,8 +1329,8 @@ checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"clap_lex",
|
||||
"strsim",
|
||||
"clap_lex 0.7.6",
|
||||
"strsim 0.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1294,12 +1339,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"
|
||||
@ -1578,7 +1632,7 @@ dependencies = [
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"strsim 0.11.1",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
@ -1592,7 +1646,7 @@ dependencies = [
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"strsim 0.11.1",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
@ -1818,7 +1872,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",
|
||||
]
|
||||
@ -2433,12 +2487,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"
|
||||
@ -2802,7 +2871,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"clap",
|
||||
"clap 4.5.53",
|
||||
"env_logger",
|
||||
"indexer_service_protocol",
|
||||
"indexer_service_rpc",
|
||||
@ -2928,7 +2997,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",
|
||||
]
|
||||
@ -3139,7 +3208,7 @@ version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2da3f8ab5ce1bb124b6d082e62dffe997578ceaf0aeb9f3174a214589dc00f07"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"heck 0.5.0",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -4338,6 +4407,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 = "overwatch"
|
||||
version = "0.1.0"
|
||||
@ -4578,7 +4653,7 @@ dependencies = [
|
||||
name = "program_deployment"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"clap 4.5.53",
|
||||
"nssa",
|
||||
"nssa_core",
|
||||
"tokio",
|
||||
@ -5388,7 +5463,7 @@ dependencies = [
|
||||
"strum",
|
||||
"tempfile",
|
||||
"thiserror 2.0.17",
|
||||
"toml",
|
||||
"toml 0.8.23",
|
||||
"yaml-rust2",
|
||||
]
|
||||
|
||||
@ -5597,7 +5672,7 @@ dependencies = [
|
||||
"actix",
|
||||
"actix-web",
|
||||
"anyhow",
|
||||
"clap",
|
||||
"clap 4.5.53",
|
||||
"common",
|
||||
"env_logger",
|
||||
"log",
|
||||
@ -5915,6 +5990,12 @@ dependencies = [
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
[[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"
|
||||
@ -5936,7 +6017,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",
|
||||
@ -6081,6 +6162,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 = "1.0.69"
|
||||
@ -6271,6 +6358,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"
|
||||
@ -6614,7 +6710,7 @@ dependencies = [
|
||||
"base64",
|
||||
"borsh",
|
||||
"bytemuck",
|
||||
"clap",
|
||||
"clap 4.5.53",
|
||||
"common",
|
||||
"env_logger",
|
||||
"futures",
|
||||
@ -6635,6 +6731,17 @@ dependencies = [
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wallet-ffi"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cbindgen",
|
||||
"common",
|
||||
"nssa",
|
||||
"tokio",
|
||||
"wallet",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.1"
|
||||
|
||||
@ -6,6 +6,7 @@ members = [
|
||||
"key_protocol",
|
||||
"mempool",
|
||||
"wallet",
|
||||
"wallet-ffi",
|
||||
"common",
|
||||
"nssa",
|
||||
"nssa/core",
|
||||
@ -40,6 +41,7 @@ indexer_service = { path = "indexer_service" }
|
||||
indexer_service_protocol = { path = "indexer_service/protocol" }
|
||||
indexer_service_rpc = { path = "indexer_service/rpc" }
|
||||
wallet = { path = "wallet" }
|
||||
wallet-ffi = { path = "wallet-ffi" }
|
||||
test_program_methods = { path = "test_program_methods" }
|
||||
bedrock_client = { path = "bedrock_client" }
|
||||
indexer_core = { path = "indexer_core" }
|
||||
|
||||
16
wallet-ffi/Cargo.toml
Normal file
16
wallet-ffi/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[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
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = "0.26"
|
||||
13
wallet-ffi/build.rs
Normal file
13
wallet-ffi/build.rs
Normal file
@ -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");
|
||||
}
|
||||
40
wallet-ffi/cbindgen.toml
Normal file
40
wallet-ffi/cbindgen.toml
Normal file
@ -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"
|
||||
395
wallet-ffi/src/account.rs
Normal file
395
wallet-ffi/src/account.rs
Normal file
@ -0,0 +1,395 @@
|
||||
//! Account management functions.
|
||||
|
||||
use std::ptr;
|
||||
|
||||
use nssa::AccountId;
|
||||
|
||||
use crate::{
|
||||
block_on,
|
||||
error::{print_error, WalletFfiError},
|
||||
types::{
|
||||
FfiAccount, FfiAccountList, FfiAccountListEntry, FfiBytes32, FfiProgramId, WalletHandle,
|
||||
},
|
||||
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
|
||||
///
|
||||
/// # 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 unsafe 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() {
|
||||
print_error("Null output pointer for account_id");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
let mut wallet = match wrapper.core.lock() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
print_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
|
||||
///
|
||||
/// # 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 unsafe 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() {
|
||||
print_error("Null output pointer for account_id");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
let mut wallet = match wrapper.core.lock() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
print_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()`.
|
||||
///
|
||||
/// # 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 unsafe 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() {
|
||||
print_error("Null output pointer for account list");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
let wallet = match wrapper.core.lock() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
print_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 unsafe 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`: Output for balance as little-endian [u8; 16]
|
||||
///
|
||||
/// # 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 unsafe extern "C" fn wallet_ffi_get_balance(
|
||||
handle: *mut WalletHandle,
|
||||
account_id: *const FfiBytes32,
|
||||
is_public: bool,
|
||||
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.is_null() {
|
||||
print_error("Null pointer argument");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
let wallet = match wrapper.core.lock() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
print_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)) => {
|
||||
print_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 => {
|
||||
print_error("Private account not found");
|
||||
return WalletFfiError::AccountNotFound;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
unsafe {
|
||||
*out_balance = balance.to_le_bytes();
|
||||
}
|
||||
|
||||
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()`.
|
||||
///
|
||||
/// # 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 unsafe 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() {
|
||||
print_error("Null pointer argument");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
let wallet = match wrapper.core.lock() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
print_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)) => {
|
||||
print_error(format!("Failed to get account: {}", e));
|
||||
return WalletFfiError::NetworkError;
|
||||
}
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
// Convert account data to FFI type
|
||||
let data_vec: Vec<u8> = 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 program_owner = FfiProgramId {
|
||||
data: account.program_owner,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
(*out_account).program_owner = program_owner;
|
||||
(*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;
|
||||
}
|
||||
|
||||
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 unsafe 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]));
|
||||
}
|
||||
}
|
||||
}
|
||||
46
wallet-ffi/src/error.rs
Normal file
46
wallet-ffi/src/error.rs
Normal file
@ -0,0 +1,46 @@
|
||||
//! Error handling for the FFI layer.
|
||||
//!
|
||||
//! Uses numeric error codes with error messages printed to stderr.
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// Log an error message to stderr.
|
||||
pub fn print_error(msg: impl Into<String>) {
|
||||
eprintln!("[wallet-ffi] {}", msg.into());
|
||||
}
|
||||
253
wallet-ffi/src/keys.rs
Normal file
253
wallet-ffi/src/keys.rs
Normal file
@ -0,0 +1,253 @@
|
||||
//! Key retrieval functions.
|
||||
|
||||
use std::ptr;
|
||||
|
||||
use nssa::{AccountId, PublicKey};
|
||||
|
||||
use crate::{
|
||||
error::{print_error, WalletFfiError},
|
||||
types::{FfiBytes32, FfiPrivateAccountKeys, FfiPublicAccountKey, WalletHandle},
|
||||
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
|
||||
///
|
||||
/// # 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 unsafe 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() {
|
||||
print_error("Null pointer argument");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
let wallet = match wrapper.core.lock() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
print_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 => {
|
||||
print_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()`.
|
||||
///
|
||||
/// # 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 unsafe 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() {
|
||||
print_error("Null pointer argument");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
let wallet = match wrapper.core.lock() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
print_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 => {
|
||||
print_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 unsafe 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()`.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `account_id` must be a valid pointer to a `FfiBytes32` struct
|
||||
#[no_mangle]
|
||||
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() {
|
||||
print_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) => {
|
||||
print_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
|
||||
///
|
||||
/// # 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 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() {
|
||||
print_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) => {
|
||||
print_error(format!("Invalid UTF-8: {}", e));
|
||||
return WalletFfiError::InvalidUtf8;
|
||||
}
|
||||
};
|
||||
|
||||
let account_id: AccountId = match str_slice.parse() {
|
||||
Ok(id) => id,
|
||||
Err(e) => {
|
||||
print_error(format!("Invalid Base58 account ID: {}", e));
|
||||
return WalletFfiError::InvalidAccountId;
|
||||
}
|
||||
};
|
||||
|
||||
unsafe {
|
||||
(*out_account_id).data = *account_id.value();
|
||||
}
|
||||
|
||||
WalletFfiError::Success
|
||||
}
|
||||
70
wallet-ffi/src/lib.rs
Normal file
70
wallet-ffi/src/lib.rs
Normal file
@ -0,0 +1,70 @@
|
||||
//! 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;
|
||||
|
||||
// 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, WalletFfiError> {
|
||||
Handle::try_current().map_err(|_| WalletFfiError::RuntimeError)
|
||||
}
|
||||
|
||||
/// Run an async future on the global runtime, blocking until completion.
|
||||
pub(crate) fn block_on<F: std::future::Future>(future: F) -> Result<F::Output, WalletFfiError> {
|
||||
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 = tokio::runtime::Builder::new_multi_thread()
|
||||
.enable_all()
|
||||
.build();
|
||||
|
||||
match result {
|
||||
Ok(_) => WalletFfiError::Success,
|
||||
Err(e) => {
|
||||
print_error(format!("Failed to initialize runtime: {}", e));
|
||||
WalletFfiError::RuntimeError
|
||||
}
|
||||
}
|
||||
}
|
||||
151
wallet-ffi/src/sync.rs
Normal file
151
wallet-ffi/src/sync.rs
Normal file
@ -0,0 +1,151 @@
|
||||
//! Block synchronization functions.
|
||||
|
||||
use crate::{
|
||||
block_on,
|
||||
error::{print_error, WalletFfiError},
|
||||
types::WalletHandle,
|
||||
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.
|
||||
///
|
||||
/// # 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_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) => {
|
||||
print_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)) => {
|
||||
print_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
|
||||
///
|
||||
/// # 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 unsafe 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() {
|
||||
print_error("Null output pointer");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
let wallet = match wrapper.core.lock() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
print_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
|
||||
///
|
||||
/// # 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 unsafe 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() {
|
||||
print_error("Null output pointer");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
let wallet = match wrapper.core.lock() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
print_error(format!("Failed to lock wallet: {}", e));
|
||||
return WalletFfiError::InternalError;
|
||||
}
|
||||
};
|
||||
|
||||
match block_on(wallet.sequencer_client.get_last_block()) {
|
||||
Ok(Ok(response)) => {
|
||||
unsafe {
|
||||
*out_block_height = response.last_block;
|
||||
}
|
||||
WalletFfiError::Success
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
print_error(format!("Failed to get block height: {:?}", e));
|
||||
WalletFfiError::NetworkError
|
||||
}
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
199
wallet-ffi/src/transfer.rs
Normal file
199
wallet-ffi/src/transfer.rs
Normal file
@ -0,0 +1,199 @@
|
||||
//! Token transfer functions.
|
||||
|
||||
use std::{ffi::CString, ptr};
|
||||
|
||||
use common::error::ExecutionFailureKind;
|
||||
use nssa::AccountId;
|
||||
use wallet::program_facades::native_token_transfer::NativeTokenTransfer;
|
||||
|
||||
use crate::{
|
||||
block_on,
|
||||
error::{print_error, WalletFfiError},
|
||||
types::{FfiBytes32, FfiTransferResult, WalletHandle},
|
||||
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`: Amount to transfer as little-endian [u8; 16]
|
||||
/// - `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()`.
|
||||
///
|
||||
/// # 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 unsafe extern "C" fn wallet_ffi_transfer_public(
|
||||
handle: *mut WalletHandle,
|
||||
from: *const FfiBytes32,
|
||||
to: *const FfiBytes32,
|
||||
amount: *const [u8; 16],
|
||||
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() || amount.is_null() || out_result.is_null() {
|
||||
print_error("Null pointer argument");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
let wallet = match wrapper.core.lock() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
print_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 = u128::from_le_bytes(unsafe { *amount });
|
||||
|
||||
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)) => {
|
||||
print_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()`.
|
||||
///
|
||||
/// # 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 unsafe 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() {
|
||||
print_error("Null pointer argument");
|
||||
return WalletFfiError::NullPointer;
|
||||
}
|
||||
|
||||
let wallet = match wrapper.core.lock() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
print_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)) => {
|
||||
print_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 unsafe 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
151
wallet-ffi/src/types.rs
Normal file
151
wallet-ffi/src/types.rs
Normal file
@ -0,0 +1,151 @@
|
||||
//! 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 represented as little-endian
|
||||
/// byte arrays since C doesn't have native u128 support.
|
||||
#[repr(C)]
|
||||
pub struct FfiAccount {
|
||||
pub program_owner: FfiProgramId,
|
||||
/// 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,
|
||||
/// Nonce as little-endian [u8; 16]
|
||||
pub nonce: [u8; 16],
|
||||
}
|
||||
|
||||
impl Default for FfiAccount {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
program_owner: FfiProgramId::default(),
|
||||
balance: [0u8; 16],
|
||||
data: std::ptr::null(),
|
||||
data_len: 0,
|
||||
nonce: [0u8; 16],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<FfiBytes32> for nssa::AccountId {
|
||||
fn from(bytes: FfiBytes32) -> Self {
|
||||
nssa::AccountId::new(bytes.data)
|
||||
}
|
||||
}
|
||||
279
wallet-ffi/src/wallet.rs
Normal file
279
wallet-ffi/src/wallet.rs
Normal file
@ -0,0 +1,279 @@
|
||||
//! Wallet lifecycle management functions.
|
||||
|
||||
use std::{
|
||||
ffi::{c_char, CStr},
|
||||
path::PathBuf,
|
||||
ptr,
|
||||
sync::Mutex,
|
||||
};
|
||||
|
||||
use wallet::WalletCore;
|
||||
|
||||
use crate::{
|
||||
block_on,
|
||||
error::{print_error, WalletFfiError},
|
||||
types::WalletHandle,
|
||||
};
|
||||
|
||||
/// Internal wrapper around WalletCore with mutex for thread safety.
|
||||
pub(crate) struct WalletWrapper {
|
||||
pub core: Mutex<WalletCore>,
|
||||
}
|
||||
|
||||
/// Helper to get the wallet wrapper from an opaque handle.
|
||||
pub(crate) fn get_wallet(
|
||||
handle: *mut WalletHandle,
|
||||
) -> Result<&'static WalletWrapper, WalletFfiError> {
|
||||
if handle.is_null() {
|
||||
print_error("Null wallet handle");
|
||||
return Err(WalletFfiError::NullPointer);
|
||||
}
|
||||
Ok(unsafe { &*(handle as *mut WalletWrapper) })
|
||||
}
|
||||
|
||||
/// Helper to get a mutable reference to the wallet wrapper.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn get_wallet_mut(
|
||||
handle: *mut WalletHandle,
|
||||
) -> Result<&'static mut WalletWrapper, WalletFfiError> {
|
||||
if handle.is_null() {
|
||||
print_error("Null wallet handle");
|
||||
return Err(WalletFfiError::NullPointer);
|
||||
}
|
||||
Ok(unsafe { &mut *(handle as *mut WalletWrapper) })
|
||||
}
|
||||
|
||||
/// Helper to convert a C string to a Rust PathBuf.
|
||||
fn c_str_to_path(ptr: *const c_char, name: &str) -> Result<PathBuf, WalletFfiError> {
|
||||
if ptr.is_null() {
|
||||
print_error(format!("Null pointer for {}", name));
|
||||
return Err(WalletFfiError::NullPointer);
|
||||
}
|
||||
|
||||
let c_str = unsafe { CStr::from_ptr(ptr) };
|
||||
match c_str.to_str() {
|
||||
Ok(s) => Ok(PathBuf::from(s)),
|
||||
Err(e) => {
|
||||
print_error(format!("Invalid UTF-8 in {}: {}", name, e));
|
||||
Err(WalletFfiError::InvalidUtf8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to convert a C string to a Rust String.
|
||||
fn c_str_to_string(ptr: *const c_char, name: &str) -> Result<String, WalletFfiError> {
|
||||
if ptr.is_null() {
|
||||
print_error(format!("Null pointer for {}", name));
|
||||
return Err(WalletFfiError::NullPointer);
|
||||
}
|
||||
|
||||
let c_str = unsafe { CStr::from_ptr(ptr) };
|
||||
match c_str.to_str() {
|
||||
Ok(s) => Ok(s.to_string()),
|
||||
Err(e) => {
|
||||
print_error(format!("Invalid UTF-8 in {}: {}", name, e));
|
||||
Err(WalletFfiError::InvalidUtf8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new wallet with fresh storage.
|
||||
///
|
||||
/// This initializes a new wallet with a new seed derived from the password.
|
||||
/// Use this for first-time wallet creation.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `config_path`: Path to the wallet configuration file (JSON)
|
||||
/// - `storage_path`: Path where wallet data will be stored
|
||||
/// - `password`: Password for encrypting the wallet seed
|
||||
///
|
||||
/// # Returns
|
||||
/// - Opaque wallet handle on success
|
||||
/// - Null pointer on error (call `wallet_ffi_get_last_error()` for details)
|
||||
///
|
||||
/// # Safety
|
||||
/// All string parameters must be valid null-terminated UTF-8 strings.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wallet_ffi_create_new(
|
||||
config_path: *const c_char,
|
||||
storage_path: *const c_char,
|
||||
password: *const c_char,
|
||||
) -> *mut WalletHandle {
|
||||
let config_path = match c_str_to_path(config_path, "config_path") {
|
||||
Ok(p) => p,
|
||||
Err(_) => return ptr::null_mut(),
|
||||
};
|
||||
|
||||
let storage_path = match c_str_to_path(storage_path, "storage_path") {
|
||||
Ok(p) => p,
|
||||
Err(_) => return ptr::null_mut(),
|
||||
};
|
||||
|
||||
let password = match c_str_to_string(password, "password") {
|
||||
Ok(s) => s,
|
||||
Err(_) => return ptr::null_mut(),
|
||||
};
|
||||
|
||||
match WalletCore::new_init_storage(config_path, storage_path, None, password) {
|
||||
Ok(core) => {
|
||||
let wrapper = Box::new(WalletWrapper {
|
||||
core: Mutex::new(core),
|
||||
});
|
||||
Box::into_raw(wrapper) as *mut WalletHandle
|
||||
}
|
||||
Err(e) => {
|
||||
print_error(format!("Failed to create wallet: {}", e));
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Open an existing wallet from storage.
|
||||
///
|
||||
/// This loads a wallet that was previously created with `wallet_ffi_create_new()`.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `config_path`: Path to the wallet configuration file (JSON)
|
||||
/// - `storage_path`: Path where wallet data is stored
|
||||
///
|
||||
/// # Returns
|
||||
/// - Opaque wallet handle on success
|
||||
/// - Null pointer on error (call `wallet_ffi_get_last_error()` for details)
|
||||
///
|
||||
/// # Safety
|
||||
/// All string parameters must be valid null-terminated UTF-8 strings.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wallet_ffi_open(
|
||||
config_path: *const c_char,
|
||||
storage_path: *const c_char,
|
||||
) -> *mut WalletHandle {
|
||||
let config_path = match c_str_to_path(config_path, "config_path") {
|
||||
Ok(p) => p,
|
||||
Err(_) => return ptr::null_mut(),
|
||||
};
|
||||
|
||||
let storage_path = match c_str_to_path(storage_path, "storage_path") {
|
||||
Ok(p) => p,
|
||||
Err(_) => return ptr::null_mut(),
|
||||
};
|
||||
|
||||
match WalletCore::new_update_chain(config_path, storage_path, None) {
|
||||
Ok(core) => {
|
||||
let wrapper = Box::new(WalletWrapper {
|
||||
core: Mutex::new(core),
|
||||
});
|
||||
Box::into_raw(wrapper) as *mut WalletHandle
|
||||
}
|
||||
Err(e) => {
|
||||
print_error(format!("Failed to open wallet: {}", e));
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Destroy a wallet handle and free its resources.
|
||||
///
|
||||
/// After calling this function, the handle is invalid and must not be used.
|
||||
///
|
||||
/// # Safety
|
||||
/// - The handle must be either null or a valid handle from `wallet_ffi_create_new()` or
|
||||
/// `wallet_ffi_open()`.
|
||||
/// - The handle must not be used after this call.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wallet_ffi_destroy(handle: *mut WalletHandle) {
|
||||
if !handle.is_null() {
|
||||
unsafe {
|
||||
drop(Box::from_raw(handle as *mut WalletWrapper));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Save wallet state to persistent storage.
|
||||
///
|
||||
/// This should be called periodically or after important operations to ensure
|
||||
/// wallet data is persisted to disk.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `handle`: Valid wallet handle
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Success` on successful save
|
||||
/// - Error code on failure
|
||||
///
|
||||
/// # Safety
|
||||
/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wallet_ffi_save(handle: *mut WalletHandle) -> WalletFfiError {
|
||||
let wrapper = match get_wallet(handle) {
|
||||
Ok(w) => w,
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
let wallet = match wrapper.core.lock() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
print_error(format!("Failed to lock wallet: {}", e));
|
||||
return WalletFfiError::InternalError;
|
||||
}
|
||||
};
|
||||
|
||||
match block_on(wallet.store_persistent_data()) {
|
||||
Ok(Ok(())) => WalletFfiError::Success,
|
||||
Ok(Err(e)) => {
|
||||
print_error(format!("Failed to save wallet: {}", e));
|
||||
WalletFfiError::StorageError
|
||||
}
|
||||
Err(e) => e,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the sequencer address from the wallet configuration.
|
||||
///
|
||||
/// # Parameters
|
||||
/// - `handle`: Valid wallet handle
|
||||
///
|
||||
/// # Returns
|
||||
/// - Pointer to null-terminated string on success (caller must free with
|
||||
/// `wallet_ffi_free_string()`)
|
||||
/// - Null pointer on error
|
||||
///
|
||||
/// # Safety
|
||||
/// - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wallet_ffi_get_sequencer_addr(handle: *mut WalletHandle) -> *mut c_char {
|
||||
let wrapper = match get_wallet(handle) {
|
||||
Ok(w) => w,
|
||||
Err(_) => return ptr::null_mut(),
|
||||
};
|
||||
|
||||
let wallet = match wrapper.core.lock() {
|
||||
Ok(w) => w,
|
||||
Err(e) => {
|
||||
print_error(format!("Failed to lock wallet: {}", e));
|
||||
return ptr::null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
let addr = wallet.config().sequencer_addr.clone();
|
||||
|
||||
match std::ffi::CString::new(addr) {
|
||||
Ok(s) => s.into_raw(),
|
||||
Err(e) => {
|
||||
print_error(format!("Invalid sequencer address: {}", e));
|
||||
ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Free a string returned by wallet FFI functions.
|
||||
///
|
||||
/// # Safety
|
||||
/// The pointer must be either null or a valid string returned by an FFI function.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn wallet_ffi_free_string(ptr: *mut c_char) {
|
||||
if !ptr.is_null() {
|
||||
unsafe {
|
||||
drop(std::ffi::CString::from_raw(ptr));
|
||||
}
|
||||
}
|
||||
}
|
||||
676
wallet-ffi/wallet_ffi.h
Normal file
676
wallet-ffi/wallet_ffi.h
Normal file
@ -0,0 +1,676 @@
|
||||
/**
|
||||
* 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 <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/**
|
||||
* 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 represented as little-endian
|
||||
* byte arrays since C doesn't have native u128 support.
|
||||
*/
|
||||
typedef struct FfiAccount {
|
||||
struct FfiProgramId program_owner;
|
||||
/**
|
||||
* Balance as little-endian [u8; 16]
|
||||
*/
|
||||
uint8_t balance[16];
|
||||
/**
|
||||
* Pointer to account data bytes
|
||||
*/
|
||||
const uint8_t *data;
|
||||
/**
|
||||
* Length of account data
|
||||
*/
|
||||
uintptr_t data_len;
|
||||
/**
|
||||
* Nonce as little-endian [u8; 16]
|
||||
*/
|
||||
uint8_t nonce[16];
|
||||
} 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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* # 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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* # 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);
|
||||
|
||||
/**
|
||||
* 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()`.
|
||||
*
|
||||
* # 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);
|
||||
|
||||
/**
|
||||
* 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`: Output for balance as little-endian [u8; 16]
|
||||
*
|
||||
* # 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,
|
||||
bool is_public,
|
||||
uint8_t (*out_balance)[16]);
|
||||
|
||||
/**
|
||||
* 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()`.
|
||||
*
|
||||
* # 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,
|
||||
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 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
|
||||
*
|
||||
* # 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,
|
||||
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()`.
|
||||
*
|
||||
* # 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,
|
||||
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()`.
|
||||
*
|
||||
* # Safety
|
||||
* - `account_id` must be a valid pointer to a `FfiBytes32` struct
|
||||
*/
|
||||
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
|
||||
*
|
||||
* # 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);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* # 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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* # 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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* # 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);
|
||||
|
||||
/**
|
||||
* 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`: Amount to transfer as little-endian [u8; 16]
|
||||
* - `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()`.
|
||||
*
|
||||
* # 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,
|
||||
const struct FfiBytes32 *to,
|
||||
const uint8_t (*amount)[16],
|
||||
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()`.
|
||||
*
|
||||
* # 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,
|
||||
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
|
||||
*
|
||||
* # 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);
|
||||
|
||||
/**
|
||||
* Get the sequencer address from the wallet configuration.
|
||||
*
|
||||
* # Parameters
|
||||
* - `handle`: Valid wallet handle
|
||||
*
|
||||
* # Returns
|
||||
* - Pointer to null-terminated string on success (caller must free with
|
||||
* `wallet_ffi_free_string()`)
|
||||
* - Null pointer on error
|
||||
*
|
||||
* # Safety
|
||||
* - `handle` must be a valid wallet handle from `wallet_ffi_create_new` or `wallet_ffi_open`
|
||||
*/
|
||||
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 */
|
||||
Loading…
x
Reference in New Issue
Block a user