diff --git a/bindings/c-ffi/example.c b/bindings/c-ffi/example.c index 43fa14f..4f7abac 100644 --- a/bindings/c-ffi/example.c +++ b/bindings/c-ffi/example.c @@ -20,9 +20,9 @@ int main() { char wallet_data[300]; sprintf(wallet_data, "{ \"data_dir\": \"./data\", \"bitcoin_network\": \"Regtest\", " - "\"database_type\": \"Sqlite\", \"max_allocations_per_utxo\": 1, " - "\"pubkey\": \"%s\", \"mnemonic\": \"%s\", \"vanilla_keychain\": " - "null }", + "\"database_type\": \"Sqlite\", \"max_allocations_per_utxo\": " + "\"1\", \"pubkey\": \"%s\", \"mnemonic\": \"%s\", " + "\"vanilla_keychain\": null }", account_xpub, mnemonic); printf("Creating wallet...\n"); @@ -82,7 +82,7 @@ int main() { printf("BTC balance after sync: %s\n", btc_balance_2); CResultString created_res = - rgblib_create_utxos(wlt, online, false, "25", NULL, 1.5, false); + rgblib_create_utxos(wlt, online, false, "25", NULL, "1.5", false); if (created_res.result == Err) { printf("ERR: %s\n", created_res.inner); return EXIT_FAILURE; @@ -90,8 +90,8 @@ int main() { const char *created = created_res.inner; printf("Created %s UTXOs\n", created); - CResultString asset_nia_res = - rgblib_issue_asset_nia(wlt, online, "USDT", "Tether", 2, "[777, 66]"); + CResultString asset_nia_res = rgblib_issue_asset_nia( + wlt, online, "USDT", "Tether", "2", "[\"777\", \"66\"]"); if (asset_nia_res.result == Ok) { printf("Issued a NIA asset: %s\n", asset_nia_res.inner); } else { @@ -99,8 +99,8 @@ int main() { return EXIT_FAILURE; } - CResultString asset_cfa_res = - rgblib_issue_asset_cfa(wlt, online, "Cfa", "desc", 2, "[777]", NULL); + CResultString asset_cfa_res = rgblib_issue_asset_cfa( + wlt, online, "Cfa", "desc", "2", "[\"777\"]", NULL); if (asset_cfa_res.result == Ok) { printf("Issued a CFA asset: %s\n", asset_cfa_res.inner); } else { @@ -109,7 +109,7 @@ int main() { } CResultString asset_uda_res = rgblib_issue_asset_uda( - wlt, online, "TKN", "Token", NULL, 2, "README.md", "[]"); + wlt, online, "TKN", "Token", NULL, "2", "README.md", "[]"); if (asset_uda_res.result == Ok) { printf("Issued a UDA asset: %s\n", asset_uda_res.inner); } else { @@ -139,7 +139,7 @@ int main() { const char *transport_endpoints = "[\"rpc://127.0.0.1:3000/json-rpc\"]"; CResultString receive_data_res = - rgblib_blind_receive(wlt, NULL, NULL, NULL, transport_endpoints, 1); + rgblib_blind_receive(wlt, NULL, NULL, NULL, transport_endpoints, "1"); if (receive_data_res.result == Ok) { printf("Receive data: %s\n", receive_data_res.inner); } else { @@ -155,7 +155,7 @@ int main() { return EXIT_FAILURE; } - CResultString fee_res = rgblib_get_fee_estimation(wlt, online, 7); + CResultString fee_res = rgblib_get_fee_estimation(wlt, online, "7"); printf("Fee estimation: %s\n", fee_res.inner); return EXIT_SUCCESS; diff --git a/bindings/c-ffi/src/lib.rs b/bindings/c-ffi/src/lib.rs index 7ee0416..9ddd8db 100644 --- a/bindings/c-ffi/src/lib.rs +++ b/bindings/c-ffi/src/lib.rs @@ -5,7 +5,7 @@ use crate::utils::*; use std::{ any::TypeId, collections::{hash_map::DefaultHasher, HashMap}, - ffi::{c_char, c_float, c_uchar, c_ushort, c_void, CStr, CString}, + ffi::{c_char, c_void, CStr, CString}, hash::{Hash, Hasher}, ptr::null_mut, str::FromStr, @@ -61,7 +61,7 @@ pub extern "C" fn rgblib_blind_receive( amount_opt: *const c_char, duration_seconds_opt: *const c_char, transport_endpoints: *const c_char, - min_confirmations: c_uchar, + min_confirmations: *const c_char, ) -> CResultString { blind_receive( wallet, @@ -81,7 +81,7 @@ pub extern "C" fn rgblib_create_utxos( up_to: bool, num_opt: *const c_char, size_opt: *const c_char, - fee_rate: c_float, + fee_rate: *const c_char, skip_sync: bool, ) -> CResultString { create_utxos( @@ -121,7 +121,7 @@ pub extern "C" fn rgblib_get_btc_balance( pub extern "C" fn rgblib_get_fee_estimation( wallet: &COpaqueStruct, online: &COpaqueStruct, - blocks: c_ushort, + blocks: *const c_char, ) -> CResultString { get_fee_estimation(wallet, online, blocks).into() } @@ -141,7 +141,7 @@ pub extern "C" fn rgblib_issue_asset_cfa( online: &COpaqueStruct, name: *const c_char, details_opt: *const c_char, - precision: c_uchar, + precision: *const c_char, amounts: *const c_char, file_path_opt: *const c_char, ) -> CResultString { @@ -163,7 +163,7 @@ pub extern "C" fn rgblib_issue_asset_nia( online: &COpaqueStruct, ticker: *const c_char, name: *const c_char, - precision: c_uchar, + precision: *const c_char, amounts: *const c_char, ) -> CResultString { issue_asset_nia(wallet, online, ticker, name, precision, amounts).into() @@ -176,7 +176,7 @@ pub extern "C" fn rgblib_issue_asset_uda( ticker: *const c_char, name: *const c_char, details_opt: *const c_char, - precision: c_uchar, + precision: *const c_char, media_file_path_opt: *const c_char, attachments_file_paths: *const c_char, ) -> CResultString { @@ -258,8 +258,8 @@ pub extern "C" fn rgblib_send( online: &COpaqueStruct, recipient_map: *const c_char, donation: bool, - fee_rate: c_float, - min_confirmations: c_uchar, + fee_rate: *const c_char, + min_confirmations: *const c_char, skip_sync: bool, ) -> CResultString { send( @@ -279,8 +279,8 @@ pub extern "C" fn rgblib_send_btc( wallet: &COpaqueStruct, online: &COpaqueStruct, address: *const c_char, - amount: u64, - fee_rate: c_float, + amount: *const c_char, + fee_rate: *const c_char, skip_sync: bool, ) -> CResultString { send_btc(wallet, online, address, amount, fee_rate, skip_sync).into() @@ -298,7 +298,7 @@ pub extern "C" fn rgblib_witness_receive( amount_opt: *const c_char, duration_seconds_opt: *const c_char, transport_endpoints: *const c_char, - min_confirmations: c_uchar, + min_confirmations: *const c_char, ) -> CResultString { witness_receive( wallet, diff --git a/bindings/c-ffi/src/utils.rs b/bindings/c-ffi/src/utils.rs index b46cc92..b61a816 100644 --- a/bindings/c-ffi/src/utils.rs +++ b/bindings/c-ffi/src/utils.rs @@ -5,6 +5,9 @@ pub(crate) enum Error { #[error("Error converting JSON: {0}")] JSONConversion(#[from] serde_json::Error), + #[error("Error converting string into number")] + StringToNumberConversion, + #[error("Error from rgb-lib: {0}")] RgbLib(#[from] RgbLibError), @@ -103,6 +106,15 @@ where } } +fn convert_strings_array(ptr: *const c_char) -> Result, Error> { + let str_array: Vec = serde_json::from_str(&ptr_to_string(ptr))?; + str_array + .iter() + .map(|a| a.parse()) + .collect::, _>>() + .map_err(|_| Error::StringToNumberConversion) +} + fn convert_optional_number( ptr: *const c_char, ) -> Result, Error> { @@ -132,6 +144,12 @@ fn convert_optional_string(ptr: *const c_char) -> Option { } } +fn ptr_to_num(ptr: *const c_char) -> Result { + ptr_to_string(ptr) + .parse() + .map_err(|_| Error::StringToNumberConversion) +} + fn ptr_to_string(ptr: *const c_char) -> String { unsafe { CStr::from_ptr(ptr).to_string_lossy().into_owned() } } @@ -154,14 +172,15 @@ pub(crate) fn blind_receive( amount_opt: *const c_char, duration_seconds_opt: *const c_char, transport_endpoints: *const c_char, - min_confirmations: c_uchar, + min_confirmations: *const c_char, ) -> Result { let wallet = Wallet::from_opaque(wallet)?; let transport_endpoints: Vec = serde_json::from_str(&ptr_to_string(transport_endpoints))?; let asset_id = convert_optional_string(asset_id_opt); - let amount: Option = convert_optional_number(amount_opt)?; - let duration_seconds: Option = convert_optional_number(duration_seconds_opt)?; + let amount = convert_optional_number(amount_opt)?; + let duration_seconds = convert_optional_number(duration_seconds_opt)?; + let min_confirmations = ptr_to_num(min_confirmations)?; let res = wallet.blind_receive( asset_id, amount, @@ -178,13 +197,14 @@ pub(crate) fn create_utxos( up_to: bool, num_opt: *const c_char, size_opt: *const c_char, - fee_rate: c_float, + fee_rate: *const c_char, skip_sync: bool, ) -> Result { let wallet = Wallet::from_opaque(wallet)?; let online = Online::from_opaque(online)?; - let num: Option = convert_optional_number(num_opt)?; - let size: Option = convert_optional_number(size_opt)?; + let num = convert_optional_number(num_opt)?; + let size = convert_optional_number(size_opt)?; + let fee_rate = ptr_to_num(fee_rate)?; let res = wallet.create_utxos((*online).clone(), up_to, num, size, fee_rate, skip_sync)?; Ok(serde_json::to_string(&res)?) } @@ -224,10 +244,11 @@ pub(crate) fn get_btc_balance( pub(crate) fn get_fee_estimation( wallet: &COpaqueStruct, online: &COpaqueStruct, - blocks: c_ushort, + blocks: *const c_char, ) -> Result { let wallet = Wallet::from_opaque(wallet)?; let online = Online::from_opaque(online)?; + let blocks = ptr_to_num(blocks)?; let res = wallet.get_fee_estimation((*online).clone(), blocks)?; Ok(serde_json::to_string(&res)?) } @@ -246,13 +267,14 @@ pub(crate) fn issue_asset_cfa( online: &COpaqueStruct, name: *const c_char, details_opt: *const c_char, - precision: c_uchar, + precision: *const c_char, amounts: *const c_char, file_path_opt: *const c_char, ) -> Result { let wallet = Wallet::from_opaque(wallet)?; let online = Online::from_opaque(online)?; - let amounts: Vec = serde_json::from_str(&ptr_to_string(amounts))?; + let precision = ptr_to_num(precision)?; + let amounts = convert_strings_array(amounts)?; let details = convert_optional_string(details_opt); let file_path = convert_optional_string(file_path_opt); let res = wallet.issue_asset_cfa( @@ -271,12 +293,13 @@ pub(crate) fn issue_asset_nia( online: &COpaqueStruct, ticker: *const c_char, name: *const c_char, - precision: c_uchar, + precision: *const c_char, amounts: *const c_char, ) -> Result { let wallet = Wallet::from_opaque(wallet)?; let online = Online::from_opaque(online)?; - let amounts: Vec = serde_json::from_str(&ptr_to_string(amounts))?; + let precision = ptr_to_num(precision)?; + let amounts = convert_strings_array(amounts)?; let res = wallet.issue_asset_nia( (*online).clone(), ptr_to_string(ticker), @@ -294,13 +317,14 @@ pub(crate) fn issue_asset_uda( ticker: *const c_char, name: *const c_char, details_opt: *const c_char, - precision: c_uchar, + precision: *const c_char, media_file_path_opt: *const c_char, attachments_file_paths: *const c_char, ) -> Result { let wallet = Wallet::from_opaque(wallet)?; let online = Online::from_opaque(online)?; let details = convert_optional_string(details_opt); + let precision = ptr_to_num(precision)?; let media_file_path = convert_optional_string(media_file_path_opt); let attachments_file_paths: Vec = serde_json::from_str(&ptr_to_string(attachments_file_paths))?; @@ -395,14 +419,16 @@ pub(crate) fn send( online: &COpaqueStruct, recipient_map: *const c_char, donation: bool, - fee_rate: c_float, - min_confirmations: c_uchar, + fee_rate: *const c_char, + min_confirmations: *const c_char, skip_sync: bool, ) -> Result { let wallet = Wallet::from_opaque(wallet)?; let online = Online::from_opaque(online)?; let recipient_map: HashMap> = serde_json::from_str(&ptr_to_string(recipient_map))?; + let fee_rate = ptr_to_num(fee_rate)?; + let min_confirmations = ptr_to_num(min_confirmations)?; let res = wallet.send( (*online).clone(), recipient_map, @@ -418,13 +444,15 @@ pub(crate) fn send_btc( wallet: &COpaqueStruct, online: &COpaqueStruct, address: *const c_char, - amount: u64, - fee_rate: c_float, + amount: *const c_char, + fee_rate: *const c_char, skip_sync: bool, ) -> Result { let wallet = Wallet::from_opaque(wallet)?; let online = Online::from_opaque(online)?; let address = ptr_to_string(address); + let amount = ptr_to_num(amount)?; + let fee_rate = ptr_to_num(fee_rate)?; let res = wallet.send_btc((*online).clone(), address, amount, fee_rate, skip_sync)?; Ok(res) } @@ -442,14 +470,15 @@ pub(crate) fn witness_receive( amount_opt: *const c_char, duration_seconds_opt: *const c_char, transport_endpoints: *const c_char, - min_confirmations: c_uchar, + min_confirmations: *const c_char, ) -> Result { let wallet = Wallet::from_opaque(wallet)?; let transport_endpoints: Vec = serde_json::from_str(&ptr_to_string(transport_endpoints))?; let asset_id = convert_optional_string(asset_id_opt); - let amount: Option = convert_optional_number(amount_opt)?; - let duration_seconds: Option = convert_optional_number(duration_seconds_opt)?; + let amount = convert_optional_number(amount_opt)?; + let duration_seconds = convert_optional_number(duration_seconds_opt)?; + let min_confirmations = ptr_to_num(min_confirmations)?; let res = wallet.witness_receive( asset_id, amount, diff --git a/src/lib.rs b/src/lib.rs index 0146a76..2a32dc7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -223,7 +223,8 @@ use sea_orm::{ EntityTrait, EnumIter, IntoActiveValue, QueryFilter, QueryOrder, TryIntoModel, }; use seals::SecretSeal; -use serde::{Deserialize, Serialize}; +use serde::de::{self, Unexpected, Visitor}; +use serde::{Deserialize, Deserializer, Serialize}; use slog::{debug, error, info, o, warn, Drain, Logger}; use slog_async::AsyncGuard; use slog_term::{FullFormat, PlainDecorator}; @@ -286,7 +287,8 @@ use crate::{ error::InternalError, utils::{ adjust_canonicalization, beneficiary_from_script_buf, calculate_descriptor_from_xprv, - calculate_descriptor_from_xpub, derive_account_xprv_from_mnemonic, get_xpub_from_xprv, + calculate_descriptor_from_xpub, derive_account_xprv_from_mnemonic, + from_str_or_number_mandatory, from_str_or_number_optional, get_xpub_from_xprv, load_rgb_runtime, now, setup_logger, RgbInExt, RgbOutExt, RgbPsbtExt, RgbRuntime, LOG_FILE, }, wallet::{Balance, Outpoint, NUM_KNOWN_SCHEMAS, SCHEMA_ID_CFA, SCHEMA_ID_NIA, SCHEMA_ID_UDA}, diff --git a/src/utils.rs b/src/utils.rs index 853ea0f..a69e67c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -140,6 +140,91 @@ pub(crate) fn adjust_canonicalization>(p: P) -> String { } } +fn deserialize_str_or_number<'de, D, T>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: FromStr + Copy, + T::Err: fmt::Display, +{ + struct StringOrNumberVisitor(std::marker::PhantomData); + + impl<'de, T> Visitor<'de> for StringOrNumberVisitor + where + T: FromStr + Copy, + T::Err: fmt::Display, + { + type Value = Option; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string, a number, or null") + } + + fn visit_u64(self, value: u64) -> Result + where + E: de::Error, + { + T::from_str(&value.to_string()) + .map(Some) + .map_err(de::Error::custom) + } + + fn visit_f64(self, value: f64) -> Result + where + E: de::Error, + { + T::from_str(&value.to_string()) + .map(Some) + .map_err(de::Error::custom) + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + value.parse::().map(Some).map_err(|e| { + de::Error::invalid_value(Unexpected::Str(value), &e.to_string().as_str()) + }) + } + + fn visit_none(self) -> Result + where + E: de::Error, + { + Ok(None) + } + + fn visit_unit(self) -> Result + where + E: de::Error, + { + Ok(None) + } + } + + deserializer.deserialize_any(StringOrNumberVisitor(std::marker::PhantomData)) +} + +pub(crate) fn from_str_or_number_mandatory<'de, D, T>(deserializer: D) -> Result +where + D: Deserializer<'de>, + T: FromStr + Copy, + T::Err: fmt::Display, +{ + match deserialize_str_or_number(deserializer)? { + Some(val) => Ok(val), + None => Err(de::Error::custom("expected a number but got null")), + } +} + +pub(crate) fn from_str_or_number_optional<'de, D, T>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: FromStr + Copy, + T::Err: fmt::Display, +{ + deserialize_str_or_number(deserializer) +} + #[cfg_attr(not(any(feature = "electrum", feature = "esplora")), allow(dead_code))] fn get_genesis_hash(bitcoin_network: &BitcoinNetwork) -> &str { match bitcoin_network { diff --git a/src/wallet/offline.rs b/src/wallet/offline.rs index 03e73b9..a7695c5 100644 --- a/src/wallet/offline.rs +++ b/src/wallet/offline.rs @@ -850,6 +850,7 @@ pub struct Recipient { /// Witness data (to be provided only with a witness recipient) pub witness_data: Option, /// RGB amount + #[serde(deserialize_with = "from_str_or_number_mandatory")] pub amount: u64, /// Transport endpoints pub transport_endpoints: Vec, @@ -860,8 +861,10 @@ pub struct Recipient { #[cfg_attr(feature = "camel_case", serde(rename_all = "camelCase"))] pub struct WitnessData { /// The Bitcoin amount (in sats) to send to the recipient + #[serde(deserialize_with = "from_str_or_number_mandatory")] pub amount_sat: u64, /// An optional blinding + #[serde(deserialize_with = "from_str_or_number_optional")] pub blinding: Option, } @@ -1101,12 +1104,14 @@ pub struct WalletData { /// Database type for the wallet pub database_type: DatabaseType, /// The max number of RGB allocations allowed per UTXO + #[serde(deserialize_with = "from_str_or_number_mandatory")] pub max_allocations_per_utxo: u32, /// Wallet account-level xPub pub pubkey: String, /// Wallet mnemonic phrase pub mnemonic: Option, /// Keychain index for the vanilla wallet (default: 1) + #[serde(deserialize_with = "from_str_or_number_optional")] pub vanilla_keychain: Option, }