diff --git a/.github/workflows/bindings-native-publish.yml b/.github/workflows/bindings-native-publish.yml new file mode 100644 index 0000000000..b18f1a0396 --- /dev/null +++ b/.github/workflows/bindings-native-publish.yml @@ -0,0 +1,93 @@ +name: Native prebuilt publish + +on: workflow_dispatch + +env: + CARGO_INCREMENTAL: 0 + +jobs: + native-binding-prebuilt: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-20.04, macos-13, windows-2022] + + steps: + - uses: actions/checkout@v3 + + - name: Select Xcode + uses: maxim-lobanov/setup-xcode@v1 + if: matrix.os == 'macos-13' + with: + xcode-version: '14.3' + + - name: Install Rust toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Install LLVM and Clang (Windows) # required for bindgen to work, see https://github.com/rust-lang/rust-bindgen/issues/1797 + uses: KyleMayes/install-llvm-action@32c4866ebb71e0949e8833eb49beeebed48532bd + if: matrix.os == 'windows-2022' + with: + version: "11.0" + directory: ${{ runner.temp }}/llvm + + - name: Set LIBCLANG_PATH (Windows) + run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV + if: matrix.os == 'windows-2022' + + - name: Set deployment target (macOS) + run: echo "MACOSX_DEPLOYMENT_TARGET=10.13" >> $GITHUB_ENV + if: matrix.os == 'macos-13' + + - name: Get current date + run: echo "CURRENT_DATE=$(date +'%Y-%m-%d')" >> $GITHUB_ENV + if: matrix.os == 'macos-13' || ${{ startsWith(matrix.os, 'ubuntu') }} + + - name: Get current date + if: matrix.os == 'windows-2022' + run: echo "CURRENT_DATE=$(Get-Date -Format "yyyy-MM-dd")" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append + + - name: Install required packages (Ubuntu) + if: ${{ startsWith(matrix.os, 'ubuntu') }} + run: | + sudo apt-get update + sudo apt-get install libudev-dev libusb-1.0-0-dev + + - name: Cache cargo registry + uses: actions/cache@v3 + with: + path: ~/.cargo/registry + # Add date to the cache to keep it up to date + key: ${{ matrix.os }}-stable-cargo-registry-${{ hashFiles('**/Cargo.lock') }}-${{ env.CURRENT_DATE }} + # Restore from outdated cache for speed + restore-keys: | + ${{ matrix.os }}-stable-cargo-registry-${{ hashFiles('**/Cargo.lock') }} + ${{ matrix.os }}-stable-cargo-registry- + - name: Cache cargo index + uses: actions/cache@v3 + with: + path: ~/.cargo/git + # Add date to the cache to keep it up to date + key: ${{ matrix.os }}-stable-cargo-index-${{ hashFiles('**/Cargo.lock') }}-${{ env.CURRENT_DATE }} + # Restore from outdated cache for speed + restore-keys: | + ${{ matrix.os }}-stable-cargo-index-${{ hashFiles('**/Cargo.lock') }} + ${{ matrix.os }}-stable-cargo-index- + + # This step is required to support macOS 10.13 + - name: Patch librocksdb-sys (macOS) + if: ${{ startsWith(matrix.os, 'macos') }} + run: | + cargo install cargo-patch + cp ${{ github.workspace }}/.patches/rocksdb_faligned_allocation.patch . + git apply --ignore-space-change --ignore-whitespace ${{ github.workspace }}/.patches/macos_cargo_toml.patch + cat Cargo.toml + cargo patch + + - name: Cargo build + run: cargo build --release + working-directory: bindings/native diff --git a/Cargo.lock b/Cargo.lock index 9f3d5bf061..573b80bfc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1690,6 +1690,19 @@ dependencies = [ "zeroize", ] +[[package]] +name = "iota-sdk-native" +version = "0.1.0" +dependencies = [ + "futures", + "iota-sdk-bindings-core", + "log", + "once_cell", + "serde_json", + "tokio", + "zeroize", +] + [[package]] name = "iota-sdk-nodejs" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 99647d0d92..a2502554f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ resolver = "2" members = [ "bindings/core", + "bindings/native", "bindings/nodejs", "bindings/python", "bindings/wasm", diff --git a/bindings/native/.cargo/config.toml b/bindings/native/.cargo/config.toml new file mode 100644 index 0000000000..ac2b23f814 --- /dev/null +++ b/bindings/native/.cargo/config.toml @@ -0,0 +1,2 @@ +[target.x86_64-pc-windows-msvc] +rustflags = ["-C", "target-feature=+crt-static"] diff --git a/bindings/native/Cargo.toml b/bindings/native/Cargo.toml new file mode 100644 index 0000000000..860c9f6a7e --- /dev/null +++ b/bindings/native/Cargo.toml @@ -0,0 +1,32 @@ +[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 = { path = "../core", default-features = false, features = [ "events", "rocksdb", "ledger_nano", "storage", "stronghold" ] } + +futures = { version = "0.3.28", default-features = false } +once_cell = { version = "1.17.2", default-features = false } +serde_json = { version = "1.0.96", default-features = false } +tokio = { version = "1.28.2", default-features = false } +log = { version = "0.4.14" } +zeroize = { version = "1.6.0", default-features = false } \ No newline at end of file diff --git a/bindings/native/README.md b/bindings/native/README.md new file mode 100644 index 0000000000..7155e86802 --- /dev/null +++ b/bindings/native/README.md @@ -0,0 +1,46 @@ +# IOTA SDK Native + +This binding wraps the IOTA SDK into a native, shared library. + +It can be referenced by other languages such as Go, C/C++ or C#. + +It exports the following functions: + +``` +init_logger +binding_get_last_error + +call_client_method +call_secret_manager_method +call_utils_method +call_wallet_method + +create_client +create_secret_manager +create_wallet +listen_wallet + +destroy_client +destroy_secret_manager +destroy_string +destroy_wallet + +get_client_from_wallet +get_secret_manager_from_wallet +``` + +Generated C/C++ headers can be found under `bindings/native/headers`. + +Headers can be regenerated by running `bindings/native/bindgen.sh`. This requires [cbindgen](https://github.com/mozilla/cbindgen/). + +An example implementation in Go can be found [here](https://github.com/iotaledger/wasp-wallet-sdk/blob/main/wrapper.go). + +# Usage + +All strings need to be null terminated. + +All strings coming from the library need to be free'd by calling `destroy_string` after receiving and copying them. + +Any function that either returns a `NULL` pointer or `false` might have catched an error. + +This error can be obtained via `get_last_error`. \ No newline at end of file diff --git a/bindings/native/bindgen.sh b/bindings/native/bindgen.sh new file mode 100755 index 0000000000..2a20b6a4cf --- /dev/null +++ b/bindings/native/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/bindings/native/headers/iota_sdk.h b/bindings/native/headers/iota_sdk.h new file mode 100644 index 0000000000..36c460421b --- /dev/null +++ b/bindings/native/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/bindings/native/headers/iota_sdk.hpp b/bindings/native/headers/iota_sdk.hpp new file mode 100644 index 0000000000..2f14a5f6a9 --- /dev/null +++ b/bindings/native/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/bindings/native/src/client.rs b/bindings/native/src/client.rs new file mode 100644 index 0000000000..e2c7e0d87e --- /dev/null +++ b/bindings/native/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/bindings/native/src/error.rs b/bindings/native/src/error.rs new file mode 100644 index 0000000000..809b99bdd9 --- /dev/null +++ b/bindings/native/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/bindings/native/src/lib.rs b/bindings/native/src/lib.rs new file mode 100644 index 0000000000..a3d9ec0bfe --- /dev/null +++ b/bindings/native/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/bindings/native/src/secret_manager.rs b/bindings/native/src/secret_manager.rs new file mode 100644 index 0000000000..455a095d5b --- /dev/null +++ b/bindings/native/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/bindings/native/src/wallet.rs b/bindings/native/src/wallet.rs new file mode 100644 index 0000000000..53942ac9a1 --- /dev/null +++ b/bindings/native/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() + } + } +}