use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int};
use std::sync::Mutex;
use once_cell::sync::Lazy;
use crate::sdk::identity::{AgentIdentity, AgentIdentityState};
use crate::sdk::storage::EncryptedFileStorage;
pub const FFI_SUCCESS: c_int = 0;
pub const FFI_ERROR_NULL_POINTER: c_int = -1;
pub const FFI_ERROR_INVALID_UTF8: c_int = -2;
pub const FFI_ERROR_NOT_INITIALIZED: c_int = -3;
pub const FFI_ERROR_ALREADY_INITIALIZED: c_int = -4;
pub const FFI_ERROR_STORAGE_FAILED: c_int = -5;
pub const FFI_ERROR_ENROLLMENT_FAILED: c_int = -6;
pub const FFI_ERROR_AUTH_FAILED: c_int = -7;
pub const FFI_ERROR_DELEGATION_INVALID: c_int = -8;
pub const FFI_ERROR_WALLET_FAILED: c_int = -9;
pub const FFI_ERROR_INTERNAL: c_int = -100;
static AGENT_STATE: Lazy<Mutex<Option<AgentState>>> = Lazy::new(|| Mutex::new(None));
struct AgentState {
identity: AgentIdentityState,
storage_path: String,
nwc_uri: Option<String>,
}
#[no_mangle]
pub extern "C" fn agent_initialize() -> c_int {
agent_initialize_with_path(std::ptr::null())
}
#[no_mangle]
pub extern "C" fn agent_initialize_with_path(storage_path: *const c_char) -> c_int {
let mut state = match AGENT_STATE.lock() {
Ok(s) => s,
Err(_) => return FFI_ERROR_INTERNAL,
};
if state.is_some() {
return FFI_ERROR_ALREADY_INITIALIZED;
}
let path = if storage_path.is_null() {
match dirs::home_dir() {
Some(home) => home.join(".signedby"),
None => return FFI_ERROR_STORAGE_FAILED,
}
} else {
let path_str = match unsafe { CStr::from_ptr(storage_path) }.to_str() {
Ok(s) => s,
Err(_) => return FFI_ERROR_INVALID_UTF8,
};
std::path::PathBuf::from(path_str)
};
let storage = match EncryptedFileStorage::new(path.clone()) {
Ok(s) => s,
Err(_) => return FFI_ERROR_STORAGE_FAILED,
};
let identity = AgentIdentity::new(storage);
let identity_state = if identity.is_initialized() {
match identity.load() {
Ok(s) => s,
Err(_) => return FFI_ERROR_STORAGE_FAILED,
}
} else {
match identity.initialize() {
Ok(s) => s,
Err(_) => return FFI_ERROR_STORAGE_FAILED,
}
};
*state = Some(AgentState {
identity: identity_state,
storage_path: path.to_string_lossy().to_string(),
nwc_uri: None,
});
FFI_SUCCESS
}
#[no_mangle]
pub extern "C" fn agent_get_npub() -> *mut c_char {
let state = match AGENT_STATE.lock() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
let agent_state = match state.as_ref() {
Some(s) => s,
None => return std::ptr::null_mut(),
};
match CString::new(agent_state.identity.agent_npub.clone()) {
Ok(s) => s.into_raw(),
Err(_) => std::ptr::null_mut(),
}
}
#[no_mangle]
pub extern "C" fn agent_get_did() -> *mut c_char {
let state = match AGENT_STATE.lock() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
let agent_state = match state.as_ref() {
Some(s) => s,
None => return std::ptr::null_mut(),
};
match CString::new(agent_state.identity.did.clone()) {
Ok(s) => s.into_raw(),
Err(_) => std::ptr::null_mut(),
}
}
#[no_mangle]
pub extern "C" fn agent_get_leaf_commitment() -> *mut c_char {
let state = match AGENT_STATE.lock() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
let agent_state = match state.as_ref() {
Some(s) => s,
None => return std::ptr::null_mut(),
};
match CString::new(agent_state.identity.leaf_commitment.clone()) {
Ok(s) => s.into_raw(),
Err(_) => std::ptr::null_mut(),
}
}
#[no_mangle]
pub extern "C" fn agent_enroll(enterprise_domain: *const c_char) -> c_int {
if enterprise_domain.is_null() {
return FFI_ERROR_NULL_POINTER;
}
let _domain = match unsafe { CStr::from_ptr(enterprise_domain) }.to_str() {
Ok(s) => s,
Err(_) => return FFI_ERROR_INVALID_UTF8,
};
let state = match AGENT_STATE.lock() {
Ok(s) => s,
Err(_) => return FFI_ERROR_INTERNAL,
};
if state.is_none() {
return FFI_ERROR_NOT_INITIALIZED;
}
FFI_SUCCESS
}
#[no_mangle]
pub extern "C" fn agent_authenticate(enterprise_domain: *const c_char) -> *mut c_char {
if enterprise_domain.is_null() {
return std::ptr::null_mut();
}
let _domain = match unsafe { CStr::from_ptr(enterprise_domain) }.to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
let state = match AGENT_STATE.lock() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
if state.is_none() {
return std::ptr::null_mut();
}
std::ptr::null_mut()
}
#[no_mangle]
pub extern "C" fn agent_check_delegation(enterprise_domain: *const c_char) -> c_int {
if enterprise_domain.is_null() {
return FFI_ERROR_NULL_POINTER;
}
let _domain = match unsafe { CStr::from_ptr(enterprise_domain) }.to_str() {
Ok(s) => s,
Err(_) => return FFI_ERROR_INVALID_UTF8,
};
let state = match AGENT_STATE.lock() {
Ok(s) => s,
Err(_) => return FFI_ERROR_INTERNAL,
};
if state.is_none() {
return FFI_ERROR_NOT_INITIALIZED;
}
1 }
#[no_mangle]
pub extern "C" fn agent_setup_wallet(nwc_uri: *const c_char) -> c_int {
if nwc_uri.is_null() {
return FFI_ERROR_NULL_POINTER;
}
let uri = match unsafe { CStr::from_ptr(nwc_uri) }.to_str() {
Ok(s) => s,
Err(_) => return FFI_ERROR_INVALID_UTF8,
};
if !uri.starts_with("nostr+walletconnect://") {
return FFI_ERROR_WALLET_FAILED;
}
let mut state = match AGENT_STATE.lock() {
Ok(s) => s,
Err(_) => return FFI_ERROR_INTERNAL,
};
let agent_state = match state.as_mut() {
Some(s) => s,
None => return FFI_ERROR_NOT_INITIALIZED,
};
agent_state.nwc_uri = Some(uri.to_string());
FFI_SUCCESS
}
#[no_mangle]
pub extern "C" fn agent_get_lightning_address() -> *mut c_char {
std::ptr::null_mut()
}
#[no_mangle]
pub extern "C" fn agent_create_invoice(
amount_sats: u64,
description: *const c_char,
) -> *mut c_char {
if description.is_null() {
return std::ptr::null_mut();
}
let _desc = match unsafe { CStr::from_ptr(description) }.to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
let state = match AGENT_STATE.lock() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
let agent_state = match state.as_ref() {
Some(s) => s,
None => return std::ptr::null_mut(),
};
if agent_state.nwc_uri.is_none() {
return std::ptr::null_mut();
}
let _ = amount_sats;
std::ptr::null_mut()
}
#[no_mangle]
pub extern "C" fn agent_pay_invoice(bolt11: *const c_char) -> *mut c_char {
if bolt11.is_null() {
return std::ptr::null_mut();
}
let _invoice = match unsafe { CStr::from_ptr(bolt11) }.to_str() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
let state = match AGENT_STATE.lock() {
Ok(s) => s,
Err(_) => return std::ptr::null_mut(),
};
let agent_state = match state.as_ref() {
Some(s) => s,
None => return std::ptr::null_mut(),
};
if agent_state.nwc_uri.is_none() {
return std::ptr::null_mut();
}
std::ptr::null_mut()
}
#[no_mangle]
pub extern "C" fn agent_get_balance() -> i64 {
let state = match AGENT_STATE.lock() {
Ok(s) => s,
Err(_) => return -1,
};
let agent_state = match state.as_ref() {
Some(s) => s,
None => return -1,
};
if agent_state.nwc_uri.is_none() {
return -1;
}
-1
}
#[no_mangle]
pub extern "C" fn agent_string_free(ptr: *mut c_char) {
if !ptr.is_null() {
unsafe {
let _ = CString::from_raw(ptr);
}
}
}
#[no_mangle]
pub extern "C" fn agent_sdk_version() -> *mut c_char {
match CString::new(env!("CARGO_PKG_VERSION")) {
Ok(s) => s.into_raw(),
Err(_) => std::ptr::null_mut(),
}
}
#[no_mangle]
pub extern "C" fn agent_is_initialized() -> c_int {
let state = match AGENT_STATE.lock() {
Ok(s) => s,
Err(_) => return 0,
};
if state.is_some() { 1 } else { 0 }
}
#[no_mangle]
pub extern "C" fn agent_shutdown() -> c_int {
let mut state = match AGENT_STATE.lock() {
Ok(s) => s,
Err(_) => return FFI_ERROR_INTERNAL,
};
*state = None;
FFI_SUCCESS
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sdk_version() {
let version = agent_sdk_version();
assert!(!version.is_null());
unsafe {
let _ = CString::from_raw(version);
}
}
#[test]
fn test_not_initialized() {
{
let mut state = AGENT_STATE.lock().unwrap();
*state = None;
}
assert_eq!(agent_is_initialized(), 0);
assert!(agent_get_npub().is_null());
}
}