From e1a076e93bbb115bdd5cc0af21944a4e3a7ddd68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20M=C3=B6ller?= Date: Thu, 5 Oct 2023 19:50:06 +0200 Subject: [PATCH] Add native library --- .gitignore | 25 +++++ Cargo.toml | 39 +++++++ bindgen.sh | 5 + headers/iota_sdk.h | 42 +++++++ headers/iota_sdk.hpp | 47 ++++++++ src/client.rs | 101 +++++++++++++++++ src/error.rs | 94 ++++++++++++++++ src/lib.rs | 100 +++++++++++++++++ src/secret_manager.rs | 107 ++++++++++++++++++ src/wallet.rs | 249 ++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 809 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100755 bindgen.sh create mode 100644 headers/iota_sdk.h create mode 100644 headers/iota_sdk.hpp create mode 100644 src/client.rs create mode 100644 src/error.rs create mode 100644 src/lib.rs create mode 100644 src/secret_manager.rs create mode 100644 src/wallet.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d8088f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# Created by https://www.toptal.com/developers/gitignore/api/rust,rust-analyzer +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,rust-analyzer + +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +### rust-analyzer ### +# Can be generated by other build systems other than cargo (ex: bazelbuild/rust_rules) +rust-project.json + + +# End of https://www.toptal.com/developers/gitignore/api/rust,rust-analyzer diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3235e03 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "iota-sdk-native" +version = "0.1.0" +authors = [ "IOTA Stiftung" ] +edition = "2021" +description = "Native wrapper for the IOTA SDK library" +documentation = "https://wiki.iota.org/iota-sdk/welcome" +homepage = "https://www.iota.org/" +repository = "https://github.com/iotaledger/iota-sdk" +license = "Apache-2.0" +keywords = [ "iota", "client", "wallet", "transaction", "native" ] +categories = [ "cryptography::cryptocurrencies" ] +publish = false + +[lib] +name = "iota_sdk_native" +crate-type = [ "cdylib" ] +doc = false + +[features] +default = ["std"] +std = ["zeroize/std"] + +[dependencies] +iota-sdk-bindings-core = { git = "https://github.com/iotaledger/iota-sdk.git", branch = "develop", default-features = false, features = [ + "events", + "rocksdb", + "ledger_nano", + "storage", + "stronghold", + "private_key_secret_manager" +] } + +futures = { version = "0.3.28", default-features = false } +once_cell = { version = "1.18.0", default-features = false } +serde_json = { version = "1.0.107", default-features = false } +tokio = { version = "1.32.0", default-features = false } +log = { version = "0.4.20", default-features = false } +zeroize = { version = "1.6.0", default-features = false } \ No newline at end of file diff --git a/bindgen.sh b/bindgen.sh new file mode 100755 index 0000000..2a20b6a --- /dev/null +++ b/bindgen.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +cbindgen --crate iota-sdk-native --output headers/iota_sdk.h --lang c +cbindgen --crate iota-sdk-native --output headers/iota_sdk.hpp --lang c++ \ No newline at end of file diff --git a/headers/iota_sdk.h b/headers/iota_sdk.h new file mode 100644 index 0000000..36c4604 --- /dev/null +++ b/headers/iota_sdk.h @@ -0,0 +1,42 @@ +#include +#include +#include +#include + +typedef struct Client Client; + +typedef struct SecretManager SecretManager; + +typedef struct Wallet Wallet; + +bool destroy_string(char *ptr); + +bool init_logger(const char *config_ptr); + +const char *call_utils_method(const char *config_ptr); + +const struct Client *create_client(const char *options_ptr); + +bool destroy_client(struct Client *client_ptr); + +const char *call_client_method(struct Client *client_ptr, char *method_ptr); + +const char *binding_get_last_error(void); + +const struct SecretManager *create_secret_manager(const char *options_ptr); + +bool destroy_secret_manager(struct SecretManager *secret_manager_ptr); + +const char *call_secret_manager_method(struct SecretManager *secret_manager, const char *method); + +bool destroy_wallet(struct Wallet *wallet_ptr); + +const struct Wallet *create_wallet(const char *options_ptr); + +const char *call_wallet_method(struct Wallet *wallet_ptr, const char *method_ptr); + +bool listen_wallet(struct Wallet *wallet_ptr, const char *events, void (*handler)(const char*)); + +const struct Client *get_client_from_wallet(struct Wallet *wallet_ptr); + +const struct SecretManager *get_secret_manager_from_wallet(struct Wallet *wallet_ptr); diff --git a/headers/iota_sdk.hpp b/headers/iota_sdk.hpp new file mode 100644 index 0000000..2f14a5f --- /dev/null +++ b/headers/iota_sdk.hpp @@ -0,0 +1,47 @@ +#include +#include +#include +#include +#include + +struct Client; + +struct SecretManager; + +struct Wallet; + +extern "C" { + +bool destroy_string(char *ptr); + +bool init_logger(const char *config_ptr); + +const char *call_utils_method(const char *config_ptr); + +const Client *create_client(const char *options_ptr); + +bool destroy_client(Client *client_ptr); + +const char *call_client_method(Client *client_ptr, char *method_ptr); + +const char *binding_get_last_error(); + +const SecretManager *create_secret_manager(const char *options_ptr); + +bool destroy_secret_manager(SecretManager *secret_manager_ptr); + +const char *call_secret_manager_method(SecretManager *secret_manager, const char *method); + +bool destroy_wallet(Wallet *wallet_ptr); + +const Wallet *create_wallet(const char *options_ptr); + +const char *call_wallet_method(Wallet *wallet_ptr, const char *method_ptr); + +bool listen_wallet(Wallet *wallet_ptr, const char *events, void (*handler)(const char*)); + +const Client *get_client_from_wallet(Wallet *wallet_ptr); + +const SecretManager *get_secret_manager_from_wallet(Wallet *wallet_ptr); + +} // extern "C" diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..e2c7e0d --- /dev/null +++ b/src/client.rs @@ -0,0 +1,101 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + ffi::{c_char, CStr, CString}, + ptr::null, +}; + +use iota_sdk_bindings_core::{ + call_client_method as rust_call_client_method, + iota_sdk::client::{Client as RustClient, ClientBuilder}, + ClientMethod, +}; + +use crate::error::{set_last_error, Error, Result}; + +pub struct Client { + pub client: RustClient, +} + +unsafe fn internal_create_client(options_ptr: *const c_char) -> Result<*const Client> { + let options_string = CStr::from_ptr(options_ptr).to_str().unwrap(); + let runtime = tokio::runtime::Runtime::new()?; + + let client = runtime.block_on(async move { + if options_string.is_empty() { + return ClientBuilder::new().finish().await; + } + + ClientBuilder::new().from_json(options_string)?.finish().await + })?; + + let client_wrap = Client { client }; + let client_ptr = Box::into_raw(Box::new(client_wrap)); + + Ok(client_ptr) +} + +#[no_mangle] +pub unsafe extern "C" fn create_client(options_ptr: *const c_char) -> *const Client { + match internal_create_client(options_ptr) { + Ok(v) => v, + Err(e) => { + set_last_error(e); + null() + } + } +} + +unsafe fn internal_destroy_client(client_ptr: *mut Client) -> Result<()> { + log::debug!("[Rust] Client destroy called"); + + if client_ptr.is_null() { + log::error!("[Rust] Client pointer was null!"); + return Err(Error::from("pointer is null")); + } + + let _ = Box::from_raw(client_ptr); + + log::debug!("[Rust] Destroyed client"); + Ok(()) +} + +#[no_mangle] +pub unsafe extern "C" fn destroy_client(client_ptr: *mut Client) -> bool { + match internal_destroy_client(client_ptr) { + Ok(_) => true, + Err(e) => { + set_last_error(e); + false + } + } +} + +unsafe fn internal_call_client_method(client_ptr: *mut Client, method_ptr: *mut c_char) -> Result<*const c_char> { + let method_str = CStr::from_ptr(method_ptr).to_str().unwrap(); + + let client = { + assert!(!client_ptr.is_null()); + &mut *client_ptr + }; + + let method = serde_json::from_str::(method_str)?; + let response = crate::block_on(async { rust_call_client_method(&client.client, method).await }); + + let response_string = serde_json::to_string(&response)?; + let s = CString::new(response_string).unwrap(); + + Ok(s.into_raw()) +} + +#[no_mangle] +pub unsafe extern "C" fn call_client_method(client_ptr: *mut Client, method_ptr: *mut c_char) -> *const c_char { + match internal_call_client_method(client_ptr, method_ptr) { + Ok(v) => v, + Err(e) => { + set_last_error(e); + null() + } + } +} diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 0000000..809b99b --- /dev/null +++ b/src/error.rs @@ -0,0 +1,94 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use core::convert::{From, Infallible}; +use std::{ + cell::RefCell, + ffi::{c_char, CString}, + ptr, +}; + +pub(crate) type Result = std::result::Result; + +#[derive(Debug)] +pub struct Error { + pub error: String, +} + +impl From for Error { + fn from(err: serde_json::Error) -> Self { + Error { error: err.to_string() } + } +} + +impl From for Error { + fn from(err: std::io::Error) -> Self { + Error { error: err.to_string() } + } +} + +impl From for Error { + fn from(err: iota_sdk_bindings_core::iota_sdk::types::block::Error) -> Self { + Error { error: err.to_string() } + } +} + +impl From for Error { + fn from(err: iota_sdk_bindings_core::Error) -> Self { + Error { error: err.to_string() } + } +} + +impl From for Error { + fn from(err: iota_sdk_bindings_core::iota_sdk::client::Error) -> Self { + Error { error: err.to_string() } + } +} + +impl From for Error { + fn from(err: iota_sdk_bindings_core::iota_sdk::wallet::Error) -> Self { + Error { error: err.to_string() } + } +} + +impl From for Error { + fn from(err: Infallible) -> Self { + Error { error: err.to_string() } + } +} + +impl From<&str> for Error { + fn from(err: &str) -> Self { + Self { error: err.to_string() } + } +} + +impl From for Error { + fn from(err: String) -> Self { + Self { error: err } + } +} + +thread_local! { + #[allow(clippy::box_collection)] + static LAST_ERROR: RefCell>> = RefCell::new(None); +} + +pub fn set_last_error(err: Error) { + LAST_ERROR.with(|prev| { + *prev.borrow_mut() = Some(Box::new(err.error)); + }); +} + +#[no_mangle] +pub unsafe extern "C" fn binding_get_last_error() -> *const c_char { + let last_error = LAST_ERROR.with(|prev| prev.borrow_mut().take()); + + let last_error = match last_error { + Some(err) => err, + None => return ptr::null_mut(), + }; + + let s = CString::new(last_error.to_string()).unwrap(); + s.into_raw() +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..a3d9ec0 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,100 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +mod client; +mod error; +mod secret_manager; +mod wallet; + +use std::{ + ffi::{c_char, CStr, CString}, + ptr::null, + sync::Mutex, +}; + +use iota_sdk_bindings_core::{ + call_utils_method as rust_call_utils_method, init_logger as rust_init_logger, UtilsMethod, +}; +use once_cell::sync::OnceCell; +use tokio::runtime::Runtime; +use zeroize::Zeroize; + +use self::{ + error::{Error, Result}, + secret_manager::*, +}; +use crate::error::set_last_error; + +/// Use one runtime. +pub(crate) fn block_on(cb: C) -> C::Output { + static INSTANCE: OnceCell> = OnceCell::new(); + let runtime = INSTANCE.get_or_init(|| Mutex::new(Runtime::new().unwrap())); + runtime.lock().unwrap().block_on(cb) +} + +unsafe fn internal_destroy_string(ptr: *mut c_char) -> Result<()> { + if ptr.is_null() { + log::error!("[Rust] String pointer was null!"); + return Err(Error::from("pointer is null")); + } + + let mut str = CString::from_raw(ptr); + str.zeroize(); + + Ok(()) +} + +#[allow(clippy::missing_safety_doc)] +#[no_mangle] +pub unsafe extern "C" fn destroy_string(ptr: *mut c_char) -> bool { + match internal_destroy_string(ptr) { + Ok(_) => true, + Err(e) => { + set_last_error(e); + false + } + } +} + +/// Init the Rust logger. +unsafe fn internal_init_logger(config_ptr: *const c_char) -> Result<()> { + let method_str = CStr::from_ptr(config_ptr).to_str().unwrap(); + rust_init_logger(method_str.to_string()).map_err(|err| Error::from(format!("{:?}", err)))?; + Ok(()) +} + +#[allow(clippy::missing_safety_doc)] +#[no_mangle] +pub unsafe extern "C" fn init_logger(config_ptr: *const c_char) -> bool { + match internal_init_logger(config_ptr) { + Ok(_) => true, + Err(e) => { + set_last_error(e); + false + } + } +} + +unsafe fn internal_call_utils_method(method_ptr: *const c_char) -> Result<*const c_char> { + let method_str = CStr::from_ptr(method_ptr).to_str().unwrap(); + + let method = serde_json::from_str::(method_str)?; + let response = rust_call_utils_method(method); + + let response_string = serde_json::to_string(&response)?; + let s = CString::new(response_string).unwrap(); + + Ok(s.into_raw()) +} + +#[allow(clippy::missing_safety_doc)] +#[no_mangle] +pub unsafe extern "C" fn call_utils_method(config_ptr: *const c_char) -> *const c_char { + match internal_call_utils_method(config_ptr) { + Ok(v) => v, + Err(e) => { + set_last_error(e); + null() + } + } +} diff --git a/src/secret_manager.rs b/src/secret_manager.rs new file mode 100644 index 0000000..455a095 --- /dev/null +++ b/src/secret_manager.rs @@ -0,0 +1,107 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + ffi::{c_char, CStr, CString}, + ptr::null, + sync::Arc, +}; + +use iota_sdk_bindings_core::{ + call_secret_manager_method as rust_call_secret_manager_method, + iota_sdk::client::secret::{SecretManager as RustSecretManager, SecretManagerDto}, + SecretManagerMethod, +}; +use tokio::sync::RwLock; + +use crate::error::{set_last_error, Error, Result}; + +pub struct SecretManager { + pub secret_manager: Arc>, +} + +unsafe fn internal_create_secret_manager(options_ptr: *const c_char) -> Result<*const SecretManager> { + let options_string = CStr::from_ptr(options_ptr); + + let secret_manager_dto = serde_json::from_str::(options_string.to_str().unwrap())?; + let secret_manager = RustSecretManager::try_from(secret_manager_dto)?; + + let secret_manager_wrap = SecretManager { + secret_manager: Arc::new(RwLock::new(secret_manager)), + }; + + let secret_manager_ptr = Box::into_raw(Box::new(secret_manager_wrap)); + + Ok(secret_manager_ptr) +} + +#[no_mangle] +pub unsafe extern "C" fn create_secret_manager(options_ptr: *const c_char) -> *const SecretManager { + match internal_create_secret_manager(options_ptr) { + Ok(v) => v, + Err(e) => { + set_last_error(e); + null() + } + } +} + +unsafe fn internal_destroy_secret_manager(secret_manager_ptr: *mut SecretManager) -> Result<()> { + log::debug!("[Rust] Secret Manager destroy called"); + + if secret_manager_ptr.is_null() { + log::error!("[Rust] Secret Manager pointer was null!"); + return Err(Error::from("pointer is null")); + } + + let _ = Box::from_raw(secret_manager_ptr); + + log::debug!("[Rust] Destroyed Secret Manager"); + Ok(()) +} + +#[no_mangle] +pub unsafe extern "C" fn destroy_secret_manager(secret_manager_ptr: *mut SecretManager) -> bool { + match internal_destroy_secret_manager(secret_manager_ptr) { + Ok(_) => true, + Err(e) => { + set_last_error(e); + false + } + } +} + +unsafe fn internal_call_secret_manager_method( + secret_manager_ptr: *mut SecretManager, + method_ptr: *const c_char, +) -> Result<*const c_char> { + let secret_manager = { + assert!(!secret_manager_ptr.is_null()); + &mut *secret_manager_ptr + }; + + let method_string = CStr::from_ptr(method_ptr); + + let method = serde_json::from_str::(method_string.to_str().unwrap())?; + let response = + crate::block_on(async { rust_call_secret_manager_method(&secret_manager.secret_manager, method).await }); + + let response_string = serde_json::to_string(&response)?; + let s = CString::new(response_string).unwrap(); + + Ok(s.into_raw()) +} + +#[no_mangle] +pub unsafe extern "C" fn call_secret_manager_method( + secret_manager: *mut SecretManager, + method: *const c_char, +) -> *const c_char { + match internal_call_secret_manager_method(secret_manager, method) { + Ok(v) => v, + Err(e) => { + set_last_error(e); + null() + } + } +} diff --git a/src/wallet.rs b/src/wallet.rs new file mode 100644 index 0000000..53942ac --- /dev/null +++ b/src/wallet.rs @@ -0,0 +1,249 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +use std::{ + ffi::{c_char, CStr, CString}, + ptr::null, + sync::Arc, +}; + +use iota_sdk_bindings_core::{ + call_wallet_method as rust_call_wallet_method, + iota_sdk::wallet::{events::types::WalletEventType, Wallet as RustWallet}, + Response, WalletMethod, WalletOptions, +}; +use log::debug; +use tokio::sync::RwLock; + +use crate::{ + client::Client, + error::{set_last_error, Error, Result}, + SecretManager, +}; + +pub struct Wallet { + pub wallet: Arc>>, +} + +unsafe fn internal_destroy_wallet(wallet_ptr: *mut Wallet) -> Result<()> { + let wallet = { + assert!(!wallet_ptr.is_null()); + &mut *wallet_ptr + }; + + crate::block_on(async { + *wallet.wallet.write().await = None; + }); + Ok(()) +} + +#[no_mangle] +pub unsafe extern "C" fn destroy_wallet(wallet_ptr: *mut Wallet) -> bool { + match internal_destroy_wallet(wallet_ptr) { + Ok(_) => true, + Err(e) => { + set_last_error(e); + false + } + } +} + +unsafe fn internal_create_wallet(options_ptr: *const c_char) -> Result<*mut Wallet> { + let options_string = CStr::from_ptr(options_ptr).to_str().unwrap(); + + let wallet_options = serde_json::from_str::(options_string)?; + let wallet = crate::block_on(async { wallet_options.build().await })?; + + let wallet_wrap = Wallet { + wallet: Arc::new(RwLock::new(Some(wallet))), + }; + + let wallet_ptr = Box::into_raw(Box::new(wallet_wrap)); + + Ok(wallet_ptr) +} + +#[no_mangle] +pub unsafe extern "C" fn create_wallet(options_ptr: *const c_char) -> *const Wallet { + match internal_create_wallet(options_ptr) { + Ok(v) => v, + Err(e) => { + set_last_error(e); + null() + } + } +} + +unsafe fn internal_call_wallet_method(wallet_ptr: *mut Wallet, method_ptr: *const c_char) -> Result<*const c_char> { + let wallet = { + assert!(!wallet_ptr.is_null()); + &mut *wallet_ptr + }; + + let method_string = CStr::from_ptr(method_ptr).to_str().unwrap(); + let method = serde_json::from_str::(method_string)?; + + let response = crate::block_on(async { + match wallet.wallet.read().await.as_ref() { + Some(wallet) => rust_call_wallet_method(wallet, method).await, + None => Response::Panic("wallet got destroyed".into()), + } + }); + + let response_string = serde_json::to_string(&response)?; + let s = CString::new(response_string).unwrap(); + + Ok(s.into_raw()) +} + +#[no_mangle] +pub unsafe extern "C" fn call_wallet_method(wallet_ptr: *mut Wallet, method_ptr: *const c_char) -> *const c_char { + match internal_call_wallet_method(wallet_ptr, method_ptr) { + Ok(v) => v, + Err(e) => { + set_last_error(e); + null() + } + } +} + +unsafe fn internal_listen_wallet( + wallet_ptr: *mut Wallet, + events_ptr: *const c_char, + handler: extern "C" fn(*const c_char), +) -> Result { + let wallet = { + assert!(!wallet_ptr.is_null()); + &mut *wallet_ptr + }; + + let events_string = CStr::from_ptr(events_ptr).to_str().unwrap(); + let rust_events = serde_json::from_str::>(events_string); + + if rust_events.is_err() { + return Ok(false); + } + + let mut wallet_events: Vec = Vec::new(); + for event in rust_events.unwrap() { + let event = match serde_json::from_str::(&event) { + Ok(event) => event, + Err(e) => { + debug!("Wrong event to listen! {e:?}"); + return Ok(false); + } + }; + wallet_events.push(event); + } + + crate::block_on(async { + wallet + .wallet + .read() + .await + .as_ref() + .expect("wallet got destroyed") + .listen(wallet_events, move |event_data| { + if let Ok(event_str) = serde_json::to_string(event_data) { + let s = CString::new(event_str).unwrap(); + handler(s.into_raw()) + } + }) + .await + }); + + Ok(true) +} + +#[no_mangle] +pub unsafe extern "C" fn listen_wallet( + wallet_ptr: *mut Wallet, + events: *const c_char, + handler: extern "C" fn(*const c_char), +) -> bool { + match internal_listen_wallet(wallet_ptr, events, handler) { + Ok(v) => v, + Err(e) => { + set_last_error(e); + false + } + } +} + +unsafe fn internal_get_client_from_wallet(wallet_ptr: *mut Wallet) -> Result<*const Client> { + let wallet = { + assert!(!wallet_ptr.is_null()); + &mut *wallet_ptr + }; + + let client = crate::block_on(async { + wallet + .wallet + .read() + .await + .as_ref() + .map(|w| w.client().clone()) + .ok_or_else(|| { + Error::from( + serde_json::to_string(&Response::Panic("wallet got destroyed".into())) + .expect("json to string error") + .as_str(), + ) + }) + })?; + + let client_wrap = Client { client }; + let client_ptr = Box::into_raw(Box::new(client_wrap)); + + Ok(client_ptr) +} + +#[no_mangle] +pub unsafe extern "C" fn get_client_from_wallet(wallet_ptr: *mut Wallet) -> *const Client { + match internal_get_client_from_wallet(wallet_ptr) { + Ok(v) => v, + Err(e) => { + set_last_error(e); + null() + } + } +} + +unsafe fn internal_get_secret_manager_from_wallet(wallet_ptr: *mut Wallet) -> Result<*const SecretManager> { + let wallet = { + assert!(!wallet_ptr.is_null()); + &mut *wallet_ptr + }; + + let secret_manager = crate::block_on(async { + wallet + .wallet + .read() + .await + .as_ref() + .map(|w| w.get_secret_manager().clone()) + .ok_or_else(|| { + Error::from( + serde_json::to_string(&Response::Panic("wallet got destroyed".into())) + .expect("json to string error") + .as_str(), + ) + }) + })?; + + let secret_manager_wrap = SecretManager { secret_manager }; + let secret_manager_ptr = Box::into_raw(Box::new(secret_manager_wrap)); + + Ok(secret_manager_ptr) +} + +#[no_mangle] +pub unsafe extern "C" fn get_secret_manager_from_wallet(wallet_ptr: *mut Wallet) -> *const SecretManager { + match internal_get_secret_manager_from_wallet(wallet_ptr) { + Ok(v) => v, + Err(e) => { + set_last_error(e); + null() + } + } +}