use std::ffi::c_char;
use super::error::{result_to_code, set_last_error, WebyErrorCode};
use super::types::{cstr_to_str, str_to_cstring};
use crate::wallet::Wallet;
pub struct WebyWallet {
wallet: Wallet,
runtime: tokio::runtime::Runtime,
}
#[no_mangle]
pub unsafe extern "C" fn weby_wallet_open(
path: *const c_char,
out_wallet: *mut *mut WebyWallet,
) -> i32 {
if out_wallet.is_null() {
set_last_error("out_wallet is null");
return WebyErrorCode::InvalidInput as i32;
}
let path_str = match unsafe { cstr_to_str(path) } {
Some(s) => s,
None => {
set_last_error("path is null or invalid UTF-8");
return WebyErrorCode::InvalidInput as i32;
}
};
let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(e) => {
set_last_error(&format!("Failed to create async runtime: {}", e));
return WebyErrorCode::Unknown as i32;
}
};
let result = rt.block_on(Wallet::open(path_str));
let code = result_to_code(&result);
if let Ok(wallet) = result {
let handle = Box::new(WebyWallet { wallet, runtime: rt });
unsafe { *out_wallet = Box::into_raw(handle) };
}
code
}
#[no_mangle]
pub unsafe extern "C" fn weby_wallet_open_with_seed(
path: *const c_char,
seed_ptr: *const u8,
seed_len: usize,
out_wallet: *mut *mut WebyWallet,
) -> i32 {
if out_wallet.is_null() || seed_ptr.is_null() || seed_len != 32 {
set_last_error("Invalid arguments: need non-null pointers and seed_len=32");
return WebyErrorCode::InvalidInput as i32;
}
let path_str = match unsafe { cstr_to_str(path) } {
Some(s) => s,
None => {
set_last_error("path is null or invalid UTF-8");
return WebyErrorCode::InvalidInput as i32;
}
};
let mut seed = [0u8; 32];
unsafe { std::ptr::copy_nonoverlapping(seed_ptr, seed.as_mut_ptr(), 32) };
let rt = match tokio::runtime::Runtime::new() {
Ok(rt) => rt,
Err(e) => {
set_last_error(&format!("Failed to create async runtime: {}", e));
return WebyErrorCode::Unknown as i32;
}
};
let result = rt.block_on(Wallet::open_with_seed(path_str, &seed));
let code = result_to_code(&result);
if let Ok(wallet) = result {
let handle = Box::new(WebyWallet { wallet, runtime: rt });
unsafe { *out_wallet = Box::into_raw(handle) };
}
code
}
#[no_mangle]
pub unsafe extern "C" fn weby_wallet_free(wallet: *mut WebyWallet) {
if !wallet.is_null() {
drop(unsafe { Box::from_raw(wallet) });
}
}
#[no_mangle]
pub unsafe extern "C" fn weby_wallet_balance(
wallet: *const WebyWallet,
out_balance: *mut *mut c_char,
) -> i32 {
if wallet.is_null() || out_balance.is_null() {
set_last_error("wallet or out_balance is null");
return WebyErrorCode::InvalidInput as i32;
}
let handle = unsafe { &*wallet };
let result = handle.runtime.block_on(handle.wallet.balance());
let code = result_to_code(&result);
if let Ok(balance) = result {
unsafe { *out_balance = str_to_cstring(&balance) };
}
code
}
#[no_mangle]
pub unsafe extern "C" fn weby_wallet_insert(
wallet: *const WebyWallet,
webcash_str: *const c_char,
) -> i32 {
if wallet.is_null() {
set_last_error("wallet is null");
return WebyErrorCode::InvalidInput as i32;
}
let wc_str = match unsafe { cstr_to_str(webcash_str) } {
Some(s) => s,
None => {
set_last_error("webcash_str is null or invalid UTF-8");
return WebyErrorCode::InvalidInput as i32;
}
};
let webcash = match crate::webcash::SecretWebcash::parse(wc_str) {
Ok(wc) => wc,
Err(e) => {
set_last_error(&e.to_string());
return WebyErrorCode::InvalidInput as i32;
}
};
let handle = unsafe { &*wallet };
let result = handle.runtime.block_on(handle.wallet.insert(webcash));
result_to_code(&result)
}
#[no_mangle]
pub unsafe extern "C" fn weby_wallet_pay(
wallet: *const WebyWallet,
amount_str: *const c_char,
memo: *const c_char,
out_webcash: *mut *mut c_char,
) -> i32 {
if wallet.is_null() || out_webcash.is_null() {
set_last_error("wallet or out_webcash is null");
return WebyErrorCode::InvalidInput as i32;
}
let amt = match unsafe { cstr_to_str(amount_str) } {
Some(s) => match s.parse::<crate::Amount>() {
Ok(a) => a,
Err(e) => {
set_last_error(&e.to_string());
return WebyErrorCode::InvalidInput as i32;
}
},
None => {
set_last_error("amount_str is null or invalid UTF-8");
return WebyErrorCode::InvalidInput as i32;
}
};
let memo_str = unsafe { cstr_to_str(memo) }.unwrap_or("");
let handle = unsafe { &*wallet };
let result = handle.runtime.block_on(handle.wallet.pay(amt, memo_str));
let code = result_to_code(&result);
if let Ok(msg) = result {
unsafe { *out_webcash = str_to_cstring(&msg) };
}
code
}
#[no_mangle]
pub unsafe extern "C" fn weby_wallet_check(wallet: *const WebyWallet) -> i32 {
if wallet.is_null() {
set_last_error("wallet is null");
return WebyErrorCode::InvalidInput as i32;
}
let handle = unsafe { &*wallet };
let result = handle.runtime.block_on(handle.wallet.check());
result_to_code(&result)
}
#[no_mangle]
pub unsafe extern "C" fn weby_wallet_merge(
wallet: *const WebyWallet,
max_outputs: u32,
out_result: *mut *mut c_char,
) -> i32 {
if wallet.is_null() || out_result.is_null() {
set_last_error("wallet or out_result is null");
return WebyErrorCode::InvalidInput as i32;
}
let handle = unsafe { &*wallet };
let result = handle
.runtime
.block_on(handle.wallet.merge(max_outputs as usize));
let code = result_to_code(&result);
if let Ok(msg) = result {
unsafe { *out_result = str_to_cstring(&msg) };
}
code
}
#[no_mangle]
pub unsafe extern "C" fn weby_wallet_recover(
wallet: *const WebyWallet,
master_secret_hex: *const c_char,
gap_limit: u32,
out_result: *mut *mut c_char,
) -> i32 {
if wallet.is_null() || out_result.is_null() {
set_last_error("wallet or out_result is null");
return WebyErrorCode::InvalidInput as i32;
}
let secret = match unsafe { cstr_to_str(master_secret_hex) } {
Some(s) => s,
None => {
set_last_error("master_secret_hex is null or invalid UTF-8");
return WebyErrorCode::InvalidInput as i32;
}
};
let handle = unsafe { &*wallet };
let result = handle
.runtime
.block_on(handle.wallet.recover(secret, gap_limit as usize));
let code = result_to_code(&result);
if let Ok(r) = result {
unsafe { *out_result = str_to_cstring(&r.to_string()) };
}
code
}
#[no_mangle]
pub unsafe extern "C" fn weby_wallet_stats(
wallet: *const WebyWallet,
out_json: *mut *mut c_char,
) -> i32 {
if wallet.is_null() || out_json.is_null() {
set_last_error("wallet or out_json is null");
return WebyErrorCode::InvalidInput as i32;
}
let handle = unsafe { &*wallet };
let result = handle.runtime.block_on(handle.wallet.stats());
let code = result_to_code(&result);
if let Ok(stats) = result {
let json = format!(
r#"{{"total_webcash":{},"unspent_webcash":{},"spent_webcash":{},"total_balance":"{}"}}"#,
stats.total_webcash, stats.unspent_webcash, stats.spent_webcash, stats.total_balance
);
unsafe { *out_json = str_to_cstring(&json) };
}
code
}
#[no_mangle]
pub unsafe extern "C" fn weby_wallet_export_snapshot(
wallet: *const WebyWallet,
out_json: *mut *mut c_char,
) -> i32 {
if wallet.is_null() || out_json.is_null() {
set_last_error("wallet or out_json is null");
return WebyErrorCode::InvalidInput as i32;
}
let handle = unsafe { &*wallet };
let result = handle.wallet.export_snapshot();
let code = result_to_code(&result);
if let Ok(snapshot) = result {
match serde_json::to_string(&snapshot) {
Ok(json) => unsafe { *out_json = str_to_cstring(&json) },
Err(e) => {
set_last_error(&e.to_string());
return WebyErrorCode::Unknown as i32;
}
}
}
code
}
#[no_mangle]
pub unsafe extern "C" fn weby_wallet_encrypt_seed(
wallet: *const WebyWallet,
password: *const c_char,
) -> i32 {
if wallet.is_null() {
set_last_error("wallet is null");
return WebyErrorCode::InvalidInput as i32;
}
let pw = match unsafe { cstr_to_str(password) } {
Some(s) => s,
None => {
set_last_error("password is null or invalid UTF-8");
return WebyErrorCode::InvalidInput as i32;
}
};
let handle = unsafe { &*wallet };
let result = handle
.runtime
.block_on(handle.wallet.encrypt_database_with_password(pw));
result_to_code(&result)
}
#[no_mangle]
pub extern "C" fn weby_version() -> *const c_char {
static VERSION_CSTR: std::sync::LazyLock<std::ffi::CString> =
std::sync::LazyLock::new(|| {
std::ffi::CString::new(crate::protocol::VERSION).unwrap()
});
VERSION_CSTR.as_ptr()
}
#[no_mangle]
pub unsafe extern "C" fn weby_amount_parse(
amount_str: *const c_char,
out_wats: *mut i64,
) -> i32 {
if out_wats.is_null() {
set_last_error("out_wats is null");
return WebyErrorCode::InvalidInput as i32;
}
let s = match unsafe { cstr_to_str(amount_str) } {
Some(s) => s,
None => {
set_last_error("amount_str is null or invalid UTF-8");
return WebyErrorCode::InvalidInput as i32;
}
};
match s.parse::<crate::Amount>() {
Ok(amount) => {
unsafe { *out_wats = amount.wats };
WebyErrorCode::Ok as i32
}
Err(e) => {
set_last_error(&e.to_string());
WebyErrorCode::InvalidInput as i32
}
}
}
#[no_mangle]
pub unsafe extern "C" fn weby_amount_format(
wats: i64,
out_str: *mut *mut c_char,
) -> i32 {
if out_str.is_null() {
set_last_error("out_str is null");
return WebyErrorCode::InvalidInput as i32;
}
let amount = crate::Amount::from_wats(wats);
unsafe { *out_str = str_to_cstring(&amount.to_string()) };
WebyErrorCode::Ok as i32
}