diff --git a/packages/wasm-sdk/src/context_provider.rs b/packages/wasm-sdk/src/context_provider.rs index be6dac6108..aaea30430c 100644 --- a/packages/wasm-sdk/src/context_provider.rs +++ b/packages/wasm-sdk/src/context_provider.rs @@ -2,11 +2,7 @@ use std::sync::Arc; use dash_sdk::platform::ContextProvider; use dash_sdk::{ - dpp::{ - data_contract::TokenConfiguration, - prelude::CoreBlockHeight, - version::PlatformVersion, - }, + dpp::{data_contract::TokenConfiguration, prelude::CoreBlockHeight, version::PlatformVersion}, error::ContextProviderError, platform::{DataContract, Identifier}, }; @@ -50,16 +46,16 @@ impl ContextProvider for WasmContext { // For WASM context without trusted provider, we need to fetch token configuration // from the network. This is a simplified implementation that would need to be // enhanced with actual network fetching logic in a production environment. - + // TODO: Implement actual token configuration fetching from network // For now, we'll return None which will cause the proof verification to fail // with a clearer error message indicating missing token configuration - + tracing::warn!( token_id = %token_id, "Token configuration not available in WASM context - this will cause proof verification to fail. Use trusted context builders for proof verification." ); - + Ok(None) } diff --git a/packages/wasm-sdk/src/dpns.rs b/packages/wasm-sdk/src/dpns.rs index 0993a2ae6e..d38d9d6eda 100644 --- a/packages/wasm-sdk/src/dpns.rs +++ b/packages/wasm-sdk/src/dpns.rs @@ -1,13 +1,16 @@ -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::{JsError, JsValue}; use crate::sdk::WasmSdk; -use serde::{Serialize, Deserialize}; -use dash_sdk::platform::dpns_usernames::{convert_to_homograph_safe_chars, is_contested_username, is_valid_username, RegisterDpnsNameInput}; -use dash_sdk::platform::{Fetch, Identity}; use dash_sdk::dpp::document::{Document, DocumentV0Getters}; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; use dash_sdk::dpp::prelude::Identifier; +use dash_sdk::platform::dpns_usernames::{ + convert_to_homograph_safe_chars, is_contested_username, is_valid_username, + RegisterDpnsNameInput, +}; +use dash_sdk::platform::{Fetch, Identity}; +use serde::{Deserialize, Serialize}; use simple_signer::SingleKeySigner; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::{JsError, JsValue}; #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -49,36 +52,37 @@ pub async fn dpns_register_name( let identity_id_parsed = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - ).map_err(|e| JsError::new(&format!("Invalid identity ID: {}", e)))?; - + ) + .map_err(|e| JsError::new(&format!("Invalid identity ID: {}", e)))?; + // Fetch the identity let identity = Identity::fetch(sdk.as_ref(), identity_id_parsed) .await .map_err(|e| JsError::new(&format!("Failed to fetch identity: {}", e)))? .ok_or_else(|| JsError::new("Identity not found"))?; - + // Create signer let signer = SingleKeySigner::new(private_key_wif) .map_err(|e| JsError::new(&format!("Invalid private key WIF: {}", e)))?; - + // Get the specific identity public key let identity_public_key = identity .get_public_key_by_id(public_key_id.into()) .ok_or_else(|| JsError::new(&format!("Public key with ID {} not found", public_key_id)))? .clone(); - + // Store the JS callback in a thread-local variable that we can access from the closure thread_local! { static PREORDER_CALLBACK: std::cell::RefCell> = std::cell::RefCell::new(None); } - + // Set the callback if provided if let Some(ref js_callback) = preorder_callback { PREORDER_CALLBACK.with(|cb| { *cb.borrow_mut() = Some(js_callback.clone()); }); } - + // Create a Rust callback that will call the JavaScript callback let callback_box = if preorder_callback.is_some() { Some(Box::new(move |doc: &Document| { @@ -93,7 +97,7 @@ pub async fn dpns_register_name( "createdAtCoreBlockHeight": doc.created_at_core_block_height(), "message": "Preorder document submitted successfully", }); - + if let Ok(js_value) = serde_wasm_bindgen::to_value(&preorder_info) { let _ = js_callback.call1(&JsValue::NULL, &js_value); } @@ -103,7 +107,7 @@ pub async fn dpns_register_name( } else { None }; - + // Create registration input with the callback let input = RegisterDpnsNameInput { label: label.to_string(), @@ -112,41 +116,42 @@ pub async fn dpns_register_name( signer, preorder_callback: callback_box, }; - + // Register the name - let result = sdk.as_ref() + let result = sdk + .as_ref() .register_dpns_name(input) .await .map_err(|e| JsError::new(&format!("Failed to register DPNS name: {}", e)))?; - + // Clear the thread-local callback PREORDER_CALLBACK.with(|cb| { *cb.borrow_mut() = None; }); - + // Convert result to JS-friendly format let js_result = RegisterDpnsNameResult { - preorder_document_id: result.preorder_document.id().to_string( - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58 - ), - domain_document_id: result.domain_document.id().to_string( - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58 - ), + preorder_document_id: result + .preorder_document + .id() + .to_string(dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58), + domain_document_id: result + .domain_document + .id() + .to_string(dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58), full_domain_name: result.full_domain_name, }; - + // Serialize to JsValue let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - js_result.serialize(&serializer) + js_result + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize result: {}", e))) } /// Check if a DPNS name is available #[wasm_bindgen] -pub async fn dpns_is_name_available( - sdk: &WasmSdk, - label: &str, -) -> Result { +pub async fn dpns_is_name_available(sdk: &WasmSdk, label: &str) -> Result { sdk.as_ref() .is_dpns_name_available(label) .await @@ -155,22 +160,19 @@ pub async fn dpns_is_name_available( /// Resolve a DPNS name to an identity ID #[wasm_bindgen] -pub async fn dpns_resolve_name( - sdk: &WasmSdk, - name: &str, -) -> Result { - let result = sdk.as_ref() +pub async fn dpns_resolve_name(sdk: &WasmSdk, name: &str) -> Result { + let result = sdk + .as_ref() .resolve_dpns_name(name) .await .map_err(|e| JsError::new(&format!("Failed to resolve DPNS name: {}", e)))?; - + match result { Some(identity_id) => { - let id_string = identity_id.to_string( - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58 - ); + let id_string = identity_id + .to_string(dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58); Ok(JsValue::from_str(&id_string)) - }, + } None => Ok(JsValue::NULL), } -} \ No newline at end of file +} diff --git a/packages/wasm-sdk/src/lib.rs b/packages/wasm-sdk/src/lib.rs index a6e0186fa8..6cd53dde7c 100644 --- a/packages/wasm-sdk/src/lib.rs +++ b/packages/wasm-sdk/src/lib.rs @@ -1,23 +1,23 @@ use wasm_bindgen::{prelude::wasm_bindgen, JsValue}; pub mod context_provider; -pub mod dpp; pub mod dpns; +pub mod dpp; pub mod error; +pub mod queries; pub mod sdk; pub mod state_transitions; pub mod verify; -pub mod queries; pub mod wallet; // Re-export commonly used items -pub use sdk::{WasmSdk, WasmSdkBuilder}; +pub use dpns::*; pub use queries::{ - data_contract::*, document::*, dpns::*, epoch::*, group::*, protocol::*, system::*, token::*, voting::*, - identity as query_identity + data_contract::*, document::*, dpns::*, epoch::*, group::*, identity as query_identity, + protocol::*, system::*, token::*, voting::*, }; +pub use sdk::{WasmSdk, WasmSdkBuilder}; pub use state_transitions::identity as state_transition_identity; -pub use dpns::*; pub use wallet::*; #[global_allocator] diff --git a/packages/wasm-sdk/src/queries/data_contract.rs b/packages/wasm-sdk/src/queries/data_contract.rs index 95d402fdd2..547499f816 100644 --- a/packages/wasm-sdk/src/queries/data_contract.rs +++ b/packages/wasm-sdk/src/queries/data_contract.rs @@ -1,14 +1,14 @@ use crate::dpp::DataContractWasm; -use crate::sdk::WasmSdk; use crate::queries::ProofMetadataResponse; -use dash_sdk::platform::{DataContract, Fetch, FetchMany, Identifier}; +use crate::sdk::WasmSdk; +use dash_sdk::dpp::data_contract::conversion::json::DataContractJsonConversionMethodsV0; use dash_sdk::platform::query::LimitQuery; +use dash_sdk::platform::{DataContract, Fetch, FetchMany, Identifier}; use drive_proof_verifier::types::{DataContractHistory, DataContracts}; -use dash_sdk::dpp::data_contract::conversion::json::DataContractJsonConversionMethodsV0; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsError, JsValue}; -use std::collections::BTreeMap; #[wasm_bindgen] pub async fn data_contract_fetch( @@ -36,20 +36,23 @@ pub async fn data_contract_fetch_with_proof_info( dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - let (contract, metadata, proof) = DataContract::fetch_with_metadata_and_proof(sdk, id, None) - .await?; + let (contract, metadata, proof) = + DataContract::fetch_with_metadata_and_proof(sdk, id, None).await?; match contract { Some(contract) => { let response = ProofMetadataResponse { - data: contract.to_json(&dash_sdk::dpp::version::PlatformVersion::get(sdk.version()).unwrap())?, + data: contract.to_json( + &dash_sdk::dpp::version::PlatformVersion::get(sdk.version()).unwrap(), + )?, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer - let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + let serializer = serde_wasm_bindgen::Serializer::json_compatible(); + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } None => Err(JsError::new("Data contract not found")), @@ -75,34 +78,35 @@ pub async fn get_data_contract_history( id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Create query with start timestamp let query = LimitQuery { query: (contract_id, start_at_ms.unwrap_or(0)), start_info: None, limit, }; - + // Fetch contract history let history_result = DataContractHistory::fetch(sdk.as_ref(), query) .await .map_err(|e| JsError::new(&format!("Failed to fetch data contract history: {}", e)))?; - + // Convert to response format let mut versions = BTreeMap::new(); let platform_version = sdk.as_ref().version(); - + if let Some(history) = history_result { for (revision, contract) in history { - versions.insert(revision.to_string(), contract.to_json(platform_version)?); + versions.insert(revision.to_string(), contract.to_json(platform_version)?); } } - + let response = DataContractHistoryResponse { versions }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -117,18 +121,20 @@ pub async fn get_data_contracts(sdk: &WasmSdk, ids: Vec) -> Result, _> = ids .iter() - .map(|id| Identifier::from_string( - id, - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - )) + .map(|id| { + Identifier::from_string( + id, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) + }) .collect(); let identifiers = identifiers?; - + // Fetch all contracts let contracts_result: DataContracts = DataContract::fetch_many(sdk.as_ref(), identifiers) .await .map_err(|e| JsError::new(&format!("Failed to fetch data contracts: {}", e)))?; - + // Convert to response format let mut data_contracts = BTreeMap::new(); let platform_version = sdk.as_ref().version(); @@ -140,12 +146,13 @@ pub async fn get_data_contracts(sdk: &WasmSdk, ids: Vec) -> Result) -> Result { +pub async fn get_data_contracts_with_proof_info( + sdk: &WasmSdk, + ids: Vec, +) -> Result { // Parse all contract IDs let identifiers: Result, _> = ids .iter() - .map(|id| Identifier::from_string( - id, - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - )) + .map(|id| { + Identifier::from_string( + id, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) + }) .collect(); let identifiers = identifiers?; - + // Fetch all contracts with proof - let (contracts_result, metadata, proof) = DataContract::fetch_many_with_metadata_and_proof(sdk.as_ref(), identifiers, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch data contracts with proof: {}", e)))?; - + let (contracts_result, metadata, proof) = + DataContract::fetch_many_with_metadata_and_proof(sdk.as_ref(), identifiers, None) + .await + .map_err(|e| { + JsError::new(&format!("Failed to fetch data contracts with proof: {}", e)) + })?; + // Convert to response format let mut data_contracts = BTreeMap::new(); let platform_version = sdk.as_ref().version(); @@ -229,17 +251,18 @@ pub async fn get_data_contracts_with_proof_info(sdk: &WasmSdk, ids: Vec) }; data_contracts.insert(id_str, contract_json); } - + let data = DataContractsResponse { data_contracts }; - + let response = ProofMetadataResponse { data, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) -} \ No newline at end of file +} diff --git a/packages/wasm-sdk/src/queries/document.rs b/packages/wasm-sdk/src/queries/document.rs index d87dd4eda7..a666176bb2 100644 --- a/packages/wasm-sdk/src/queries/document.rs +++ b/packages/wasm-sdk/src/queries/document.rs @@ -1,16 +1,16 @@ -use crate::sdk::WasmSdk; use crate::queries::ProofMetadataResponse; -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::{JsError, JsValue, JsCast}; -use serde::{Serialize, Deserialize}; -use dash_sdk::platform::Fetch; -use dash_sdk::dpp::prelude::Identifier; +use crate::sdk::WasmSdk; +use dash_sdk::dpp::data_contract::accessors::v0::DataContractV0Getters; use dash_sdk::dpp::document::Document; use dash_sdk::dpp::document::DocumentV0Getters; -use dash_sdk::dpp::data_contract::accessors::v0::DataContractV0Getters; +use dash_sdk::dpp::platform_value::{platform_value, Value}; +use dash_sdk::dpp::prelude::Identifier; +use dash_sdk::platform::Fetch; +use drive::query::{OrderClause, WhereClause, WhereOperator}; +use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; -use drive::query::{WhereClause, WhereOperator, OrderClause}; -use dash_sdk::dpp::platform_value::{Value, platform_value}; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::{JsCast, JsError, JsValue}; #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -41,30 +41,36 @@ struct DocumentResponse { impl DocumentResponse { fn from_document( - doc: &Document, + doc: &Document, _data_contract: &dash_sdk::platform::DataContract, - _document_type: dash_sdk::dpp::data_contract::document_type::DocumentTypeRef + _document_type: dash_sdk::dpp::data_contract::document_type::DocumentTypeRef, ) -> Result { use dash_sdk::dpp::document::DocumentV0Getters; - + // For now, we'll continue with the existing approach // In the future, we could use the document type to better interpret the data - + // Get document properties and convert each to JSON let mut data = serde_json::Map::new(); let properties = doc.properties(); - + for (key, value) in properties { // Convert platform Value to JSON - let json_value: JsonValue = value.clone().try_into() + let json_value: JsonValue = value + .clone() + .try_into() .map_err(|e| JsError::new(&format!("Failed to convert value to JSON: {:?}", e)))?; - + data.insert(key.clone(), json_value); } - + let response = Self { - id: doc.id().to_string(dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58), - owner_id: doc.owner_id().to_string(dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58), + id: doc + .id() + .to_string(dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58), + owner_id: doc + .owner_id() + .to_string(dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58), revision: doc.revision().unwrap_or(0), created_at: doc.created_at(), updated_at: doc.updated_at(), @@ -77,27 +83,32 @@ impl DocumentResponse { transferred_at_core_block_height: doc.transferred_at_core_block_height(), data, }; - + Ok(response) } } /// Parse JSON where clause into WhereClause fn parse_where_clause(json_clause: &JsonValue) -> Result { - let clause_array = json_clause.as_array() + let clause_array = json_clause + .as_array() .ok_or_else(|| JsError::new("where clause must be an array"))?; - + if clause_array.len() != 3 { - return Err(JsError::new("where clause must have exactly 3 elements: [field, operator, value]")); + return Err(JsError::new( + "where clause must have exactly 3 elements: [field, operator, value]", + )); } - - let field = clause_array[0].as_str() + + let field = clause_array[0] + .as_str() .ok_or_else(|| JsError::new("where clause field must be a string"))? .to_string(); - - let operator_str = clause_array[1].as_str() + + let operator_str = clause_array[1] + .as_str() .ok_or_else(|| JsError::new("where clause operator must be a string"))?; - + let operator = match operator_str { "==" | "=" => WhereOperator::Equal, ">" => WhereOperator::GreaterThan, @@ -112,10 +123,10 @@ fn parse_where_clause(json_clause: &JsonValue) -> Result { "startsWith" | "StartsWith" => WhereOperator::StartsWith, _ => return Err(JsError::new(&format!("Unknown operator: {}", operator_str))), }; - + // Convert JSON value to platform Value let value = json_to_platform_value(&clause_array[2])?; - + Ok(WhereClause { field, operator, @@ -125,30 +136,32 @@ fn parse_where_clause(json_clause: &JsonValue) -> Result { /// Parse JSON order by clause into OrderClause fn parse_order_clause(json_clause: &JsonValue) -> Result { - let clause_array = json_clause.as_array() + let clause_array = json_clause + .as_array() .ok_or_else(|| JsError::new("order by clause must be an array"))?; - + if clause_array.len() != 2 { - return Err(JsError::new("order by clause must have exactly 2 elements: [field, direction]")); + return Err(JsError::new( + "order by clause must have exactly 2 elements: [field, direction]", + )); } - - let field = clause_array[0].as_str() + + let field = clause_array[0] + .as_str() .ok_or_else(|| JsError::new("order by field must be a string"))? .to_string(); - - let direction = clause_array[1].as_str() + + let direction = clause_array[1] + .as_str() .ok_or_else(|| JsError::new("order by direction must be a string"))?; - + let ascending = match direction { "asc" => true, "desc" => false, _ => return Err(JsError::new("order by direction must be 'asc' or 'desc'")), }; - - Ok(OrderClause { - field, - ascending, - }) + + Ok(OrderClause { field, ascending }) } /// Convert JSON value to platform Value @@ -166,36 +179,37 @@ fn json_to_platform_value(json_val: &JsonValue) -> Result { } else { Err(JsError::new("Unsupported number type")) } - }, + } JsonValue::String(s) => { // Check if it's an identifier (base58 encoded) if s.len() == 44 && s.chars().all(|c| c.is_alphanumeric()) { // Try to parse as identifier - match Identifier::from_string(s, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58) { + match Identifier::from_string( + s, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) { Ok(id) => Ok(platform_value!(id)), Err(_) => Ok(Value::Text(s.clone())), } } else { Ok(Value::Text(s.clone())) } - }, + } JsonValue::Array(arr) => { - let values: Result, JsError> = arr.iter() - .map(json_to_platform_value) - .collect(); + let values: Result, JsError> = + arr.iter().map(json_to_platform_value).collect(); Ok(Value::Array(values?)) - }, + } JsonValue::Object(obj) => { let mut map = Vec::new(); for (key, val) in obj { map.push((Value::Text(key.clone()), json_to_platform_value(val)?)); } Ok(Value::Map(map)) - }, + } } } - #[wasm_bindgen] pub async fn get_documents( sdk: &WasmSdk, @@ -210,29 +224,26 @@ pub async fn get_documents( use dash_sdk::platform::documents::document_query::DocumentQuery; use dash_sdk::platform::FetchMany; use drive_proof_verifier::types::Documents; - + // Parse data contract ID let contract_id = Identifier::from_string( data_contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Create base document query - let mut query = DocumentQuery::new_with_data_contract_id( - sdk.as_ref(), - contract_id, - document_type, - ) - .await - .map_err(|e| JsError::new(&format!("Failed to create document query: {}", e)))?; - + let mut query = + DocumentQuery::new_with_data_contract_id(sdk.as_ref(), contract_id, document_type) + .await + .map_err(|e| JsError::new(&format!("Failed to create document query: {}", e)))?; + // Set limit if provided if let Some(limit_val) = limit { query.limit = limit_val; } else { query.limit = 100; // Default limit } - + // Handle start parameters if let Some(start_after_id) = start_after { let doc_id = Identifier::from_string( @@ -251,64 +262,71 @@ pub async fn get_documents( doc_id.to_vec() )); } - + // Parse and apply where clauses if let Some(where_json) = where_clause { let json_value: JsonValue = serde_json::from_str(&where_json) .map_err(|e| JsError::new(&format!("Failed to parse where clause JSON: {}", e)))?; - + // Expect an array of where clauses - let where_array = json_value.as_array() + let where_array = json_value + .as_array() .ok_or_else(|| JsError::new("where clause must be an array of clauses"))?; - + for clause_json in where_array { let where_clause = parse_where_clause(clause_json)?; query = query.with_where(where_clause); } } - + // Parse and apply order by clauses if let Some(order_json) = order_by { let json_value: JsonValue = serde_json::from_str(&order_json) .map_err(|e| JsError::new(&format!("Failed to parse order by JSON: {}", e)))?; - + // Expect an array of order clauses - let order_array = json_value.as_array() + let order_array = json_value + .as_array() .ok_or_else(|| JsError::new("order by must be an array of clauses"))?; - + for clause_json in order_array { let order_clause = parse_order_clause(clause_json)?; query = query.with_order_by(order_clause); } } - + // Execute query let documents_result: Documents = Document::fetch_many(sdk.as_ref(), query) .await .map_err(|e| JsError::new(&format!("Failed to fetch documents: {}", e)))?; - + // Fetch the data contract to get the document type let data_contract = dash_sdk::platform::DataContract::fetch(sdk.as_ref(), contract_id) .await .map_err(|e| JsError::new(&format!("Failed to fetch data contract: {}", e)))? .ok_or_else(|| JsError::new("Data contract not found"))?; - + // Get the document type let document_type_ref = data_contract .document_type_for_name(document_type) .map_err(|e| JsError::new(&format!("Document type not found: {}", e)))?; - + // Convert documents to response format let mut responses: Vec = Vec::new(); for (_, doc_opt) in documents_result { if let Some(doc) = doc_opt { - responses.push(DocumentResponse::from_document(&doc, &data_contract, document_type_ref)?); + responses.push(DocumentResponse::from_document( + &doc, + &data_contract, + document_type_ref, + )?); } } - + // Use json_compatible serializer to convert maps to objects let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - responses.serialize(&serializer) + responses + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -325,30 +343,26 @@ pub async fn get_documents_with_proof_info( ) -> Result { use dash_sdk::platform::documents::document_query::DocumentQuery; use dash_sdk::platform::FetchMany; - - + // Parse data contract ID let contract_id = Identifier::from_string( data_contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Create base document query - let mut query = DocumentQuery::new_with_data_contract_id( - sdk.as_ref(), - contract_id, - document_type, - ) - .await - .map_err(|e| JsError::new(&format!("Failed to create document query: {}", e)))?; - + let mut query = + DocumentQuery::new_with_data_contract_id(sdk.as_ref(), contract_id, document_type) + .await + .map_err(|e| JsError::new(&format!("Failed to create document query: {}", e)))?; + // Set limit if provided if let Some(limit_val) = limit { query.limit = limit_val; } else { query.limit = 100; // Default limit } - + // Handle start parameters if let Some(start_after_id) = start_after { let doc_id = Identifier::from_string( @@ -367,62 +381,68 @@ pub async fn get_documents_with_proof_info( doc_id.to_vec() )); } - + // Parse and set where clauses if provided if let Some(where_json) = where_clause { let clauses: Vec = serde_json::from_str(&where_json) .map_err(|e| JsError::new(&format!("Invalid where clause JSON: {}", e)))?; - + for clause_json in clauses { let where_clause = parse_where_clause(&clause_json)?; query = query.with_where(where_clause); } } - + // Parse and set order by clauses if provided if let Some(order_json) = order_by { let clauses: Vec = serde_json::from_str(&order_json) .map_err(|e| JsError::new(&format!("Invalid order by JSON: {}", e)))?; - + for clause_json in clauses { let order_clause = parse_order_clause(&clause_json)?; query = query.with_order_by(order_clause); } } - + // Execute query with proof - let (documents_result, metadata, proof) = Document::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch documents: {}", e)))?; - + let (documents_result, metadata, proof) = + Document::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) + .await + .map_err(|e| JsError::new(&format!("Failed to fetch documents: {}", e)))?; + // Fetch the data contract to get the document type let data_contract = dash_sdk::platform::DataContract::fetch(sdk.as_ref(), contract_id) .await .map_err(|e| JsError::new(&format!("Failed to fetch data contract: {}", e)))? .ok_or_else(|| JsError::new("Data contract not found"))?; - + // Get the document type let document_type_ref = data_contract .document_type_for_name(document_type) .map_err(|e| JsError::new(&format!("Document type not found: {}", e)))?; - + // Convert documents to response format let mut responses: Vec = Vec::new(); for (_, doc_opt) in documents_result { if let Some(doc) = doc_opt { - responses.push(DocumentResponse::from_document(&doc, &data_contract, document_type_ref)?); + responses.push(DocumentResponse::from_document( + &doc, + &data_contract, + document_type_ref, + )?); } } - + let response = ProofMetadataResponse { data: responses, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -434,53 +454,50 @@ pub async fn get_document( document_id: &str, ) -> Result { use dash_sdk::platform::documents::document_query::DocumentQuery; - + // Parse IDs let contract_id = Identifier::from_string( data_contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + let doc_id = Identifier::from_string( document_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Create document query - let query = DocumentQuery::new_with_data_contract_id( - sdk.as_ref(), - contract_id, - document_type, - ) - .await - .map_err(|e| JsError::new(&format!("Failed to create document query: {}", e)))? - .with_document_id(&doc_id); - + let query = DocumentQuery::new_with_data_contract_id(sdk.as_ref(), contract_id, document_type) + .await + .map_err(|e| JsError::new(&format!("Failed to create document query: {}", e)))? + .with_document_id(&doc_id); + // Fetch the data contract to get the document type let data_contract = dash_sdk::platform::DataContract::fetch(sdk.as_ref(), contract_id) .await .map_err(|e| JsError::new(&format!("Failed to fetch data contract: {}", e)))? .ok_or_else(|| JsError::new("Data contract not found"))?; - + // Get the document type let document_type = data_contract .document_type_for_name(document_type) .map_err(|e| JsError::new(&format!("Document type not found: {}", e)))?; - + // Execute query let document_result: Option = Document::fetch(sdk.as_ref(), query) .await .map_err(|e| JsError::new(&format!("Failed to fetch document: {}", e)))?; - + match document_result { Some(doc) => { let response = DocumentResponse::from_document(&doc, &data_contract, document_type)?; - + // Use json_compatible serializer to convert maps to objects let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) - }, + } None => Ok(JsValue::NULL), } } @@ -493,59 +510,58 @@ pub async fn get_document_with_proof_info( document_id: &str, ) -> Result { use dash_sdk::platform::documents::document_query::DocumentQuery; - + // Parse IDs let contract_id = Identifier::from_string( data_contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + let doc_id = Identifier::from_string( document_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Create document query - let query = DocumentQuery::new_with_data_contract_id( - sdk.as_ref(), - contract_id, - document_type, - ) - .await - .map_err(|e| JsError::new(&format!("Failed to create document query: {}", e)))? - .with_document_id(&doc_id); - + let query = DocumentQuery::new_with_data_contract_id(sdk.as_ref(), contract_id, document_type) + .await + .map_err(|e| JsError::new(&format!("Failed to create document query: {}", e)))? + .with_document_id(&doc_id); + // Fetch the data contract to get the document type let data_contract = dash_sdk::platform::DataContract::fetch(sdk.as_ref(), contract_id) .await .map_err(|e| JsError::new(&format!("Failed to fetch data contract: {}", e)))? .ok_or_else(|| JsError::new("Data contract not found"))?; - + // Get the document type let document_type_ref = data_contract .document_type_for_name(document_type) .map_err(|e| JsError::new(&format!("Document type not found: {}", e)))?; - + // Execute query with proof - let (document_result, metadata, proof) = Document::fetch_with_metadata_and_proof(sdk.as_ref(), query, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch document: {}", e)))?; - + let (document_result, metadata, proof) = + Document::fetch_with_metadata_and_proof(sdk.as_ref(), query, None) + .await + .map_err(|e| JsError::new(&format!("Failed to fetch document: {}", e)))?; + match document_result { Some(doc) => { - let doc_response = DocumentResponse::from_document(&doc, &data_contract, document_type_ref)?; - + let doc_response = + DocumentResponse::from_document(&doc, &data_contract, document_type_ref)?; + let response = ProofMetadataResponse { data: doc_response, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer - let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + let serializer = serde_wasm_bindgen::Serializer::json_compatible(); + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) - }, + } None => { // Return null data with proof let response = ProofMetadataResponse { @@ -553,10 +569,11 @@ pub async fn get_document_with_proof_info( metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer - let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + let serializer = serde_wasm_bindgen::Serializer::json_compatible(); + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } } @@ -571,61 +588,58 @@ pub async fn get_dpns_usernames( use dash_sdk::platform::documents::document_query::DocumentQuery; use dash_sdk::platform::FetchMany; use drive_proof_verifier::types::Documents; - + // DPNS contract ID on testnet const DPNS_CONTRACT_ID: &str = "GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec"; const DPNS_DOCUMENT_TYPE: &str = "domain"; - + // Parse identity ID let identity_id_parsed = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Parse DPNS contract ID let contract_id = Identifier::from_string( DPNS_CONTRACT_ID, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Create document query for DPNS domains owned by this identity - let mut query = DocumentQuery::new_with_data_contract_id( - sdk.as_ref(), - contract_id, - DPNS_DOCUMENT_TYPE, - ) - .await - .map_err(|e| JsError::new(&format!("Failed to create document query: {}", e)))?; - + let mut query = + DocumentQuery::new_with_data_contract_id(sdk.as_ref(), contract_id, DPNS_DOCUMENT_TYPE) + .await + .map_err(|e| JsError::new(&format!("Failed to create document query: {}", e)))?; + // Query by records.identity using the identityId index let where_clause = WhereClause { field: "records.identity".to_string(), operator: WhereOperator::Equal, value: Value::Identifier(identity_id_parsed.to_buffer()), }; - + query = query.with_where(where_clause); - + // Set limit from parameter or default to 10 query.limit = limit.unwrap_or(10); - + // Execute query let documents_result: Documents = Document::fetch_many(sdk.as_ref(), query) .await .map_err(|e| JsError::new(&format!("Failed to fetch DPNS documents: {}", e)))?; - + // Collect all usernames let mut usernames: Vec = Vec::new(); - + // Process all results for (_, doc_opt) in documents_result { if let Some(doc) = doc_opt { // Extract the username from the document let properties = doc.properties(); - + if let (Some(Value::Text(label)), Some(Value::Text(parent_domain))) = ( properties.get("label"), - properties.get("normalizedParentDomainName") + properties.get("normalizedParentDomainName"), ) { // Construct the full username let username = format!("{}.{}", label, parent_domain); @@ -633,29 +647,27 @@ pub async fn get_dpns_usernames( } } } - + // Return usernames as a JSON array let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - usernames.serialize(&serializer) + usernames + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize usernames: {}", e))) } // Keep the old function for backward compatibility but have it call the new one #[wasm_bindgen] -pub async fn get_dpns_username( - sdk: &WasmSdk, - identity_id: &str, -) -> Result { +pub async fn get_dpns_username(sdk: &WasmSdk, identity_id: &str) -> Result { // Call the new function with limit 1 let result = get_dpns_usernames(sdk, identity_id, Some(1)).await?; - + // Extract the first username from the array if let Some(array) = result.dyn_ref::() { if array.length() > 0 { return Ok(array.get(0)); } } - + Ok(JsValue::NULL) } @@ -669,62 +681,61 @@ pub async fn get_dpns_usernames_with_proof_info( ) -> Result { use dash_sdk::platform::documents::document_query::DocumentQuery; use dash_sdk::platform::FetchMany; - - + // DPNS contract ID on testnet const DPNS_CONTRACT_ID: &str = "GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec"; const DPNS_DOCUMENT_TYPE: &str = "domain"; - + // Parse identity ID let identity_id_parsed = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Parse DPNS contract ID let contract_id = Identifier::from_string( DPNS_CONTRACT_ID, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Create document query for DPNS domains owned by this identity - let mut query = DocumentQuery::new_with_data_contract_id( - sdk.as_ref(), - contract_id, - DPNS_DOCUMENT_TYPE, - ) - .await - .map_err(|e| JsError::new(&format!("Failed to create document query: {}", e)))?; - + let mut query = + DocumentQuery::new_with_data_contract_id(sdk.as_ref(), contract_id, DPNS_DOCUMENT_TYPE) + .await + .map_err(|e| JsError::new(&format!("Failed to create document query: {}", e)))?; + // Query by records.identity using the identityId index let where_clause = WhereClause { field: "records.identity".to_string(), operator: WhereOperator::Equal, value: Value::Identifier(identity_id_parsed.to_buffer()), }; - + query = query.with_where(where_clause); - + // Set limit from parameter or default to 10 query.limit = limit.unwrap_or(10); - + // Execute query with proof - let (documents_result, metadata, proof) = Document::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch DPNS documents with proof: {}", e)))?; - + let (documents_result, metadata, proof) = + Document::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) + .await + .map_err(|e| { + JsError::new(&format!("Failed to fetch DPNS documents with proof: {}", e)) + })?; + // Collect all usernames let mut usernames: Vec = Vec::new(); - + // Process all results for (_, doc_opt) in documents_result { if let Some(doc) = doc_opt { // Extract the username from the document let properties = doc.properties(); - + if let (Some(Value::Text(label)), Some(Value::Text(parent_domain))) = ( properties.get("label"), - properties.get("normalizedParentDomainName") + properties.get("normalizedParentDomainName"), ) { // Construct the full username let username = format!("{}.{}", label, parent_domain); @@ -732,16 +743,17 @@ pub async fn get_dpns_usernames_with_proof_info( } } } - + let response = ProofMetadataResponse { data: usernames, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -752,26 +764,26 @@ pub async fn get_dpns_username_with_proof_info( ) -> Result { // Call the new function with limit 1 let result = get_dpns_usernames_with_proof_info(sdk, identity_id, Some(1)).await?; - + // The result already contains proof info, just modify the data field // Parse the result to extract first username let result_obj: serde_json::Value = serde_wasm_bindgen::from_value(result.clone())?; - + if let Some(data_array) = result_obj.get("data").and_then(|d| d.as_array()) { if let Some(first_username) = data_array.first() { // Create a new response with just the first username let mut modified_result = result_obj.clone(); modified_result["data"] = first_username.clone(); - + return serde_wasm_bindgen::to_value(&modified_result) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))); } } - + // If no username found, return null data with proof info let mut modified_result = result_obj.clone(); modified_result["data"] = serde_json::Value::Null; - + serde_wasm_bindgen::to_value(&modified_result) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) -} \ No newline at end of file +} diff --git a/packages/wasm-sdk/src/queries/dpns.rs b/packages/wasm-sdk/src/queries/dpns.rs index 703479341c..16a98a3923 100644 --- a/packages/wasm-sdk/src/queries/dpns.rs +++ b/packages/wasm-sdk/src/queries/dpns.rs @@ -1,11 +1,11 @@ -use crate::sdk::WasmSdk; use crate::queries::ProofMetadataResponse; +use crate::sdk::WasmSdk; +use dash_sdk::dpp::document::DocumentV0Getters; +use dash_sdk::dpp::platform_value::string_encoding::Encoding; +use dash_sdk::platform::{Document, FetchMany}; +use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsError, JsValue}; -use serde::{Serialize, Deserialize}; -use dash_sdk::platform::{FetchMany, Document}; -use dash_sdk::dpp::platform_value::string_encoding::Encoding; -use dash_sdk::dpp::document::DocumentV0Getters; #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -16,63 +16,57 @@ struct DpnsUsernameInfo { } #[wasm_bindgen] -pub async fn get_dpns_username_by_name( - sdk: &WasmSdk, - username: &str, -) -> Result { +pub async fn get_dpns_username_by_name(sdk: &WasmSdk, username: &str) -> Result { + use dash_sdk::dpp::platform_value::Value; use dash_sdk::platform::documents::document_query::DocumentQuery; use drive::query::{WhereClause, WhereOperator}; - use dash_sdk::dpp::platform_value::Value; - + // DPNS contract ID on testnet const DPNS_CONTRACT_ID: &str = "GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec"; const DPNS_DOCUMENT_TYPE: &str = "domain"; - + // Parse username into label and domain let parts: Vec<&str> = username.split('.').collect(); if parts.len() != 2 { - return Err(JsError::new("Invalid username format. Expected format: label.dash")); + return Err(JsError::new( + "Invalid username format. Expected format: label.dash", + )); } let label = parts[0]; let domain = parts[1]; - + // Parse DPNS contract ID - let contract_id = dash_sdk::dpp::prelude::Identifier::from_string( - DPNS_CONTRACT_ID, - Encoding::Base58, - )?; - + let contract_id = + dash_sdk::dpp::prelude::Identifier::from_string(DPNS_CONTRACT_ID, Encoding::Base58)?; + // Create document query - let mut query = DocumentQuery::new_with_data_contract_id( - sdk.as_ref(), - contract_id, - DPNS_DOCUMENT_TYPE, - ) - .await - .map_err(|e| JsError::new(&format!("Failed to create document query: {}", e)))?; - + let mut query = + DocumentQuery::new_with_data_contract_id(sdk.as_ref(), contract_id, DPNS_DOCUMENT_TYPE) + .await + .map_err(|e| JsError::new(&format!("Failed to create document query: {}", e)))?; + // Query by label and normalizedParentDomainName query = query.with_where(WhereClause { field: "normalizedLabel".to_string(), operator: WhereOperator::Equal, value: Value::Text(label.to_lowercase()), }); - + query = query.with_where(WhereClause { field: "normalizedParentDomainName".to_string(), operator: WhereOperator::Equal, value: Value::Text(domain.to_lowercase()), }); - + let documents = Document::fetch_many(sdk.as_ref(), query).await?; - + if let Some((_, Some(document))) = documents.into_iter().next() { let result = DpnsUsernameInfo { username: username.to_string(), identity_id: document.owner_id().to_string(Encoding::Base58), document_id: document.id().to_string(Encoding::Base58), }; - + serde_wasm_bindgen::to_value(&result) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } else { @@ -85,71 +79,69 @@ pub async fn get_dpns_username_by_name_with_proof_info( sdk: &WasmSdk, username: &str, ) -> Result { + use dash_sdk::dpp::platform_value::Value; use dash_sdk::platform::documents::document_query::DocumentQuery; use drive::query::{WhereClause, WhereOperator}; - use dash_sdk::dpp::platform_value::Value; - + // DPNS contract ID on testnet const DPNS_CONTRACT_ID: &str = "GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec"; const DPNS_DOCUMENT_TYPE: &str = "domain"; - + // Parse username into label and domain let parts: Vec<&str> = username.split('.').collect(); if parts.len() != 2 { - return Err(JsError::new("Invalid username format. Expected format: label.dash")); + return Err(JsError::new( + "Invalid username format. Expected format: label.dash", + )); } let label = parts[0]; let domain = parts[1]; - + // Parse DPNS contract ID - let contract_id = dash_sdk::dpp::prelude::Identifier::from_string( - DPNS_CONTRACT_ID, - Encoding::Base58, - )?; - + let contract_id = + dash_sdk::dpp::prelude::Identifier::from_string(DPNS_CONTRACT_ID, Encoding::Base58)?; + // Create document query - let mut query = DocumentQuery::new_with_data_contract_id( - sdk.as_ref(), - contract_id, - DPNS_DOCUMENT_TYPE, - ) - .await - .map_err(|e| JsError::new(&format!("Failed to create document query: {}", e)))?; - + let mut query = + DocumentQuery::new_with_data_contract_id(sdk.as_ref(), contract_id, DPNS_DOCUMENT_TYPE) + .await + .map_err(|e| JsError::new(&format!("Failed to create document query: {}", e)))?; + // Query by label and normalizedParentDomainName query = query.with_where(WhereClause { field: "normalizedLabel".to_string(), operator: WhereOperator::Equal, value: Value::Text(label.to_lowercase()), }); - + query = query.with_where(WhereClause { field: "normalizedParentDomainName".to_string(), operator: WhereOperator::Equal, value: Value::Text(domain.to_lowercase()), }); - - let (documents, metadata, proof) = Document::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None).await?; - + + let (documents, metadata, proof) = + Document::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None).await?; + if let Some((_, Some(document))) = documents.into_iter().next() { let result = DpnsUsernameInfo { username: username.to_string(), identity_id: document.owner_id().to_string(Encoding::Base58), document_id: document.id().to_string(Encoding::Base58), }; - + let response = ProofMetadataResponse { data: result, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer - let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + let serializer = serde_wasm_bindgen::Serializer::json_compatible(); + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } else { Err(JsError::new(&format!("Username '{}' not found", username))) } } - diff --git a/packages/wasm-sdk/src/queries/epoch.rs b/packages/wasm-sdk/src/queries/epoch.rs index 82d8c89f51..14ba731c94 100644 --- a/packages/wasm-sdk/src/queries/epoch.rs +++ b/packages/wasm-sdk/src/queries/epoch.rs @@ -1,16 +1,16 @@ -use crate::sdk::WasmSdk; use crate::queries::ProofMetadataResponse; -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::{JsError, JsValue}; -use serde::{Serialize, Deserialize}; -use dash_sdk::platform::{FetchMany, LimitQuery}; -use dash_sdk::platform::fetch_current_no_parameters::FetchCurrent; -use dash_sdk::dpp::block::extended_epoch_info::ExtendedEpochInfo; +use crate::sdk::WasmSdk; use dash_sdk::dpp::block::extended_epoch_info::v0::ExtendedEpochInfoV0Getters; +use dash_sdk::dpp::block::extended_epoch_info::ExtendedEpochInfo; use dash_sdk::dpp::dashcore::hashes::Hash; use dash_sdk::dpp::dashcore::ProTxHash; +use dash_sdk::platform::fetch_current_no_parameters::FetchCurrent; +use dash_sdk::platform::{FetchMany, LimitQuery}; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use std::str::FromStr; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::{JsError, JsValue}; #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -50,7 +50,7 @@ pub async fn get_epochs_info( ascending: Option, ) -> Result { use dash_sdk::platform::types::epoch::EpochQuery; - + let query = LimitQuery { query: EpochQuery { start: start_epoch, @@ -59,17 +59,18 @@ pub async fn get_epochs_info( limit: count, start_info: None, }; - - let epochs_result: drive_proof_verifier::types::ExtendedEpochInfos = ExtendedEpochInfo::fetch_many(sdk.as_ref(), query) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch epochs info: {}", e)))?; - + + let epochs_result: drive_proof_verifier::types::ExtendedEpochInfos = + ExtendedEpochInfo::fetch_many(sdk.as_ref(), query) + .await + .map_err(|e| JsError::new(&format!("Failed to fetch epochs info: {}", e)))?; + // Convert to our response format let epochs: Vec = epochs_result .into_iter() .filter_map(|(_, epoch_opt)| epoch_opt.map(Into::into)) .collect(); - + serde_wasm_bindgen::to_value(&epochs) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -82,25 +83,27 @@ pub async fn get_finalized_epoch_infos( ascending: Option, ) -> Result { use dash_sdk::platform::types::finalized_epoch::FinalizedEpochQuery; - + if start_epoch.is_none() { - return Err(JsError::new("start_epoch is required for finalized epoch queries")); + return Err(JsError::new( + "start_epoch is required for finalized epoch queries", + )); } - + let start = start_epoch.unwrap(); let is_ascending = ascending.unwrap_or(true); let limit = count.unwrap_or(100); - + // Ensure limit is at least 1 to avoid underflow let limit = limit.max(1); - + // Calculate end epoch based on direction and limit let end_epoch = if is_ascending { start.saturating_add((limit - 1) as u16) } else { start.saturating_sub((limit - 1) as u16) }; - + let query = if is_ascending { FinalizedEpochQuery { start_epoch_index: start, @@ -116,11 +119,15 @@ pub async fn get_finalized_epoch_infos( end_epoch_index_included: true, } }; - - let epochs_result: drive_proof_verifier::types::FinalizedEpochInfos = dash_sdk::dpp::block::finalized_epoch_info::FinalizedEpochInfo::fetch_many(sdk.as_ref(), query) + + let epochs_result: drive_proof_verifier::types::FinalizedEpochInfos = + dash_sdk::dpp::block::finalized_epoch_info::FinalizedEpochInfo::fetch_many( + sdk.as_ref(), + query, + ) .await .map_err(|e| JsError::new(&format!("Failed to fetch finalized epochs info: {}", e)))?; - + // Convert to our response format and sort by epoch index let mut epochs: Vec = epochs_result .into_iter() @@ -138,7 +145,7 @@ pub async fn get_finalized_epoch_infos( }) }) .collect(); - + // Sort based on ascending flag epochs.sort_by(|a, b| { if is_ascending { @@ -147,7 +154,7 @@ pub async fn get_finalized_epoch_infos( b.index.cmp(&a.index) } }); - + serde_wasm_bindgen::to_value(&epochs) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -166,7 +173,7 @@ pub async fn get_evonodes_proposed_epoch_blocks_by_ids( ids: Vec, ) -> Result { use drive_proof_verifier::types::ProposerBlockCountById; - + // Parse the ProTxHash strings let pro_tx_hashes: Vec = ids .into_iter() @@ -175,12 +182,12 @@ pub async fn get_evonodes_proposed_epoch_blocks_by_ids( .map_err(|e| JsError::new(&format!("Invalid ProTxHash '{}': {}", hash_str, e))) }) .collect::, _>>()?; - + // Use FetchMany to get block counts for specific IDs let counts = ProposerBlockCountById::fetch_many(sdk.as_ref(), (epoch, pro_tx_hashes)) .await .map_err(|e| JsError::new(&format!("Failed to fetch evonode proposed blocks: {}", e)))?; - + // Convert to response format let mut evonodes_proposed_block_counts = BTreeMap::new(); for (identifier, count) in counts.0 { @@ -192,11 +199,11 @@ pub async fn get_evonodes_proposed_epoch_blocks_by_ids( evonodes_proposed_block_counts.insert(pro_tx_hash.to_string(), count); } } - + let response = EvonodesProposedBlocksResponse { evonodes_proposed_block_counts, }; - + serde_wasm_bindgen::to_value(&response) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -210,9 +217,9 @@ pub async fn get_evonodes_proposed_epoch_blocks_by_range( order_ascending: Option, ) -> Result { use dash_sdk::platform::types::proposed_blocks::ProposedBlockCountEx; + use dash_sdk::platform::QueryStartInfo; use drive_proof_verifier::types::ProposerBlockCounts; - use dash_sdk::platform::QueryStartInfo; - + // Parse start_after if provided let start_info = if let Some(start) = start_after { let pro_tx_hash = ProTxHash::from_str(&start) @@ -224,7 +231,7 @@ pub async fn get_evonodes_proposed_epoch_blocks_by_range( } else { None }; - + let counts_result = ProposerBlockCounts::fetch_proposed_blocks_by_range( sdk.as_ref(), Some(epoch), @@ -232,10 +239,16 @@ pub async fn get_evonodes_proposed_epoch_blocks_by_range( start_info, ) .await - .map_err(|e| JsError::new(&format!("Failed to fetch evonode proposed blocks by range: {}", e)))?; - + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch evonode proposed blocks by range: {}", + e + )) + })?; + // Convert to response format - let mut responses: Vec = counts_result.0 + let mut responses: Vec = counts_result + .0 .into_iter() .map(|(identifier, count)| { // Convert Identifier back to ProTxHash @@ -248,7 +261,7 @@ pub async fn get_evonodes_proposed_epoch_blocks_by_range( } }) .collect(); - + // Sort based on order_ascending (default is true) let ascending = order_ascending.unwrap_or(true); responses.sort_by(|a, b| { @@ -258,7 +271,7 @@ pub async fn get_evonodes_proposed_epoch_blocks_by_range( b.proposer_pro_tx_hash.cmp(&a.proposer_pro_tx_hash) } }); - + serde_wasm_bindgen::to_value(&responses) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -268,9 +281,9 @@ pub async fn get_current_epoch(sdk: &WasmSdk) -> Result { let epoch = ExtendedEpochInfo::fetch_current(sdk.as_ref()) .await .map_err(|e| JsError::new(&format!("Failed to fetch current epoch: {}", e)))?; - + let epoch_info = EpochInfo::from(epoch); - + serde_wasm_bindgen::to_value(&epoch_info) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -283,7 +296,7 @@ pub async fn get_epochs_info_with_proof_info( ascending: Option, ) -> Result { use dash_sdk::platform::types::epoch::EpochQuery; - + let query = LimitQuery { query: EpochQuery { start: start_epoch, @@ -292,46 +305,52 @@ pub async fn get_epochs_info_with_proof_info( limit: count, start_info: None, }; - - let (epochs_result, metadata, proof) = ExtendedEpochInfo::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch epochs info with proof: {}", e)))?; - + + let (epochs_result, metadata, proof) = + ExtendedEpochInfo::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) + .await + .map_err(|e| JsError::new(&format!("Failed to fetch epochs info with proof: {}", e)))?; + // Convert to our response format let epochs: Vec = epochs_result .into_iter() .filter_map(|(_, epoch_opt)| epoch_opt.map(Into::into)) .collect(); - + let response = ProofMetadataResponse { data: epochs, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } #[wasm_bindgen] pub async fn get_current_epoch_with_proof_info(sdk: &WasmSdk) -> Result { - let (epoch, metadata, proof) = ExtendedEpochInfo::fetch_current_with_metadata_and_proof(sdk.as_ref()) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch current epoch with proof: {}", e)))?; - + let (epoch, metadata, proof) = + ExtendedEpochInfo::fetch_current_with_metadata_and_proof(sdk.as_ref()) + .await + .map_err(|e| { + JsError::new(&format!("Failed to fetch current epoch with proof: {}", e)) + })?; + let epoch_info = EpochInfo::from(epoch); - + let response = ProofMetadataResponse { data: epoch_info, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -345,26 +364,27 @@ pub async fn get_finalized_epoch_infos_with_proof_info( ascending: Option, ) -> Result { use dash_sdk::platform::types::finalized_epoch::FinalizedEpochQuery; - - + if start_epoch.is_none() { - return Err(JsError::new("start_epoch is required for finalized epoch queries")); + return Err(JsError::new( + "start_epoch is required for finalized epoch queries", + )); } - + let start = start_epoch.unwrap(); let is_ascending = ascending.unwrap_or(true); let limit = count.unwrap_or(100); - + // Ensure limit is at least 1 to avoid underflow let limit = limit.max(1); - + // Calculate end epoch based on direction and limit let end_epoch = if is_ascending { start.saturating_add((limit - 1) as u16) } else { start.saturating_sub((limit - 1) as u16) }; - + let query = if is_ascending { FinalizedEpochQuery { start_epoch_index: start, @@ -380,11 +400,11 @@ pub async fn get_finalized_epoch_infos_with_proof_info( end_epoch_index_included: true, } }; - + let (epochs_result, metadata, proof) = dash_sdk::dpp::block::finalized_epoch_info::FinalizedEpochInfo::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) .await .map_err(|e| JsError::new(&format!("Failed to fetch finalized epochs info with proof: {}", e)))?; - + // Convert to our response format and sort by epoch index let mut epochs: Vec = epochs_result .into_iter() @@ -402,7 +422,7 @@ pub async fn get_finalized_epoch_infos_with_proof_info( }) }) .collect(); - + // Sort based on ascending flag epochs.sort_by(|a, b| { if is_ascending { @@ -411,16 +431,17 @@ pub async fn get_finalized_epoch_infos_with_proof_info( b.index.cmp(&a.index) } }); - + let response = ProofMetadataResponse { data: epochs, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -433,7 +454,9 @@ pub async fn get_evonodes_proposed_epoch_blocks_by_ids_with_proof_info( // TODO: Implement once SDK Query trait is implemented for ProposerBlockCountById // Currently not supported due to query format issues let _ = (sdk, epoch, pro_tx_hashes); // Parameters will be used when implemented - Err(JsError::new("get_evonodes_proposed_epoch_blocks_by_ids_with_proof_info is not yet implemented")) + Err(JsError::new( + "get_evonodes_proposed_epoch_blocks_by_ids_with_proof_info is not yet implemented", + )) } #[wasm_bindgen] @@ -447,5 +470,7 @@ pub async fn get_evonodes_proposed_epoch_blocks_by_range_with_proof_info( // TODO: Implement once SDK Query trait is implemented for ProposerBlockCountByRange // Currently not supported due to query format issues let _ = (sdk, epoch, limit, start_after, order_ascending); // Parameters will be used when implemented - Err(JsError::new("get_evonodes_proposed_epoch_blocks_by_range_with_proof_info is not yet implemented")) -} \ No newline at end of file + Err(JsError::new( + "get_evonodes_proposed_epoch_blocks_by_range_with_proof_info is not yet implemented", + )) +} diff --git a/packages/wasm-sdk/src/queries/group.rs b/packages/wasm-sdk/src/queries/group.rs index 8c88f0e822..df9d276d51 100644 --- a/packages/wasm-sdk/src/queries/group.rs +++ b/packages/wasm-sdk/src/queries/group.rs @@ -1,17 +1,19 @@ +use crate::queries::{ProofInfo, ProofMetadataResponse, ResponseMetadata}; use crate::sdk::WasmSdk; -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::{JsError, JsValue}; -use serde::{Serialize, Deserialize}; -use crate::queries::{ProofMetadataResponse, ResponseMetadata, ProofInfo}; -use dash_sdk::platform::{Fetch, FetchMany, Identifier}; +use dash_sdk::dpp::data_contract::group::accessors::v0::GroupV0Getters; use dash_sdk::dpp::data_contract::group::Group; +use dash_sdk::dpp::data_contract::group::GroupMemberPower; use dash_sdk::dpp::data_contract::GroupContractPosition; -use dash_sdk::dpp::data_contract::group::accessors::v0::GroupV0Getters; -use dash_sdk::platform::group_actions::{GroupQuery, GroupInfosQuery, GroupActionsQuery, GroupActionSignersQuery}; use dash_sdk::dpp::group::group_action::GroupAction; use dash_sdk::dpp::group::group_action_status::GroupActionStatus; -use dash_sdk::dpp::data_contract::group::GroupMemberPower; +use dash_sdk::platform::group_actions::{ + GroupActionSignersQuery, GroupActionsQuery, GroupInfosQuery, GroupQuery, +}; +use dash_sdk::platform::{Fetch, FetchMany, Identifier}; +use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::{JsError, JsValue}; // Proof info functions are now included below @@ -24,13 +26,17 @@ pub struct GroupInfoResponse { impl GroupInfoResponse { fn from_group(group: &Group) -> Self { - let members = group.members() + let members = group + .members() .iter() .map(|(id, power)| { - (id.to_string(dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58), *power) + ( + id.to_string(dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58), + *power, + ) }) .collect(); - + Self { members, required_power: group.required_power(), @@ -49,27 +55,28 @@ pub async fn get_group_info( data_contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Create group query let query = GroupQuery { contract_id, group_contract_position: group_contract_position as GroupContractPosition, }; - + // Fetch the group let group_result: Option = Group::fetch(sdk.as_ref(), query) .await .map_err(|e| JsError::new(&format!("Failed to fetch group: {}", e)))?; - + match group_result { Some(group) => { let response = GroupInfoResponse::from_group(&group); - + // Use json_compatible serializer to convert maps to objects let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) - }, + } None => Ok(JsValue::NULL), } } @@ -95,37 +102,41 @@ pub async fn get_group_members( data_contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Create group query let query = GroupQuery { contract_id, group_contract_position: group_contract_position as GroupContractPosition, }; - + // Fetch the group let group_result: Option = Group::fetch(sdk.as_ref(), query) .await .map_err(|e| JsError::new(&format!("Failed to fetch group: {}", e)))?; - + match group_result { Some(group) => { let mut members: Vec = Vec::new(); - + // If specific member IDs are requested, filter by them if let Some(requested_ids) = member_ids { let requested_identifiers: Result, _> = requested_ids .iter() - .map(|id| Identifier::from_string( - id, - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - )) + .map(|id| { + Identifier::from_string( + id, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) + }) .collect(); let requested_identifiers = requested_identifiers?; - + for id in requested_identifiers { if let Ok(power) = group.member_power(id) { members.push(GroupMember { - member_id: id.to_string(dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58), + member_id: id.to_string( + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ), power, }); } @@ -135,38 +146,44 @@ pub async fn get_group_members( let all_members = group.members(); let mut sorted_members: Vec<_> = all_members.iter().collect(); sorted_members.sort_by_key(|(id, _)| *id); - + // Apply start_at if provided let start_index = if let Some(start_id) = start_at { let start_identifier = Identifier::from_string( &start_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - sorted_members.iter().position(|(id, _)| **id > start_identifier).unwrap_or(sorted_members.len()) + sorted_members + .iter() + .position(|(id, _)| **id > start_identifier) + .unwrap_or(sorted_members.len()) } else { 0 }; - + // Apply limit let end_index = if let Some(lim) = limit { (start_index + lim as usize).min(sorted_members.len()) } else { sorted_members.len() }; - + for (id, power) in &sorted_members[start_index..end_index] { members.push(GroupMember { - member_id: (*id).to_string(dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58), + member_id: (*id).to_string( + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ), power: **power, }); } } - + // Use json_compatible serializer to convert response let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - members.serialize(&serializer) + members + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) - }, + } None => Ok(JsValue::NULL), } } @@ -176,7 +193,7 @@ pub async fn get_group_members( struct IdentityGroupInfo { data_contract_id: String, group_contract_position: u32, - role: String, // "member", "owner", or "moderator" + role: String, // "member", "owner", or "moderator" power: Option, // Only for members } @@ -193,9 +210,9 @@ pub async fn get_identity_groups( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + let mut groups: Vec = Vec::new(); - + // Check member data contracts if let Some(contracts) = member_data_contracts { for contract_id_str in contracts { @@ -203,18 +220,18 @@ pub async fn get_identity_groups( &contract_id_str, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Fetch all groups for this contract let query = GroupInfosQuery { contract_id, start_group_contract_position: None, limit: None, }; - + let groups_result = Group::fetch_many(sdk.as_ref(), query) .await .map_err(|e| JsError::new(&format!("Failed to fetch groups: {}", e)))?; - + // Check each group for the identity for (position, group_opt) in groups_result { if let Some(group) = group_opt { @@ -230,18 +247,19 @@ pub async fn get_identity_groups( } } } - + // Note: Owner and moderator roles would require additional contract queries // which are not yet implemented in the SDK. For now, return a warning. if owner_data_contracts.is_some() || moderator_data_contracts.is_some() { web_sys::console::warn_1(&JsValue::from_str( - "Warning: Owner and moderator role queries are not yet implemented" + "Warning: Owner and moderator role queries are not yet implemented", )); } - + // Use json_compatible serializer to convert response let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - groups.serialize(&serializer) + groups + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -271,34 +289,38 @@ pub async fn get_group_infos( contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Parse start at info if provided - let start_group_contract_position = if !start_at_info.is_null() && !start_at_info.is_undefined() { + let start_group_contract_position = if !start_at_info.is_null() && !start_at_info.is_undefined() + { let info = serde_wasm_bindgen::from_value::(start_at_info); match info { Ok(json) => { - let position = json["position"].as_u64().ok_or_else(|| JsError::new("Invalid start position"))? as u32; + let position = json["position"] + .as_u64() + .ok_or_else(|| JsError::new("Invalid start position"))? + as u32; let included = json["included"].as_bool().unwrap_or(false); Some((position as GroupContractPosition, included)) } - Err(_) => None + Err(_) => None, } } else { None }; - + // Create query let query = GroupInfosQuery { contract_id, start_group_contract_position, limit: count.map(|c| c as u16), }; - + // Fetch groups let groups_result = Group::fetch_many(sdk.as_ref(), query) .await .map_err(|e| JsError::new(&format!("Failed to fetch groups: {}", e)))?; - + // Convert result to response format let mut group_infos = Vec::new(); for (position, group_opt) in groups_result { @@ -312,7 +334,7 @@ pub async fn get_group_infos( }) }) .collect(); - + group_infos.push(serde_json::json!({ "groupContractPosition": position, "members": members, @@ -320,14 +342,15 @@ pub async fn get_group_infos( })); } } - + let response = serde_json::json!({ "groupInfos": group_infos }); - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -345,35 +368,42 @@ pub async fn get_group_actions( contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Parse status let status = match status { "ACTIVE" => GroupActionStatus::ActionActive, "CLOSED" => GroupActionStatus::ActionClosed, - _ => return Err(JsError::new(&format!("Invalid status: {}. Must be ACTIVE or CLOSED", status))), + _ => { + return Err(JsError::new(&format!( + "Invalid status: {}. Must be ACTIVE or CLOSED", + status + ))) + } }; - + // Parse start action ID if provided let start_at_action_id = if !start_at_info.is_null() && !start_at_info.is_undefined() { let info = serde_wasm_bindgen::from_value::(start_at_info); match info { Ok(json) => { - let action_id = json["actionId"].as_str().ok_or_else(|| JsError::new("Invalid action ID"))?; + let action_id = json["actionId"] + .as_str() + .ok_or_else(|| JsError::new("Invalid action ID"))?; let included = json["included"].as_bool().unwrap_or(false); Some(( Identifier::from_string( action_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?, - included + included, )) } - Err(_) => None + Err(_) => None, } } else { None }; - + // Create query let query = GroupActionsQuery { contract_id, @@ -382,12 +412,12 @@ pub async fn get_group_actions( start_at_action_id, limit: count.map(|c| c as u16), }; - + // Fetch actions let actions_result = GroupAction::fetch_many(sdk.as_ref(), query) .await .map_err(|e| JsError::new(&format!("Failed to fetch group actions: {}", e)))?; - + // Convert result to response format let mut group_actions = Vec::new(); for (action_id, action_opt) in actions_result { @@ -400,14 +430,15 @@ pub async fn get_group_actions( })); } } - + let response = serde_json::json!({ "groupActions": group_actions }); - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -424,20 +455,25 @@ pub async fn get_group_action_signers( contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Parse action ID let action_id = Identifier::from_string( action_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Parse status let status = match status { "ACTIVE" => GroupActionStatus::ActionActive, "CLOSED" => GroupActionStatus::ActionClosed, - _ => return Err(JsError::new(&format!("Invalid status: {}. Must be ACTIVE or CLOSED", status))), + _ => { + return Err(JsError::new(&format!( + "Invalid status: {}. Must be ACTIVE or CLOSED", + status + ))) + } }; - + // Create query let query = GroupActionSignersQuery { contract_id, @@ -445,12 +481,12 @@ pub async fn get_group_action_signers( status, action_id, }; - + // Fetch signers let signers_result = GroupMemberPower::fetch_many(sdk.as_ref(), query) .await .map_err(|e| JsError::new(&format!("Failed to fetch group action signers: {}", e)))?; - + // Convert result to response format let mut signers = Vec::new(); for (signer_id, power_opt) in signers_result { @@ -461,14 +497,15 @@ pub async fn get_group_action_signers( })); } } - + let response = serde_json::json!({ "signers": signers }); - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -478,26 +515,29 @@ pub async fn get_groups_data_contracts( data_contract_ids: Vec, ) -> Result { let mut results: Vec = Vec::new(); - + for contract_id_str in data_contract_ids { let contract_id = Identifier::from_string( &contract_id_str, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Fetch all groups for this contract let query = GroupInfosQuery { contract_id, start_group_contract_position: None, limit: None, }; - - let groups_result = Group::fetch_many(sdk.as_ref(), query) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch groups for contract {}: {}", contract_id_str, e)))?; - + + let groups_result = Group::fetch_many(sdk.as_ref(), query).await.map_err(|e| { + JsError::new(&format!( + "Failed to fetch groups for contract {}: {}", + contract_id_str, e + )) + })?; + let mut groups: Vec = Vec::new(); - + for (position, group_opt) in groups_result { if let Some(group) = group_opt { groups.push(GroupContractPositionInfo { @@ -506,16 +546,17 @@ pub async fn get_groups_data_contracts( }); } } - + results.push(GroupsDataContractInfo { data_contract_id: contract_id_str, groups, }); } - + // Use json_compatible serializer to convert response let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - results.serialize(&serializer) + results + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -528,35 +569,37 @@ pub async fn get_group_info_with_proof_info( group_contract_position: u32, ) -> Result { use crate::queries::ProofMetadataResponse; - + // Parse data contract ID let contract_id = Identifier::from_string( data_contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Create group query let query = GroupQuery { contract_id, group_contract_position: group_contract_position as GroupContractPosition, }; - + // Fetch group with proof - let (group_result, metadata, proof) = Group::fetch_with_metadata_and_proof(sdk.as_ref(), query, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch group with proof: {}", e)))?; - + let (group_result, metadata, proof) = + Group::fetch_with_metadata_and_proof(sdk.as_ref(), query, None) + .await + .map_err(|e| JsError::new(&format!("Failed to fetch group with proof: {}", e)))?; + let data = group_result.map(|group| GroupInfoResponse::from_group(&group)); - + let response = ProofMetadataResponse { data, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -568,40 +611,45 @@ pub async fn get_group_infos_with_proof_info( count: Option, ) -> Result { use crate::queries::ProofMetadataResponse; - + // Parse contract ID let contract_id = Identifier::from_string( contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Parse start at info if provided - let start_group_contract_position = if !start_at_info.is_null() && !start_at_info.is_undefined() { + let start_group_contract_position = if !start_at_info.is_null() && !start_at_info.is_undefined() + { let info = serde_wasm_bindgen::from_value::(start_at_info); match info { Ok(json) => { - let position = json["position"].as_u64().ok_or_else(|| JsError::new("Invalid start position"))? as u32; + let position = json["position"] + .as_u64() + .ok_or_else(|| JsError::new("Invalid start position"))? + as u32; let included = json["included"].as_bool().unwrap_or(false); Some((position as GroupContractPosition, included)) } - Err(_) => None + Err(_) => None, } } else { None }; - + // Create query let query = GroupInfosQuery { contract_id, start_group_contract_position, limit: count.map(|c| c as u16), }; - + // Fetch groups with proof - let (groups_result, metadata, proof) = Group::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch groups with proof: {}", e)))?; - + let (groups_result, metadata, proof) = + Group::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) + .await + .map_err(|e| JsError::new(&format!("Failed to fetch groups with proof: {}", e)))?; + // Convert result to response format let mut group_infos = Vec::new(); for (position, group_opt) in groups_result { @@ -612,20 +660,21 @@ pub async fn get_group_infos_with_proof_info( }); } } - + let data = serde_json::json!({ "groupInfos": group_infos }); - + let response = ProofMetadataResponse { data, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -645,37 +694,42 @@ pub async fn get_group_members_with_proof_info( data_contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Create group query let query = GroupQuery { contract_id, group_contract_position: group_contract_position as GroupContractPosition, }; - + // Fetch the group with proof - let (group_result, metadata, proof) = Group::fetch_with_metadata_and_proof(sdk.as_ref(), query, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch group with proof: {}", e)))?; - + let (group_result, metadata, proof) = + Group::fetch_with_metadata_and_proof(sdk.as_ref(), query, None) + .await + .map_err(|e| JsError::new(&format!("Failed to fetch group with proof: {}", e)))?; + let data = match group_result { Some(group) => { let mut members: Vec = Vec::new(); - + // If specific member IDs are requested, filter by them if let Some(requested_ids) = member_ids { let requested_identifiers: Result, _> = requested_ids .iter() - .map(|id| Identifier::from_string( - id, - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - )) + .map(|id| { + Identifier::from_string( + id, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) + }) .collect(); let requested_identifiers = requested_identifiers?; - + for id in requested_identifiers { if let Ok(power) = group.member_power(id) { members.push(GroupMember { - member_id: id.to_string(dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58), + member_id: id.to_string( + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ), power, }); } @@ -685,47 +739,53 @@ pub async fn get_group_members_with_proof_info( let all_members = group.members(); let mut sorted_members: Vec<_> = all_members.iter().collect(); sorted_members.sort_by_key(|(id, _)| *id); - + // Apply start_at if provided let start_index = if let Some(start_id) = start_at { let start_identifier = Identifier::from_string( &start_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - sorted_members.iter().position(|(id, _)| **id > start_identifier).unwrap_or(sorted_members.len()) + sorted_members + .iter() + .position(|(id, _)| **id > start_identifier) + .unwrap_or(sorted_members.len()) } else { 0 }; - + // Apply limit let end_index = if let Some(lim) = limit { (start_index + lim as usize).min(sorted_members.len()) } else { sorted_members.len() }; - + for (id, power) in &sorted_members[start_index..end_index] { members.push(GroupMember { - member_id: (*id).to_string(dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58), + member_id: (*id).to_string( + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ), power: **power, }); } } - + Some(members) - }, + } None => None, }; - + let response = ProofMetadataResponse { data, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -742,20 +802,20 @@ pub async fn get_identity_groups_with_proof_info( struct IdentityGroupInfo { data_contract_id: String, group_contract_position: u32, - role: String, // "member", "owner", or "moderator" + role: String, // "member", "owner", or "moderator" power: Option, // Only for members } - + // Parse identity ID let id = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + let mut groups: Vec = Vec::new(); let mut combined_metadata: Option = None; let mut combined_proof: Option = None; - + // Check member data contracts if let Some(contracts) = member_data_contracts { for contract_id_str in contracts { @@ -763,24 +823,27 @@ pub async fn get_identity_groups_with_proof_info( &contract_id_str, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Fetch all groups for this contract with proof let query = GroupInfosQuery { contract_id, start_group_contract_position: None, limit: None, }; - - let (groups_result, metadata, proof) = Group::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch groups with proof: {}", e)))?; - + + let (groups_result, metadata, proof) = + Group::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) + .await + .map_err(|e| { + JsError::new(&format!("Failed to fetch groups with proof: {}", e)) + })?; + // Store first metadata and proof if combined_metadata.is_none() { combined_metadata = Some(metadata.into()); combined_proof = Some(proof.into()); } - + // Check each group for the identity for (position, group_opt) in groups_result { if let Some(group) = group_opt { @@ -796,15 +859,15 @@ pub async fn get_identity_groups_with_proof_info( } } } - + // Note: Owner and moderator roles would require additional contract queries // which are not yet implemented in the SDK. For now, return a warning. if owner_data_contracts.is_some() || moderator_data_contracts.is_some() { web_sys::console::warn_1(&JsValue::from_str( - "Warning: Owner and moderator role queries are not yet implemented" + "Warning: Owner and moderator role queries are not yet implemented", )); } - + let response = ProofMetadataResponse { data: groups, metadata: combined_metadata.unwrap_or_else(|| ResponseMetadata { @@ -824,10 +887,11 @@ pub async fn get_identity_groups_with_proof_info( quorum_type: 0, }), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -845,35 +909,42 @@ pub async fn get_group_actions_with_proof_info( contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Parse status let status = match status { "ACTIVE" => GroupActionStatus::ActionActive, "CLOSED" => GroupActionStatus::ActionClosed, - _ => return Err(JsError::new(&format!("Invalid status: {}. Must be ACTIVE or CLOSED", status))), + _ => { + return Err(JsError::new(&format!( + "Invalid status: {}. Must be ACTIVE or CLOSED", + status + ))) + } }; - + // Parse start action ID if provided let start_at_action_id = if !start_at_info.is_null() && !start_at_info.is_undefined() { let info = serde_wasm_bindgen::from_value::(start_at_info); match info { Ok(json) => { - let action_id = json["actionId"].as_str().ok_or_else(|| JsError::new("Invalid action ID"))?; + let action_id = json["actionId"] + .as_str() + .ok_or_else(|| JsError::new("Invalid action ID"))?; let included = json["included"].as_bool().unwrap_or(false); Some(( Identifier::from_string( action_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?, - included + included, )) } - Err(_) => None + Err(_) => None, } } else { None }; - + // Create query let query = GroupActionsQuery { contract_id, @@ -882,12 +953,15 @@ pub async fn get_group_actions_with_proof_info( start_at_action_id, limit: count.map(|c| c as u16), }; - + // Fetch actions with proof - let (actions_result, metadata, proof) = GroupAction::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch group actions with proof: {}", e)))?; - + let (actions_result, metadata, proof) = + GroupAction::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) + .await + .map_err(|e| { + JsError::new(&format!("Failed to fetch group actions with proof: {}", e)) + })?; + // Convert result to response format let mut group_actions = Vec::new(); for (action_id, action_opt) in actions_result { @@ -900,20 +974,21 @@ pub async fn get_group_actions_with_proof_info( })); } } - + let data = serde_json::json!({ "groupActions": group_actions }); - + let response = ProofMetadataResponse { data, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -930,20 +1005,25 @@ pub async fn get_group_action_signers_with_proof_info( contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Parse action ID let action_id = Identifier::from_string( action_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Parse status let status = match status { "ACTIVE" => GroupActionStatus::ActionActive, "CLOSED" => GroupActionStatus::ActionClosed, - _ => return Err(JsError::new(&format!("Invalid status: {}. Must be ACTIVE or CLOSED", status))), + _ => { + return Err(JsError::new(&format!( + "Invalid status: {}. Must be ACTIVE or CLOSED", + status + ))) + } }; - + // Create query let query = GroupActionSignersQuery { contract_id, @@ -951,12 +1031,18 @@ pub async fn get_group_action_signers_with_proof_info( status, action_id, }; - + // Fetch signers with proof - let (signers_result, metadata, proof) = GroupMemberPower::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch group action signers with proof: {}", e)))?; - + let (signers_result, metadata, proof) = + GroupMemberPower::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) + .await + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch group action signers with proof: {}", + e + )) + })?; + // Convert result to response format let mut signers = Vec::new(); for (signer_id, power_opt) in signers_result { @@ -967,20 +1053,21 @@ pub async fn get_group_action_signers_with_proof_info( })); } } - + let data = serde_json::json!({ "signers": signers }); - + let response = ProofMetadataResponse { data, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -992,32 +1079,38 @@ pub async fn get_groups_data_contracts_with_proof_info( let mut results: Vec = Vec::new(); let mut combined_metadata: Option = None; let mut combined_proof: Option = None; - + for contract_id_str in data_contract_ids { let contract_id = Identifier::from_string( &contract_id_str, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Fetch all groups for this contract with proof let query = GroupInfosQuery { contract_id, start_group_contract_position: None, limit: None, }; - - let (groups_result, metadata, proof) = Group::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch groups for contract {} with proof: {}", contract_id_str, e)))?; - + + let (groups_result, metadata, proof) = + Group::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) + .await + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch groups for contract {} with proof: {}", + contract_id_str, e + )) + })?; + // Store first metadata and proof if combined_metadata.is_none() { combined_metadata = Some(metadata.into()); combined_proof = Some(proof.into()); } - + let mut groups: Vec = Vec::new(); - + for (position, group_opt) in groups_result { if let Some(group) = group_opt { groups.push(GroupContractPositionInfo { @@ -1026,13 +1119,13 @@ pub async fn get_groups_data_contracts_with_proof_info( }); } } - + results.push(GroupsDataContractInfo { data_contract_id: contract_id_str, groups, }); } - + let response = ProofMetadataResponse { data: results, metadata: combined_metadata.unwrap_or_else(|| ResponseMetadata { @@ -1052,9 +1145,10 @@ pub async fn get_groups_data_contracts_with_proof_info( quorum_type: 0, }), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) -} \ No newline at end of file +} diff --git a/packages/wasm-sdk/src/queries/identity.rs b/packages/wasm-sdk/src/queries/identity.rs index 2a50dc79a7..7dcb44d180 100644 --- a/packages/wasm-sdk/src/queries/identity.rs +++ b/packages/wasm-sdk/src/queries/identity.rs @@ -1,15 +1,15 @@ use crate::dpp::IdentityWasm; +use crate::queries::{ProofInfo, ProofMetadataResponse, ResponseMetadata}; use crate::sdk::WasmSdk; -use crate::queries::{ProofMetadataResponse, ResponseMetadata, ProofInfo}; -use dash_sdk::platform::{Fetch, FetchMany, Identifier, Identity}; -use dash_sdk::dpp::identity::identity_public_key::IdentityPublicKey; use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::{JsError, JsValue}; -use serde::{Serialize, Deserialize}; +use dash_sdk::dpp::identity::identity_public_key::IdentityPublicKey; +use dash_sdk::platform::{Fetch, FetchMany, Identifier, Identity}; +use drive_proof_verifier::types::{IdentityPublicKeys, IndexMap}; use js_sys::Array; use rs_dapi_client::IntoInner; -use drive_proof_verifier::types::{IdentityPublicKeys, IndexMap}; +use serde::{Deserialize, Serialize}; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::{JsError, JsValue}; // Proof info functions are now included below @@ -27,31 +27,36 @@ pub async fn identity_fetch(sdk: &WasmSdk, base58_id: &str) -> Result Result { +pub async fn identity_fetch_with_proof_info( + sdk: &WasmSdk, + base58_id: &str, +) -> Result { let id = Identifier::from_string( base58_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - let (identity, metadata, proof) = Identity::fetch_with_metadata_and_proof(sdk, id, None) - .await?; + let (identity, metadata, proof) = + Identity::fetch_with_metadata_and_proof(sdk, id, None).await?; match identity { Some(identity) => { // Convert identity to JSON value first - let identity_json = IdentityWasm::from(identity).to_json() - .map_err(|e| JsError::new(&format!("Failed to convert identity to JSON: {:?}", e)))?; + let identity_json = IdentityWasm::from(identity).to_json().map_err(|e| { + JsError::new(&format!("Failed to convert identity to JSON: {:?}", e)) + })?; let identity_value: serde_json::Value = serde_wasm_bindgen::from_value(identity_json)?; - + let response = ProofMetadataResponse { data: identity_value, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } None => Err(JsError::new("Identity not found")), @@ -59,12 +64,19 @@ pub async fn identity_fetch_with_proof_info(sdk: &WasmSdk, base58_id: &str) -> R } #[wasm_bindgen] -pub async fn identity_fetch_unproved(sdk: &WasmSdk, base58_id: &str) -> Result { - use dash_sdk::platform::proto::get_identity_request::{GetIdentityRequestV0, Version as GetIdentityRequestVersion}; - use dash_sdk::platform::proto::get_identity_response::{get_identity_response_v0, GetIdentityResponseV0, Version}; +pub async fn identity_fetch_unproved( + sdk: &WasmSdk, + base58_id: &str, +) -> Result { + use dash_sdk::platform::proto::get_identity_request::{ + GetIdentityRequestV0, Version as GetIdentityRequestVersion, + }; + use dash_sdk::platform::proto::get_identity_response::{ + get_identity_response_v0, GetIdentityResponseV0, Version, + }; use dash_sdk::platform::proto::{GetIdentityRequest, GetIdentityResponse}; use rs_dapi_client::{DapiRequest, RequestSettings}; - + let id = Identifier::from_string( base58_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, @@ -89,9 +101,7 @@ pub async fn identity_fetch_unproved(sdk: &WasmSdk, base58_id: &str) -> Result { use dash_sdk::dpp::serialization::PlatformDeserializable; - let identity = Identity::deserialize_from_bytes( - identity_bytes.as_slice() - )?; + let identity = Identity::deserialize_from_bytes(identity_bytes.as_slice())?; Ok(identity.into()) } _ => Err(JsError::new("Identity not found")), @@ -112,7 +122,7 @@ pub(crate) struct IdentityKeyResponse { #[wasm_bindgen] pub async fn get_identity_keys( - sdk: &WasmSdk, + sdk: &WasmSdk, identity_id: &str, key_request_type: &str, specific_key_ids: Option>, @@ -120,18 +130,17 @@ pub async fn get_identity_keys( limit: Option, offset: Option, ) -> Result { - // DapiRequestExecutor not needed anymore - + if identity_id.is_empty() { return Err(JsError::new("Identity ID is required")); } - + let id = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Handle different key request types let keys_result = match key_request_type { "all" => { @@ -143,14 +152,16 @@ pub async fn get_identity_keys( "specific" => { // Use direct gRPC request for specific keys use dash_sdk::platform::proto::{ - GetIdentityKeysRequest, get_identity_keys_request::{GetIdentityKeysRequestV0, Version}, - KeyRequestType, key_request_type::Request, SpecificKeys + get_identity_keys_request::{GetIdentityKeysRequestV0, Version}, + key_request_type::Request, + GetIdentityKeysRequest, KeyRequestType, SpecificKeys, }; use rs_dapi_client::{DapiRequest, RequestSettings}; - - let key_ids = specific_key_ids - .ok_or_else(|| JsError::new("specific_key_ids is required for 'specific' key request type"))?; - + + let key_ids = specific_key_ids.ok_or_else(|| { + JsError::new("specific_key_ids is required for 'specific' key request type") + })?; + let request = GetIdentityKeysRequest { version: Some(Version::V0(GetIdentityKeysRequestV0 { identity_id: id.to_vec(), @@ -158,22 +169,24 @@ pub async fn get_identity_keys( limit: Some(limit.unwrap_or(100).into()), // Always provide a limit when prove=false offset: None, // Offsets not supported when prove=false request_type: Some(KeyRequestType { - request: Some(Request::SpecificKeys(SpecificKeys { - key_ids, - })), + request: Some(Request::SpecificKeys(SpecificKeys { key_ids })), }), })), }; - + let response = request .execute(sdk.as_ref(), RequestSettings::default()) .await - .map_err(|e| JsError::new(&format!("Failed to fetch specific identity keys: {}", e)))?; - + .map_err(|e| { + JsError::new(&format!("Failed to fetch specific identity keys: {}", e)) + })?; + // Process the response to extract keys - use dash_sdk::platform::proto::{GetIdentityKeysResponse, get_identity_keys_response::Version as ResponseVersion}; + use dash_sdk::platform::proto::{ + get_identity_keys_response::Version as ResponseVersion, GetIdentityKeysResponse, + }; use rs_dapi_client::IntoInner; - + let response: GetIdentityKeysResponse = response.into_inner(); match response.version { Some(ResponseVersion::V0(response_v0)) => { @@ -201,53 +214,62 @@ pub async fn get_identity_keys( "search" => { // Use direct gRPC request for search keys use dash_sdk::platform::proto::{ - GetIdentityKeysRequest, get_identity_keys_request::{GetIdentityKeysRequestV0, Version}, - KeyRequestType, key_request_type::Request, SearchKey, SecurityLevelMap, - security_level_map::KeyKindRequestType as GrpcKeyKindRequestType + get_identity_keys_request::{GetIdentityKeysRequestV0, Version}, + key_request_type::Request, + security_level_map::KeyKindRequestType as GrpcKeyKindRequestType, + GetIdentityKeysRequest, KeyRequestType, SearchKey, SecurityLevelMap, }; use rs_dapi_client::{DapiRequest, RequestSettings}; use std::collections::HashMap; - - let purpose_map_str = search_purpose_map - .ok_or_else(|| JsError::new("search_purpose_map is required for 'search' key request type"))?; - + + let purpose_map_str = search_purpose_map.ok_or_else(|| { + JsError::new("search_purpose_map is required for 'search' key request type") + })?; + // Parse the JSON purpose map let purpose_map_json: serde_json::Value = serde_json::from_str(&purpose_map_str) .map_err(|e| JsError::new(&format!("Invalid JSON in search_purpose_map: {}", e)))?; - + // Convert JSON to gRPC structure let mut purpose_map = HashMap::new(); - + if let serde_json::Value::Object(map) = purpose_map_json { for (purpose_str, security_levels) in map { - let purpose = purpose_str.parse::() - .map_err(|_| JsError::new(&format!("Invalid purpose value: {}", purpose_str)))?; - + let purpose = purpose_str.parse::().map_err(|_| { + JsError::new(&format!("Invalid purpose value: {}", purpose_str)) + })?; + let mut security_level_map = HashMap::new(); - + if let serde_json::Value::Object(levels) = security_levels { for (level_str, kind_str) in levels { - let level = level_str.parse::() - .map_err(|_| JsError::new(&format!("Invalid security level: {}", level_str)))?; - + let level = level_str.parse::().map_err(|_| { + JsError::new(&format!("Invalid security level: {}", level_str)) + })?; + let kind = match kind_str.as_str().unwrap_or("") { - "current" | "0" => GrpcKeyKindRequestType::CurrentKeyOfKindRequest as i32, + "current" | "0" => { + GrpcKeyKindRequestType::CurrentKeyOfKindRequest as i32 + } "all" | "1" => GrpcKeyKindRequestType::AllKeysOfKindRequest as i32, - _ => return Err(JsError::new(&format!("Invalid key kind: {}", kind_str))), + _ => { + return Err(JsError::new(&format!( + "Invalid key kind: {}", + kind_str + ))) + } }; - + security_level_map.insert(level, kind); } } - - purpose_map.insert(purpose, SecurityLevelMap { - security_level_map, - }); + + purpose_map.insert(purpose, SecurityLevelMap { security_level_map }); } } else { return Err(JsError::new("search_purpose_map must be a JSON object")); } - + let request = GetIdentityKeysRequest { version: Some(Version::V0(GetIdentityKeysRequestV0 { identity_id: id.to_vec(), @@ -255,22 +277,24 @@ pub async fn get_identity_keys( limit: Some(limit.unwrap_or(100).into()), // Always provide a limit when prove=false offset: None, // Offsets not supported when prove=false request_type: Some(KeyRequestType { - request: Some(Request::SearchKey(SearchKey { - purpose_map, - })), + request: Some(Request::SearchKey(SearchKey { purpose_map })), }), })), }; - + let response = request .execute(sdk.as_ref(), RequestSettings::default()) .await - .map_err(|e| JsError::new(&format!("Failed to fetch search identity keys: {}", e)))?; - + .map_err(|e| { + JsError::new(&format!("Failed to fetch search identity keys: {}", e)) + })?; + // Process the response to extract keys - use dash_sdk::platform::proto::{GetIdentityKeysResponse, get_identity_keys_response::Version as ResponseVersion}; + use dash_sdk::platform::proto::{ + get_identity_keys_response::Version as ResponseVersion, GetIdentityKeysResponse, + }; use rs_dapi_client::IntoInner; - + let response: GetIdentityKeysResponse = response.into_inner(); match response.version { Some(ResponseVersion::V0(response_v0)) => { @@ -296,13 +320,15 @@ pub async fn get_identity_keys( } } _ => { - return Err(JsError::new("Invalid key_request_type. Use 'all', 'specific', or 'search'")); + return Err(JsError::new( + "Invalid key_request_type. Use 'all', 'specific', or 'search'", + )); } }; - + // Convert keys to response format let mut keys: Vec = Vec::new(); - + // Apply offset and limit if provided let start = offset.unwrap_or(0) as usize; let end = if let Some(lim) = limit { @@ -310,7 +336,7 @@ pub async fn get_identity_keys( } else { usize::MAX }; - + for (idx, (key_id, key_opt)) in keys_result.into_iter().enumerate() { if idx < start { continue; @@ -318,7 +344,7 @@ pub async fn get_identity_keys( if idx >= end { break; } - + if let Some(key) = key_opt { keys.push(IdentityKeyResponse { key_id: key_id, @@ -331,84 +357,92 @@ pub async fn get_identity_keys( }); } } - + serde_wasm_bindgen::to_value(&keys) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } #[wasm_bindgen] pub async fn get_identity_nonce(sdk: &WasmSdk, identity_id: &str) -> Result { - use drive_proof_verifier::types::IdentityNonceFetcher; use dash_sdk::platform::Fetch; - + use drive_proof_verifier::types::IdentityNonceFetcher; + if identity_id.is_empty() { return Err(JsError::new("Identity ID is required")); } - + let id = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + let nonce_result = IdentityNonceFetcher::fetch(sdk.as_ref(), id) .await .map_err(|e| JsError::new(&format!("Failed to fetch identity nonce: {}", e)))?; - + let nonce = nonce_result .map(|fetcher| fetcher.0) .ok_or_else(|| JsError::new("Identity nonce not found"))?; - + // Return as a JSON object with nonce as string to avoid BigInt serialization issues #[derive(Serialize)] struct NonceResponse { nonce: String, } - + let response = NonceResponse { nonce: nonce.to_string(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } #[wasm_bindgen] -pub async fn get_identity_nonce_with_proof_info(sdk: &WasmSdk, identity_id: &str) -> Result { - use drive_proof_verifier::types::IdentityNonceFetcher; +pub async fn get_identity_nonce_with_proof_info( + sdk: &WasmSdk, + identity_id: &str, +) -> Result { use dash_sdk::platform::Fetch; - + use drive_proof_verifier::types::IdentityNonceFetcher; + if identity_id.is_empty() { return Err(JsError::new("Identity ID is required")); } - + let id = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - - let (nonce_result, metadata, proof) = IdentityNonceFetcher::fetch_with_metadata_and_proof(sdk.as_ref(), id, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch identity nonce with proof: {}", e)))?; - + + let (nonce_result, metadata, proof) = + IdentityNonceFetcher::fetch_with_metadata_and_proof(sdk.as_ref(), id, None) + .await + .map_err(|e| { + JsError::new(&format!("Failed to fetch identity nonce with proof: {}", e)) + })?; + let nonce = nonce_result .map(|fetcher| fetcher.0) .ok_or_else(|| JsError::new("Identity nonce not found"))?; - + let data = serde_json::json!({ "nonce": nonce.to_string() }); - + let response = ProofMetadataResponse { data, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -418,48 +452,52 @@ pub async fn get_identity_contract_nonce( identity_id: &str, contract_id: &str, ) -> Result { - use drive_proof_verifier::types::IdentityContractNonceFetcher; use dash_sdk::platform::Fetch; - + use drive_proof_verifier::types::IdentityContractNonceFetcher; + if identity_id.is_empty() { return Err(JsError::new("Identity ID is required")); } - + if contract_id.is_empty() { return Err(JsError::new("Contract ID is required")); } - + let identity_id = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + let contract_id = Identifier::from_string( contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - - let nonce_result = IdentityContractNonceFetcher::fetch(sdk.as_ref(), (identity_id, contract_id)) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch identity contract nonce: {}", e)))?; - + + let nonce_result = + IdentityContractNonceFetcher::fetch(sdk.as_ref(), (identity_id, contract_id)) + .await + .map_err(|e| { + JsError::new(&format!("Failed to fetch identity contract nonce: {}", e)) + })?; + let nonce = nonce_result .map(|fetcher| fetcher.0) .ok_or_else(|| JsError::new("Identity contract nonce not found"))?; - + // Return as a JSON object with nonce as string to avoid BigInt serialization issues #[derive(Serialize)] struct NonceResponse { nonce: String, } - + let response = NonceResponse { nonce: nonce.to_string(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -469,87 +507,95 @@ pub async fn get_identity_contract_nonce_with_proof_info( identity_id: &str, contract_id: &str, ) -> Result { - use drive_proof_verifier::types::IdentityContractNonceFetcher; use dash_sdk::platform::Fetch; - + use drive_proof_verifier::types::IdentityContractNonceFetcher; + if identity_id.is_empty() { return Err(JsError::new("Identity ID is required")); } - + if contract_id.is_empty() { return Err(JsError::new("Contract ID is required")); } - + let identity_id = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + let contract_id = Identifier::from_string( contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - - let (nonce_result, metadata, proof) = IdentityContractNonceFetcher::fetch_with_metadata_and_proof( - sdk.as_ref(), - (identity_id, contract_id), - None - ) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch identity contract nonce with proof: {}", e)))?; - + + let (nonce_result, metadata, proof) = + IdentityContractNonceFetcher::fetch_with_metadata_and_proof( + sdk.as_ref(), + (identity_id, contract_id), + None, + ) + .await + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch identity contract nonce with proof: {}", + e + )) + })?; + let nonce = nonce_result .map(|fetcher| fetcher.0) .ok_or_else(|| JsError::new("Identity contract nonce not found"))?; - + let data = serde_json::json!({ "nonce": nonce.to_string() }); - + let response = ProofMetadataResponse { data, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } #[wasm_bindgen] pub async fn get_identity_balance(sdk: &WasmSdk, id: &str) -> Result { - use drive_proof_verifier::types::IdentityBalance; use dash_sdk::platform::Fetch; - + use drive_proof_verifier::types::IdentityBalance; + if id.is_empty() { return Err(JsError::new("Identity ID is required")); } - + let identity_id = Identifier::from_string( id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + let balance_result = IdentityBalance::fetch(sdk.as_ref(), identity_id) .await .map_err(|e| JsError::new(&format!("Failed to fetch identity balance: {}", e)))?; - + if let Some(balance) = balance_result { // Return as object with balance as string to handle large numbers #[derive(Serialize)] struct BalanceResponse { balance: String, } - + let response = BalanceResponse { balance: balance.to_string(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } else { Err(JsError::new("Identity balance not found")) @@ -560,42 +606,47 @@ pub async fn get_identity_balance(sdk: &WasmSdk, id: &str) -> Result) -> Result { +pub async fn get_identities_balances( + sdk: &WasmSdk, + identity_ids: Vec, +) -> Result { use drive_proof_verifier::types::IdentityBalance; - - + // Convert string IDs to Identifiers let identifiers: Vec = identity_ids .into_iter() - .map(|id| Identifier::from_string( - &id, - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - )) + .map(|id| { + Identifier::from_string( + &id, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) + }) .collect::, _>>()?; - - let balances_result: drive_proof_verifier::types::IdentityBalances = IdentityBalance::fetch_many(sdk.as_ref(), identifiers.clone()) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch identities balances: {}", e)))?; - + + let balances_result: drive_proof_verifier::types::IdentityBalances = + IdentityBalance::fetch_many(sdk.as_ref(), identifiers.clone()) + .await + .map_err(|e| JsError::new(&format!("Failed to fetch identities balances: {}", e)))?; + // Convert to response format let responses: Vec = identifiers .into_iter() .filter_map(|id| { balances_result.get(&id).and_then(|balance_opt| { - balance_opt.map(|balance| { - IdentityBalanceResponse { - identity_id: id.to_string(dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58), - balance: balance.to_string(), - } + balance_opt.map(|balance| IdentityBalanceResponse { + identity_id: id.to_string( + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ), + balance: balance.to_string(), }) }) }) .collect(); - + serde_wasm_bindgen::to_value(&responses) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -603,37 +654,46 @@ pub async fn get_identities_balances(sdk: &WasmSdk, identity_ids: Vec) - #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] pub(crate) struct IdentityBalanceAndRevisionResponse { - balance: String, // String to handle large numbers + balance: String, // String to handle large numbers revision: u64, } #[wasm_bindgen] -pub async fn get_identity_balance_and_revision(sdk: &WasmSdk, identity_id: &str) -> Result { - use drive_proof_verifier::types::IdentityBalanceAndRevision; +pub async fn get_identity_balance_and_revision( + sdk: &WasmSdk, + identity_id: &str, +) -> Result { use dash_sdk::platform::Fetch; - + use drive_proof_verifier::types::IdentityBalanceAndRevision; + if identity_id.is_empty() { return Err(JsError::new("Identity ID is required")); } - + let id = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + let result = IdentityBalanceAndRevision::fetch(sdk.as_ref(), id) .await - .map_err(|e| JsError::new(&format!("Failed to fetch identity balance and revision: {}", e)))?; - + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch identity balance and revision: {}", + e + )) + })?; + if let Some(balance_and_revision) = result { let response = IdentityBalanceAndRevisionResponse { balance: balance_and_revision.0.to_string(), revision: balance_and_revision.1, }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } else { Err(JsError::new("Identity balance and revision not found")) @@ -641,24 +701,34 @@ pub async fn get_identity_balance_and_revision(sdk: &WasmSdk, identity_id: &str) } #[wasm_bindgen] -pub async fn get_identity_by_public_key_hash(sdk: &WasmSdk, public_key_hash: &str) -> Result { +pub async fn get_identity_by_public_key_hash( + sdk: &WasmSdk, + public_key_hash: &str, +) -> Result { use dash_sdk::platform::types::identity::PublicKeyHash; - + // Parse the hex-encoded public key hash let hash_bytes = hex::decode(public_key_hash) .map_err(|e| JsError::new(&format!("Invalid public key hash hex: {}", e)))?; - + if hash_bytes.len() != 20 { - return Err(JsError::new("Public key hash must be 20 bytes (40 hex characters)")); + return Err(JsError::new( + "Public key hash must be 20 bytes (40 hex characters)", + )); } - + let mut hash_array = [0u8; 20]; hash_array.copy_from_slice(&hash_bytes); - + let result = Identity::fetch(sdk.as_ref(), PublicKeyHash(hash_array)) .await - .map_err(|e| JsError::new(&format!("Failed to fetch identity by public key hash: {}", e)))?; - + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch identity by public key hash: {}", + e + )) + })?; + result .ok_or_else(|| JsError::new("Identity not found for public key hash")) .map(Into::into) @@ -692,22 +762,24 @@ pub async fn get_identities_contract_keys( purposes: Option>, ) -> Result { use dash_sdk::dpp::identity::Purpose; - + // Convert string IDs to Identifiers let _identity_ids: Vec = identities_ids .iter() - .map(|id| Identifier::from_string( - id, - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - )) + .map(|id| { + Identifier::from_string( + id, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) + }) .collect::, _>>()?; - + // Contract ID is not used in the individual key queries, but we validate it let _contract_identifier = Identifier::from_string( contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Convert purposes if provided let purposes_opt = purposes.map(|p| { p.into_iter() @@ -722,24 +794,29 @@ pub async fn get_identities_contract_keys( }) .collect::>() }); - + // For now, we'll implement this by fetching keys for each identity individually // The SDK doesn't fully expose the batch query yet let mut responses: Vec = Vec::new(); - + for identity_id_str in identities_ids { let identity_id = Identifier::from_string( &identity_id_str, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Get keys for this identity using the regular identity keys query let keys_result = IdentityPublicKey::fetch_many(sdk.as_ref(), identity_id) .await - .map_err(|e| JsError::new(&format!("Failed to fetch keys for identity {}: {}", identity_id_str, e)))?; - + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch keys for identity {}: {}", + identity_id_str, e + )) + })?; + let mut identity_keys = Vec::new(); - + // Filter keys by purpose if specified for (key_id, key_opt) in keys_result { if let Some(key) = key_opt { @@ -749,7 +826,7 @@ pub async fn get_identities_contract_keys( continue; } } - + let key_response = IdentityKeyResponse { key_id: key_id, key_type: format!("{:?}", key.key_type()), @@ -762,7 +839,7 @@ pub async fn get_identities_contract_keys( identity_keys.push(key_response); } } - + if !identity_keys.is_empty() { responses.push(IdentityContractKeysResponse { identity_id: identity_id_str, @@ -770,7 +847,7 @@ pub async fn get_identities_contract_keys( }); } } - + serde_wasm_bindgen::to_value(&responses) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -781,19 +858,19 @@ pub async fn get_identity_by_non_unique_public_key_hash( public_key_hash: &str, start_after: Option, ) -> Result { - - // Parse the hex-encoded public key hash let hash_bytes = hex::decode(public_key_hash) .map_err(|e| JsError::new(&format!("Invalid public key hash hex: {}", e)))?; - + if hash_bytes.len() != 20 { - return Err(JsError::new("Public key hash must be 20 bytes (40 hex characters)")); + return Err(JsError::new( + "Public key hash must be 20 bytes (40 hex characters)", + )); } - + let mut hash_array = [0u8; 20]; hash_array.copy_from_slice(&hash_bytes); - + // Convert start_after if provided let start_id = if let Some(start) = start_after { Some(Identifier::from_string( @@ -803,36 +880,38 @@ pub async fn get_identity_by_non_unique_public_key_hash( } else { None }; - + use dash_sdk::platform::types::identity::NonUniquePublicKeyHashQuery; - + let query = NonUniquePublicKeyHashQuery { key_hash: hash_array, after: start_id.map(|id| *id.as_bytes()), }; - + // Fetch identity by non-unique public key hash - let identity = Identity::fetch(sdk.as_ref(), query) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch identities by non-unique public key hash: {}", e)))?; - + let identity = Identity::fetch(sdk.as_ref(), query).await.map_err(|e| { + JsError::new(&format!( + "Failed to fetch identities by non-unique public key hash: {}", + e + )) + })?; + // Return array with single identity if found let results = if let Some(id) = identity { vec![id] } else { vec![] }; - + // Convert results to IdentityWasm - let identities: Vec = results - .into_iter() - .map(Into::into) - .collect(); - + let identities: Vec = results.into_iter().map(Into::into).collect(); + // Create JS array directly let js_array = Array::new(); for identity in identities { - let json = identity.to_json().map_err(|e| JsError::new(&format!("Failed to convert identity to JSON: {:?}", e)))?; + let json = identity + .to_json() + .map_err(|e| JsError::new(&format!("Failed to convert identity to JSON: {:?}", e)))?; js_array.push(&json); } Ok(js_array.into()) @@ -842,7 +921,7 @@ pub async fn get_identity_by_non_unique_public_key_hash( #[serde(rename_all = "camelCase")] pub(crate) struct TokenBalanceResponse { token_id: String, - balance: String, // String to handle large numbers + balance: String, // String to handle large numbers } #[wasm_bindgen] @@ -851,35 +930,38 @@ pub async fn get_identity_token_balances( identity_id: &str, token_ids: Vec, ) -> Result { - use dash_sdk::platform::tokens::identity_token_balances::IdentityTokenBalancesQuery; use dash_sdk::dpp::balances::credits::TokenAmount; - + use dash_sdk::platform::tokens::identity_token_balances::IdentityTokenBalancesQuery; + let identity_id = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Convert token IDs to Identifiers let token_identifiers: Vec = token_ids .iter() - .map(|id| Identifier::from_string( - id, - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - )) + .map(|id| { + Identifier::from_string( + id, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) + }) .collect::, _>>()?; - + let query = IdentityTokenBalancesQuery { identity_id, token_ids: token_identifiers.clone(), }; - - - + // Use FetchMany trait to fetch token balances - let balances: drive_proof_verifier::types::identity_token_balance::IdentityTokenBalances = TokenAmount::fetch_many(sdk.as_ref(), query) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch identity token balances: {}", e)))?; - + let balances: drive_proof_verifier::types::identity_token_balance::IdentityTokenBalances = + TokenAmount::fetch_many(sdk.as_ref(), query) + .await + .map_err(|e| { + JsError::new(&format!("Failed to fetch identity token balances: {}", e)) + })?; + // Convert to response format let responses: Vec = token_identifiers .into_iter() @@ -893,7 +975,7 @@ pub async fn get_identity_token_balances( }) }) .collect(); - + serde_wasm_bindgen::to_value(&responses) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -902,7 +984,7 @@ pub async fn get_identity_token_balances( #[wasm_bindgen] pub async fn get_identity_keys_with_proof_info( - sdk: &WasmSdk, + sdk: &WasmSdk, identity_id: &str, key_request_type: &str, specific_key_ids: Option>, @@ -912,33 +994,37 @@ pub async fn get_identity_keys_with_proof_info( if identity_id.is_empty() { return Err(JsError::new("Identity ID is required")); } - + let id = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Handle different key request types let (keys_result, metadata, proof) = match key_request_type { "all" => { // Use existing all keys implementation with proof IdentityPublicKey::fetch_many_with_metadata_and_proof(sdk.as_ref(), id, None) .await - .map_err(|e| JsError::new(&format!("Failed to fetch identity keys with proof: {}", e)))? + .map_err(|e| { + JsError::new(&format!("Failed to fetch identity keys with proof: {}", e)) + })? } "specific" => { // For now, specific keys with proof is not implemented // Fall back to the non-proof version temporarily - let key_ids = specific_key_ids - .ok_or_else(|| JsError::new("specific_key_ids is required for 'specific' key request type"))?; - + let key_ids = specific_key_ids.ok_or_else(|| { + JsError::new("specific_key_ids is required for 'specific' key request type") + })?; + // Use direct gRPC request for specific keys use dash_sdk::platform::proto::{ - GetIdentityKeysRequest, get_identity_keys_request::{GetIdentityKeysRequestV0, Version}, - KeyRequestType, key_request_type::Request, SpecificKeys + get_identity_keys_request::{GetIdentityKeysRequestV0, Version}, + key_request_type::Request, + GetIdentityKeysRequest, KeyRequestType, SpecificKeys, }; use rs_dapi_client::{DapiRequest, RequestSettings}; - + let request = GetIdentityKeysRequest { version: Some(Version::V0(GetIdentityKeysRequestV0 { identity_id: id.to_vec(), @@ -946,22 +1032,24 @@ pub async fn get_identity_keys_with_proof_info( limit: limit.map(|l| l.into()), offset: offset.map(|o| o.into()), request_type: Some(KeyRequestType { - request: Some(Request::SpecificKeys(SpecificKeys { - key_ids, - })), + request: Some(Request::SpecificKeys(SpecificKeys { key_ids })), }), })), }; - + let response = request .execute(sdk.as_ref(), RequestSettings::default()) .await - .map_err(|e| JsError::new(&format!("Failed to fetch specific identity keys: {}", e)))?; - + .map_err(|e| { + JsError::new(&format!("Failed to fetch specific identity keys: {}", e)) + })?; + // Process the response to extract keys - use dash_sdk::platform::proto::{GetIdentityKeysResponse, get_identity_keys_response::Version as ResponseVersion}; + use dash_sdk::platform::proto::{ + get_identity_keys_response::Version as ResponseVersion, GetIdentityKeysResponse, + }; use rs_dapi_client::IntoInner; - + let response: GetIdentityKeysResponse = response.into_inner(); match response.version { Some(ResponseVersion::V0(response_v0)) => { @@ -1004,13 +1092,15 @@ pub async fn get_identity_keys_with_proof_info( } } _ => { - return Err(JsError::new("Invalid key_request_type. Use 'all', 'specific', or 'search'")); + return Err(JsError::new( + "Invalid key_request_type. Use 'all', 'specific', or 'search'", + )); } }; - + // Convert keys to response format let mut keys: Vec = Vec::new(); - + // Apply offset and limit if provided let start = offset.unwrap_or(0) as usize; let end = if let Some(lim) = limit { @@ -1018,7 +1108,7 @@ pub async fn get_identity_keys_with_proof_info( } else { usize::MAX }; - + for (idx, (key_id, key_opt)) in keys_result.into_iter().enumerate() { if idx < start { continue; @@ -1026,7 +1116,7 @@ pub async fn get_identity_keys_with_proof_info( if idx >= end { break; } - + if let Some(key) = key_opt { keys.push(IdentityKeyResponse { key_id: key_id, @@ -1039,56 +1129,67 @@ pub async fn get_identity_keys_with_proof_info( }); } } - + let response = ProofMetadataResponse { data: keys, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } #[wasm_bindgen] -pub async fn get_identity_balance_with_proof_info(sdk: &WasmSdk, id: &str) -> Result { - use drive_proof_verifier::types::IdentityBalance; +pub async fn get_identity_balance_with_proof_info( + sdk: &WasmSdk, + id: &str, +) -> Result { use dash_sdk::platform::Fetch; - + use drive_proof_verifier::types::IdentityBalance; + if id.is_empty() { return Err(JsError::new("Identity ID is required")); } - + let identity_id = Identifier::from_string( id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - - let (balance_result, metadata, proof) = IdentityBalance::fetch_with_metadata_and_proof(sdk.as_ref(), identity_id, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch identity balance with proof: {}", e)))?; - + + let (balance_result, metadata, proof) = + IdentityBalance::fetch_with_metadata_and_proof(sdk.as_ref(), identity_id, None) + .await + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch identity balance with proof: {}", + e + )) + })?; + if let Some(balance) = balance_result { #[derive(Serialize)] struct BalanceResponse { balance: String, } - + let data = BalanceResponse { balance: balance.to_string(), }; - + let response = ProofMetadataResponse { data, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } else { Err(JsError::new("Identity balance not found")) @@ -1096,82 +1197,104 @@ pub async fn get_identity_balance_with_proof_info(sdk: &WasmSdk, id: &str) -> Re } #[wasm_bindgen] -pub async fn get_identities_balances_with_proof_info(sdk: &WasmSdk, identity_ids: Vec) -> Result { +pub async fn get_identities_balances_with_proof_info( + sdk: &WasmSdk, + identity_ids: Vec, +) -> Result { use drive_proof_verifier::types::IdentityBalance; - + // Convert string IDs to Identifiers let identifiers: Vec = identity_ids .iter() - .map(|id| Identifier::from_string( - id, - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - )) + .map(|id| { + Identifier::from_string( + id, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) + }) .collect::, _>>()?; - - let (balances_result, metadata, proof): (drive_proof_verifier::types::IdentityBalances, _, _) = IdentityBalance::fetch_many_with_metadata_and_proof(sdk.as_ref(), identifiers.clone(), None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch identities balances with proof: {}", e)))?; - + + let (balances_result, metadata, proof): (drive_proof_verifier::types::IdentityBalances, _, _) = + IdentityBalance::fetch_many_with_metadata_and_proof(sdk.as_ref(), identifiers.clone(), None) + .await + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch identities balances with proof: {}", + e + )) + })?; + // Convert to response format let responses: Vec = identifiers .into_iter() .filter_map(|id| { balances_result.get(&id).and_then(|balance_opt| { - balance_opt.map(|balance| { - IdentityBalanceResponse { - identity_id: id.to_string(dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58), - balance: balance.to_string(), - } + balance_opt.map(|balance| IdentityBalanceResponse { + identity_id: id.to_string( + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ), + balance: balance.to_string(), }) }) }) .collect(); - + let response = ProofMetadataResponse { data: responses, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } #[wasm_bindgen] -pub async fn get_identity_balance_and_revision_with_proof_info(sdk: &WasmSdk, identity_id: &str) -> Result { - use drive_proof_verifier::types::IdentityBalanceAndRevision; +pub async fn get_identity_balance_and_revision_with_proof_info( + sdk: &WasmSdk, + identity_id: &str, +) -> Result { use dash_sdk::platform::Fetch; - + use drive_proof_verifier::types::IdentityBalanceAndRevision; + if identity_id.is_empty() { return Err(JsError::new("Identity ID is required")); } - + let id = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - - let (result, metadata, proof) = IdentityBalanceAndRevision::fetch_with_metadata_and_proof(sdk.as_ref(), id, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch identity balance and revision with proof: {}", e)))?; - + + let (result, metadata, proof) = + IdentityBalanceAndRevision::fetch_with_metadata_and_proof(sdk.as_ref(), id, None) + .await + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch identity balance and revision with proof: {}", + e + )) + })?; + if let Some(balance_and_revision) = result { let data = IdentityBalanceAndRevisionResponse { balance: balance_and_revision.0.to_string(), revision: balance_and_revision.1, }; - + let response = ProofMetadataResponse { data, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } else { Err(JsError::new("Identity balance and revision not found")) @@ -1179,39 +1302,52 @@ pub async fn get_identity_balance_and_revision_with_proof_info(sdk: &WasmSdk, id } #[wasm_bindgen] -pub async fn get_identity_by_public_key_hash_with_proof_info(sdk: &WasmSdk, public_key_hash: &str) -> Result { +pub async fn get_identity_by_public_key_hash_with_proof_info( + sdk: &WasmSdk, + public_key_hash: &str, +) -> Result { use dash_sdk::platform::types::identity::PublicKeyHash; - + // Parse the hex-encoded public key hash let hash_bytes = hex::decode(public_key_hash) .map_err(|e| JsError::new(&format!("Invalid public key hash hex: {}", e)))?; - + if hash_bytes.len() != 20 { - return Err(JsError::new("Public key hash must be 20 bytes (40 hex characters)")); + return Err(JsError::new( + "Public key hash must be 20 bytes (40 hex characters)", + )); } - + let mut hash_array = [0u8; 20]; hash_array.copy_from_slice(&hash_bytes); - - let (result, metadata, proof) = Identity::fetch_with_metadata_and_proof(sdk.as_ref(), PublicKeyHash(hash_array), None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch identity by public key hash with proof: {}", e)))?; - + + let (result, metadata, proof) = + Identity::fetch_with_metadata_and_proof(sdk.as_ref(), PublicKeyHash(hash_array), None) + .await + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch identity by public key hash with proof: {}", + e + )) + })?; + match result { Some(identity) => { - let identity_json = IdentityWasm::from(identity).to_json() - .map_err(|e| JsError::new(&format!("Failed to convert identity to JSON: {:?}", e)))?; + let identity_json = IdentityWasm::from(identity).to_json().map_err(|e| { + JsError::new(&format!("Failed to convert identity to JSON: {:?}", e)) + })?; let identity_value: serde_json::Value = serde_wasm_bindgen::from_value(identity_json)?; - + let response = ProofMetadataResponse { data: identity_value, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } None => Err(JsError::new("Identity not found for public key hash")), @@ -1227,14 +1363,16 @@ pub async fn get_identity_by_non_unique_public_key_hash_with_proof_info( // Parse the hex-encoded public key hash let hash_bytes = hex::decode(public_key_hash) .map_err(|e| JsError::new(&format!("Invalid public key hash hex: {}", e)))?; - + if hash_bytes.len() != 20 { - return Err(JsError::new("Public key hash must be 20 bytes (40 hex characters)")); + return Err(JsError::new( + "Public key hash must be 20 bytes (40 hex characters)", + )); } - + let mut hash_array = [0u8; 20]; hash_array.copy_from_slice(&hash_bytes); - + // Convert start_after if provided let start_id = if let Some(start) = start_after { Some(Identifier::from_string( @@ -1244,46 +1382,54 @@ pub async fn get_identity_by_non_unique_public_key_hash_with_proof_info( } else { None }; - + use dash_sdk::platform::types::identity::NonUniquePublicKeyHashQuery; - + let query = NonUniquePublicKeyHashQuery { key_hash: hash_array, after: start_id.map(|id| *id.as_bytes()), }; - + // Fetch identity by non-unique public key hash with proof - let (identity, metadata, proof) = Identity::fetch_with_metadata_and_proof(sdk.as_ref(), query, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch identities by non-unique public key hash with proof: {}", e)))?; - + let (identity, metadata, proof) = + Identity::fetch_with_metadata_and_proof(sdk.as_ref(), query, None) + .await + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch identities by non-unique public key hash with proof: {}", + e + )) + })?; + // Return array with single identity if found let results = if let Some(id) = identity { vec![id] } else { vec![] }; - + // Convert results to JSON let identities_json: Vec = results .into_iter() .map(|identity| { let identity_wasm: IdentityWasm = identity.into(); - let json = identity_wasm.to_json() - .map_err(|_| serde_wasm_bindgen::Error::new("Failed to convert identity to JSON"))?; + let json = identity_wasm.to_json().map_err(|_| { + serde_wasm_bindgen::Error::new("Failed to convert identity to JSON") + })?; serde_wasm_bindgen::from_value(json) }) .collect::, _>>()?; - + let response = ProofMetadataResponse { data: identities_json, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -1295,22 +1441,24 @@ pub async fn get_identities_contract_keys_with_proof_info( purposes: Option>, ) -> Result { use dash_sdk::dpp::identity::Purpose; - + // Convert string IDs to Identifiers let _identity_ids: Vec = identities_ids .iter() - .map(|id| Identifier::from_string( - id, - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - )) + .map(|id| { + Identifier::from_string( + id, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) + }) .collect::, _>>()?; - + // Contract ID is not used in the individual key queries, but we validate it let _contract_identifier = Identifier::from_string( contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Convert purposes if provided let purposes_opt = purposes.map(|p| { p.into_iter() @@ -1325,32 +1473,38 @@ pub async fn get_identities_contract_keys_with_proof_info( }) .collect::>() }); - + // For now, we'll implement this by fetching keys for each identity individually with proof // The SDK doesn't fully expose the batch query with proof yet let mut all_responses: Vec = Vec::new(); let mut combined_metadata: Option = None; let mut combined_proof: Option = None; - + for identity_id_str in identities_ids { let identity_id = Identifier::from_string( &identity_id_str, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Get keys for this identity using the regular identity keys query with proof - let (keys_result, metadata, proof) = IdentityPublicKey::fetch_many_with_metadata_and_proof(sdk.as_ref(), identity_id, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch keys for identity {} with proof: {}", identity_id_str, e)))?; - + let (keys_result, metadata, proof) = + IdentityPublicKey::fetch_many_with_metadata_and_proof(sdk.as_ref(), identity_id, None) + .await + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch keys for identity {} with proof: {}", + identity_id_str, e + )) + })?; + // Store first metadata and proof if combined_metadata.is_none() { combined_metadata = Some(metadata.into()); combined_proof = Some(proof.into()); } - + let mut identity_keys = Vec::new(); - + // Filter keys by purpose if specified for (key_id, key_opt) in keys_result { if let Some(key) = key_opt { @@ -1360,7 +1514,7 @@ pub async fn get_identities_contract_keys_with_proof_info( continue; } } - + let key_response = IdentityKeyResponse { key_id: key_id, key_type: format!("{:?}", key.key_type()), @@ -1373,7 +1527,7 @@ pub async fn get_identities_contract_keys_with_proof_info( identity_keys.push(key_response); } } - + if !identity_keys.is_empty() { all_responses.push(IdentityContractKeysResponse { identity_id: identity_id_str, @@ -1381,7 +1535,7 @@ pub async fn get_identities_contract_keys_with_proof_info( }); } } - + let response = ProofMetadataResponse { data: all_responses, metadata: combined_metadata.unwrap_or_else(|| ResponseMetadata { @@ -1401,10 +1555,11 @@ pub async fn get_identities_contract_keys_with_proof_info( quorum_type: 0, }), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -1414,33 +1569,44 @@ pub async fn get_identity_token_balances_with_proof_info( identity_id: &str, token_ids: Vec, ) -> Result { - use dash_sdk::platform::tokens::identity_token_balances::IdentityTokenBalancesQuery; use dash_sdk::dpp::balances::credits::TokenAmount; - + use dash_sdk::platform::tokens::identity_token_balances::IdentityTokenBalancesQuery; + let identity_id = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Convert token IDs to Identifiers let token_identifiers: Vec = token_ids .iter() - .map(|id| Identifier::from_string( - id, - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - )) + .map(|id| { + Identifier::from_string( + id, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) + }) .collect::, _>>()?; - + let query = IdentityTokenBalancesQuery { identity_id, token_ids: token_identifiers.clone(), }; - + // Use FetchMany trait to fetch token balances with proof - let (balances, metadata, proof): (dash_sdk::query_types::identity_token_balance::IdentityTokenBalances, _, _) = TokenAmount::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) + let (balances, metadata, proof): ( + dash_sdk::query_types::identity_token_balance::IdentityTokenBalances, + _, + _, + ) = TokenAmount::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) .await - .map_err(|e| JsError::new(&format!("Failed to fetch identity token balances with proof: {}", e)))?; - + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch identity token balances with proof: {}", + e + )) + })?; + // Convert to response format let responses: Vec = token_identifiers .into_iter() @@ -1454,15 +1620,16 @@ pub async fn get_identity_token_balances_with_proof_info( }) }) .collect(); - + let response = ProofMetadataResponse { data: responses, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) -} \ No newline at end of file +} diff --git a/packages/wasm-sdk/src/queries/mod.rs b/packages/wasm-sdk/src/queries/mod.rs index 8e9d6f70c0..5b75050d0b 100644 --- a/packages/wasm-sdk/src/queries/mod.rs +++ b/packages/wasm-sdk/src/queries/mod.rs @@ -1,27 +1,27 @@ -pub mod identity; pub mod data_contract; pub mod document; pub mod dpns; -pub mod protocol; pub mod epoch; -pub mod token; -pub mod voting; pub mod group; +pub mod identity; +pub mod protocol; pub mod system; +pub mod token; +pub mod voting; // Re-export all query functions for easy access -pub use identity::*; pub use data_contract::*; pub use document::*; pub use dpns::*; -pub use protocol::*; pub use epoch::*; -pub use token::*; -pub use voting::*; pub use group::*; +pub use identity::*; +pub use protocol::*; pub use system::*; +pub use token::*; +pub use voting::*; -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; // Common response structure for queries with proof and metadata #[derive(Serialize, Deserialize, Debug)] @@ -71,8 +71,8 @@ impl From for ResponseMetadata { // Helper function to convert platform Proof to our ProofInfo impl From for ProofInfo { fn from(proof: dash_sdk::platform::proto::Proof) -> Self { - use base64::{Engine as _, engine::general_purpose}; - + use base64::{engine::general_purpose, Engine as _}; + ProofInfo { grovedb_proof: general_purpose::STANDARD.encode(&proof.grovedb_proof), quorum_hash: hex::encode(&proof.quorum_hash), @@ -82,4 +82,4 @@ impl From for ProofInfo { quorum_type: proof.quorum_type, } } -} \ No newline at end of file +} diff --git a/packages/wasm-sdk/src/queries/protocol.rs b/packages/wasm-sdk/src/queries/protocol.rs index 428dc8c4c2..0cfbd6eb98 100644 --- a/packages/wasm-sdk/src/queries/protocol.rs +++ b/packages/wasm-sdk/src/queries/protocol.rs @@ -1,7 +1,7 @@ use crate::sdk::WasmSdk; +use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsError, JsValue}; -use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] @@ -24,20 +24,26 @@ struct ProtocolVersionUpgradeVoteStatus { pub async fn get_protocol_version_upgrade_state(sdk: &WasmSdk) -> Result { use dash_sdk::platform::FetchMany; use drive_proof_verifier::types::ProtocolVersionVoteCount; - - let upgrade_result: drive_proof_verifier::types::ProtocolVersionUpgrades = ProtocolVersionVoteCount::fetch_many(sdk.as_ref(), ()) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch protocol version upgrade state: {}", e)))?; - + + let upgrade_result: drive_proof_verifier::types::ProtocolVersionUpgrades = + ProtocolVersionVoteCount::fetch_many(sdk.as_ref(), ()) + .await + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch protocol version upgrade state: {}", + e + )) + })?; + // Get the current protocol version from the SDK let current_version = sdk.version(); - + // Find the next version with votes let mut next_version = None; let mut activation_height = None; let mut vote_count = None; let mut threshold_reached = false; - + // The result is an IndexMap> where u32 is version and Option is activation height for (version, height_opt) in upgrade_result.iter() { if *version > current_version { @@ -49,7 +55,7 @@ pub async fn get_protocol_version_upgrade_state(sdk: &WasmSdk) -> Result Result Result { + use dash_sdk::dpp::dashcore::ProTxHash; use dash_sdk::platform::types::version_votes::MasternodeProtocolVoteEx; use drive_proof_verifier::types::MasternodeProtocolVote; - use dash_sdk::dpp::dashcore::ProTxHash; use std::str::FromStr; - + // Parse the ProTxHash let start_hash = if start_pro_tx_hash.is_empty() { None } else { - Some(ProTxHash::from_str(start_pro_tx_hash) - .map_err(|e| JsError::new(&format!("Invalid ProTxHash: {}", e)))?) + Some( + ProTxHash::from_str(start_pro_tx_hash) + .map_err(|e| JsError::new(&format!("Invalid ProTxHash: {}", e)))?, + ) }; - + let votes_result = MasternodeProtocolVote::fetch_votes(sdk.as_ref(), start_hash, Some(count)) .await .map_err(|e| JsError::new(&format!("Failed to fetch protocol version votes: {}", e)))?; - + // Convert to our response format let votes: Vec = votes_result .into_iter() @@ -96,7 +104,7 @@ pub async fn get_protocol_version_upgrade_vote_status( }) }) .collect(); - + serde_wasm_bindgen::to_value(&votes) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -104,24 +112,35 @@ pub async fn get_protocol_version_upgrade_vote_status( // Proof versions for protocol queries #[wasm_bindgen] -pub async fn get_protocol_version_upgrade_state_with_proof_info(sdk: &WasmSdk) -> Result { +pub async fn get_protocol_version_upgrade_state_with_proof_info( + sdk: &WasmSdk, +) -> Result { + use crate::queries::ProofMetadataResponse; use dash_sdk::platform::FetchMany; use drive_proof_verifier::types::ProtocolVersionVoteCount; - use crate::queries::ProofMetadataResponse; - - let (upgrade_result, metadata, proof): (drive_proof_verifier::types::ProtocolVersionUpgrades, _, _) = ProtocolVersionVoteCount::fetch_many_with_metadata_and_proof(sdk.as_ref(), (), None) + + let (upgrade_result, metadata, proof): ( + drive_proof_verifier::types::ProtocolVersionUpgrades, + _, + _, + ) = ProtocolVersionVoteCount::fetch_many_with_metadata_and_proof(sdk.as_ref(), (), None) .await - .map_err(|e| JsError::new(&format!("Failed to fetch protocol version upgrade state with proof: {}", e)))?; - + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch protocol version upgrade state with proof: {}", + e + )) + })?; + // Get the current protocol version from the SDK let current_version = sdk.version(); - + // Find the next version with votes let mut next_version = None; let mut activation_height = None; let mut vote_count = None; let mut threshold_reached = false; - + for (version, height_opt) in upgrade_result.iter() { if *version > current_version { next_version = Some(*version); @@ -131,7 +150,7 @@ pub async fn get_protocol_version_upgrade_state_with_proof_info(sdk: &WasmSdk) - break; } } - + let state = ProtocolVersionUpgradeState { current_protocol_version: current_version, next_protocol_version: next_version, @@ -139,16 +158,17 @@ pub async fn get_protocol_version_upgrade_state_with_proof_info(sdk: &WasmSdk) - vote_count, threshold_reached, }; - + let response = ProofMetadataResponse { data: state, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -161,5 +181,7 @@ pub async fn get_protocol_version_upgrade_vote_status_with_proof_info( // TODO: Implement once a proper fetch_many_with_metadata_and_proof method is available for MasternodeProtocolVote // The fetch_votes method has different parameters than fetch_many let _ = (sdk, start_pro_tx_hash, count); // Parameters will be used when implemented - Err(JsError::new("get_protocol_version_upgrade_vote_status_with_proof_info is not yet implemented")) -} \ No newline at end of file + Err(JsError::new( + "get_protocol_version_upgrade_vote_status_with_proof_info is not yet implemented", + )) +} diff --git a/packages/wasm-sdk/src/queries/system.rs b/packages/wasm-sdk/src/queries/system.rs index e264d484ff..88effc6fbb 100644 --- a/packages/wasm-sdk/src/queries/system.rs +++ b/packages/wasm-sdk/src/queries/system.rs @@ -1,8 +1,8 @@ use crate::sdk::WasmSdk; +use dash_sdk::dpp::core_types::validator_set::v0::ValidatorSetV0Getters; +use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsError, JsValue}; -use serde::{Serialize, Deserialize}; -use dash_sdk::dpp::core_types::validator_set::v0::ValidatorSetV0Getters; // Response structures for the gRPC getStatus endpoint #[derive(Serialize, Deserialize, Debug)] @@ -123,7 +123,7 @@ struct CurrentQuorumsInfo { #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct TotalCreditsResponse { - total_credits_in_platform: String, // Use String to handle large numbers + total_credits_in_platform: String, // Use String to handle large numbers } #[derive(Serialize, Deserialize, Debug)] @@ -150,67 +150,81 @@ struct PathElement { #[wasm_bindgen] pub async fn get_status(sdk: &WasmSdk) -> Result { - use dapi_grpc::platform::v0::get_status_request::{Version, GetStatusRequestV0}; + use dapi_grpc::platform::v0::get_status_request::{GetStatusRequestV0, Version}; use dapi_grpc::platform::v0::GetStatusRequest; use dash_sdk::RequestSettings; use rs_dapi_client::DapiRequestExecutor; - + // Create the gRPC request let request = GetStatusRequest { version: Some(Version::V0(GetStatusRequestV0 {})), }; - + // Execute the request let response = sdk .as_ref() .execute(request, RequestSettings::default()) .await .map_err(|e| JsError::new(&format!("Failed to get status: {}", e)))?; - + // Parse the response use dapi_grpc::platform::v0::get_status_response::Version as ResponseVersion; - + let v0_response = match response.inner.version { Some(ResponseVersion::V0(v0)) => v0, None => return Err(JsError::new("No version in GetStatus response")), }; - + // Map the response to our StatusResponse structure let status = StatusResponse { version: StatusVersion { software: StatusSoftware { - dapi: v0_response.version.as_ref() + dapi: v0_response + .version + .as_ref() .map(|v| v.software.as_ref()) .flatten() .map(|s| s.dapi.clone()) .unwrap_or_else(|| "unknown".to_string()), - drive: v0_response.version.as_ref() + drive: v0_response + .version + .as_ref() .and_then(|v| v.software.as_ref()) .and_then(|s| s.drive.clone()), - tenderdash: v0_response.version.as_ref() + tenderdash: v0_response + .version + .as_ref() .and_then(|v| v.software.as_ref()) .and_then(|s| s.tenderdash.clone()), }, protocol: StatusProtocol { tenderdash: StatusTenderdashProtocol { - p2p: v0_response.version.as_ref() + p2p: v0_response + .version + .as_ref() .and_then(|v| v.protocol.as_ref()) .and_then(|p| p.tenderdash.as_ref()) .map(|t| t.p2p) .unwrap_or(0), - block: v0_response.version.as_ref() + block: v0_response + .version + .as_ref() .and_then(|v| v.protocol.as_ref()) .and_then(|p| p.tenderdash.as_ref()) .map(|t| t.block) .unwrap_or(0), }, drive: StatusDriveProtocol { - latest: v0_response.version.as_ref() + latest: v0_response + .version + .as_ref() .and_then(|v| v.protocol.as_ref()) .and_then(|p| p.drive.as_ref()) .map(|d| d.latest) .unwrap_or(0), - current: v0_response.version.as_ref() + current: v0_response + .version + .as_ref() .and_then(|v| v.protocol.as_ref()) .and_then(|p| p.drive.as_ref()) .map(|d| d.current) @@ -219,93 +233,142 @@ pub async fn get_status(sdk: &WasmSdk) -> Result { }, }, node: StatusNode { - id: v0_response.node.as_ref() + id: v0_response + .node + .as_ref() .map(|n| hex::encode(&n.id)) .unwrap_or_else(|| "unknown".to_string()), - pro_tx_hash: v0_response.node.as_ref() + pro_tx_hash: v0_response + .node + .as_ref() .and_then(|n| n.pro_tx_hash.as_ref()) .map(|hash| hex::encode(hash)), }, chain: StatusChain { - catching_up: v0_response.chain.as_ref() + catching_up: v0_response + .chain + .as_ref() .map(|c| c.catching_up) .unwrap_or(false), - latest_block_hash: v0_response.chain.as_ref() + latest_block_hash: v0_response + .chain + .as_ref() .map(|c| hex::encode(&c.latest_block_hash)) .unwrap_or_else(|| "unknown".to_string()), - latest_app_hash: v0_response.chain.as_ref() + latest_app_hash: v0_response + .chain + .as_ref() .map(|c| hex::encode(&c.latest_app_hash)) .unwrap_or_else(|| "unknown".to_string()), - latest_block_height: v0_response.chain.as_ref() + latest_block_height: v0_response + .chain + .as_ref() .map(|c| c.latest_block_height.to_string()) .unwrap_or_else(|| "0".to_string()), - earliest_block_hash: v0_response.chain.as_ref() + earliest_block_hash: v0_response + .chain + .as_ref() .map(|c| hex::encode(&c.earliest_block_hash)) .unwrap_or_else(|| "unknown".to_string()), - earliest_app_hash: v0_response.chain.as_ref() + earliest_app_hash: v0_response + .chain + .as_ref() .map(|c| hex::encode(&c.earliest_app_hash)) .unwrap_or_else(|| "unknown".to_string()), - earliest_block_height: v0_response.chain.as_ref() + earliest_block_height: v0_response + .chain + .as_ref() .map(|c| c.earliest_block_height.to_string()) .unwrap_or_else(|| "0".to_string()), - max_peer_block_height: v0_response.chain.as_ref() + max_peer_block_height: v0_response + .chain + .as_ref() .map(|c| c.max_peer_block_height.to_string()) .unwrap_or_else(|| "0".to_string()), - core_chain_locked_height: v0_response.chain.as_ref() + core_chain_locked_height: v0_response + .chain + .as_ref() .and_then(|c| c.core_chain_locked_height), }, network: StatusNetwork { - chain_id: v0_response.network.as_ref() + chain_id: v0_response + .network + .as_ref() .map(|n| n.chain_id.clone()) .unwrap_or_else(|| "unknown".to_string()), - peers_count: v0_response.network.as_ref() + peers_count: v0_response + .network + .as_ref() .map(|n| n.peers_count) .unwrap_or(0), - listening: v0_response.network.as_ref() + listening: v0_response + .network + .as_ref() .map(|n| n.listening) .unwrap_or(false), }, state_sync: StatusStateSync { - total_synced_time: v0_response.state_sync.as_ref() + total_synced_time: v0_response + .state_sync + .as_ref() .map(|s| s.total_synced_time.to_string()) .unwrap_or_else(|| "0".to_string()), - remaining_time: v0_response.state_sync.as_ref() + remaining_time: v0_response + .state_sync + .as_ref() .map(|s| s.remaining_time.to_string()) .unwrap_or_else(|| "0".to_string()), - total_snapshots: v0_response.state_sync.as_ref() + total_snapshots: v0_response + .state_sync + .as_ref() .map(|s| s.total_snapshots) .unwrap_or(0), - chunk_process_avg_time: v0_response.state_sync.as_ref() + chunk_process_avg_time: v0_response + .state_sync + .as_ref() .map(|s| s.chunk_process_avg_time.to_string()) .unwrap_or_else(|| "0".to_string()), - snapshot_height: v0_response.state_sync.as_ref() + snapshot_height: v0_response + .state_sync + .as_ref() .map(|s| s.snapshot_height.to_string()) .unwrap_or_else(|| "0".to_string()), - snapshot_chunks_count: v0_response.state_sync.as_ref() + snapshot_chunks_count: v0_response + .state_sync + .as_ref() .map(|s| s.snapshot_chunks_count.to_string()) .unwrap_or_else(|| "0".to_string()), - backfilled_blocks: v0_response.state_sync.as_ref() + backfilled_blocks: v0_response + .state_sync + .as_ref() .map(|s| s.backfilled_blocks.to_string()) .unwrap_or_else(|| "0".to_string()), - backfill_blocks_total: v0_response.state_sync.as_ref() + backfill_blocks_total: v0_response + .state_sync + .as_ref() .map(|s| s.backfill_blocks_total.to_string()) .unwrap_or_else(|| "0".to_string()), }, time: StatusTime { - local: v0_response.time.as_ref() + local: v0_response + .time + .as_ref() .map(|t| t.local.to_string()) .unwrap_or_else(|| "0".to_string()), - block: v0_response.time.as_ref() + block: v0_response + .time + .as_ref() .and_then(|t| t.block) .map(|b| b.to_string()), - genesis: v0_response.time.as_ref() + genesis: v0_response + .time + .as_ref() .and_then(|t| t.genesis) .map(|g| g.to_string()), - epoch: v0_response.time.as_ref() - .and_then(|t| t.epoch), + epoch: v0_response.time.as_ref().and_then(|t| t.epoch), }, }; - + serde_wasm_bindgen::to_value(&status) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -313,32 +376,31 @@ pub async fn get_status(sdk: &WasmSdk) -> Result { #[wasm_bindgen] pub async fn get_current_quorums_info(sdk: &WasmSdk) -> Result { use dash_sdk::platform::FetchUnproved; - use drive_proof_verifier::types::{NoParamQuery, CurrentQuorumsInfo as SdkCurrentQuorumsInfo}; - + use drive_proof_verifier::types::{CurrentQuorumsInfo as SdkCurrentQuorumsInfo, NoParamQuery}; + let quorums_result = SdkCurrentQuorumsInfo::fetch_unproved(sdk.as_ref(), NoParamQuery {}) .await .map_err(|e| JsError::new(&format!("Failed to fetch quorums info: {}", e)))?; - + // The result is Option if let Some(quorum_info) = quorums_result { // Convert the SDK response to our structure // Match quorum hashes with validator sets to get detailed information - let quorums: Vec = quorum_info.quorum_hashes + let quorums: Vec = quorum_info + .quorum_hashes .into_iter() .map(|quorum_hash| { // Try to find the corresponding validator set - let validator_set = quorum_info.validator_sets - .iter() - .find(|vs| { - // Compare the quorum hash bytes directly - - let vs_hash_bytes: &[u8] = vs.quorum_hash().as_ref(); - vs_hash_bytes == &quorum_hash[..] - }); - + let validator_set = quorum_info.validator_sets.iter().find(|vs| { + // Compare the quorum hash bytes directly + + let vs_hash_bytes: &[u8] = vs.quorum_hash().as_ref(); + vs_hash_bytes == &quorum_hash[..] + }); + if let Some(vs) = validator_set { let member_count = vs.members().len() as u32; - + // Determine quorum type based on member count and quorum index // This is an approximation based on common quorum sizes // TODO: Get actual quorum type from the platform when available @@ -346,9 +408,12 @@ pub async fn get_current_quorums_info(sdk: &WasmSdk) -> Result 50..=70 => ("LLMQ_60_75".to_string(), (member_count * 75 / 100).max(1)), 90..=110 => ("LLMQ_100_67".to_string(), (member_count * 67 / 100).max(1)), 350..=450 => ("LLMQ_400_60".to_string(), (member_count * 60 / 100).max(1)), - _ => ("LLMQ_TYPE_UNKNOWN".to_string(), (member_count * 2 / 3).max(1)), + _ => ( + "LLMQ_TYPE_UNKNOWN".to_string(), + (member_count * 2 / 3).max(1), + ), }; - + QuorumInfo { quorum_hash: hex::encode(&quorum_hash), quorum_type, @@ -370,12 +435,12 @@ pub async fn get_current_quorums_info(sdk: &WasmSdk) -> Result } }) .collect(); - + let info = CurrentQuorumsInfo { quorums, height: quorum_info.last_platform_block_height, }; - + serde_wasm_bindgen::to_value(&info) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } else { @@ -384,7 +449,7 @@ pub async fn get_current_quorums_info(sdk: &WasmSdk) -> Result quorums: vec![], height: 0, }; - + serde_wasm_bindgen::to_value(&info) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -393,12 +458,12 @@ pub async fn get_current_quorums_info(sdk: &WasmSdk) -> Result #[wasm_bindgen] pub async fn get_total_credits_in_platform(sdk: &WasmSdk) -> Result { use dash_sdk::platform::Fetch; - use drive_proof_verifier::types::{TotalCreditsInPlatform as TotalCreditsQuery, NoParamQuery}; - + use drive_proof_verifier::types::{NoParamQuery, TotalCreditsInPlatform as TotalCreditsQuery}; + let total_credits_result = TotalCreditsQuery::fetch(sdk.as_ref(), NoParamQuery {}) .await .map_err(|e| JsError::new(&format!("Failed to fetch total credits: {}", e)))?; - + // TotalCreditsInPlatform is likely a newtype wrapper around u64 let credits_value = if let Some(credits) = total_credits_result { // Extract the inner value - assuming it has a field or can be dereferenced @@ -407,14 +472,15 @@ pub async fn get_total_credits_in_platform(sdk: &WasmSdk) -> Result Result { - use dash_sdk::platform::{Identifier, Fetch}; + use dash_sdk::platform::{Fetch, Identifier}; use drive_proof_verifier::types::PrefundedSpecializedBalance as PrefundedBalance; - + // Parse identity ID let identity_identifier = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Fetch prefunded specialized balance let balance_result = PrefundedBalance::fetch(sdk.as_ref(), identity_identifier) .await - .map_err(|e| JsError::new(&format!("Failed to fetch prefunded specialized balance: {}", e)))?; - + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch prefunded specialized balance: {}", + e + )) + })?; + if let Some(balance) = balance_result { let response = PrefundedSpecializedBalance { identity_id: identity_id.to_string(), balance: balance.0, // PrefundedSpecializedBalance is a newtype wrapper around u64 }; - + // Use json_compatible serializer - let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + let serializer = serde_wasm_bindgen::Serializer::json_compatible(); + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } else { // Return zero balance if not found @@ -453,10 +525,11 @@ pub async fn get_prefunded_specialized_balance( identity_id: identity_id.to_string(), balance: 0, }; - + // Use json_compatible serializer - let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + let serializer = serde_wasm_bindgen::Serializer::json_compatible(); + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } } @@ -470,14 +543,14 @@ pub async fn wait_for_state_transition_result( Version, WaitForStateTransitionResultRequestV0, }; use dapi_grpc::platform::v0::WaitForStateTransitionResultRequest; - + use dash_sdk::RequestSettings; use rs_dapi_client::DapiRequestExecutor; - + // Parse the hash from hex string to bytes let hash_bytes = hex::decode(state_transition_hash) .map_err(|e| JsError::new(&format!("Invalid state transition hash: {}", e)))?; - + // Create the gRPC request let request = WaitForStateTransitionResultRequest { version: Some(Version::V0(WaitForStateTransitionResultRequestV0 { @@ -485,41 +558,52 @@ pub async fn wait_for_state_transition_result( prove: sdk.prove(), })), }; - + // Execute the request let response = sdk .as_ref() .execute(request, RequestSettings::default()) .await - .map_err(|e| JsError::new(&format!("Failed to wait for state transition result: {}", e)))?; - + .map_err(|e| { + JsError::new(&format!( + "Failed to wait for state transition result: {}", + e + )) + })?; + // Parse the response use dapi_grpc::platform::v0::wait_for_state_transition_result_response::{ wait_for_state_transition_result_response_v0::Result as V0Result, Version as ResponseVersion, }; - + let (status, error) = match response.inner.version { Some(ResponseVersion::V0(v0)) => match v0.result { Some(V0Result::Error(e)) => { let error_message = format!("Code: {}, Message: {}", e.code, e.message); ("ERROR".to_string(), Some(error_message)) - }, + } Some(V0Result::Proof(_)) => { // State transition was successful ("SUCCESS".to_string(), None) - }, - None => ("UNKNOWN".to_string(), Some("No result returned".to_string())), + } + None => ( + "UNKNOWN".to_string(), + Some("No result returned".to_string()), + ), }, - None => ("UNKNOWN".to_string(), Some("No version in response".to_string())), + None => ( + "UNKNOWN".to_string(), + Some("No version in response".to_string()), + ), }; - + let result = StateTransitionResult { state_transition_hash: state_transition_hash.to_string(), status, error, }; - + serde_wasm_bindgen::to_value(&result) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -530,13 +614,14 @@ pub async fn get_path_elements( path: Vec, keys: Vec, ) -> Result { - use dash_sdk::platform::FetchMany; - use drive_proof_verifier::types::{KeysInPath, Elements}; use dash_sdk::drive::grovedb::Element; - + use dash_sdk::platform::FetchMany; + use drive_proof_verifier::types::{Elements, KeysInPath}; + // Convert string path to byte vectors // Path elements can be either numeric values (like "96" for Balances) or string keys - let path_bytes: Vec> = path.iter() + let path_bytes: Vec> = path + .iter() .map(|p| { // Try to parse as a u8 number first (for root tree paths) if let Ok(num) = p.parse::() { @@ -547,28 +632,28 @@ pub async fn get_path_elements( } }) .collect(); - + // Convert string keys to byte vectors - let key_bytes: Vec> = keys.iter() - .map(|k| k.as_bytes().to_vec()) - .collect(); - + let key_bytes: Vec> = keys.iter().map(|k| k.as_bytes().to_vec()).collect(); + // Create the query let query = KeysInPath { path: path_bytes, keys: key_bytes, }; - + // Fetch path elements let path_elements_result: Elements = Element::fetch_many(sdk.as_ref(), query) .await .map_err(|e| JsError::new(&format!("Failed to fetch path elements: {}", e)))?; - + // Convert the result to our response format - let elements: Vec = keys.into_iter() + let elements: Vec = keys + .into_iter() .map(|key| { // Check if this key exists in the result - let value = path_elements_result.get(key.as_bytes()) + let value = path_elements_result + .get(key.as_bytes()) .and_then(|element_opt| element_opt.as_ref()) .and_then(|element| { // Element can contain different types, we'll serialize it as base64 @@ -577,14 +662,14 @@ pub async fn get_path_elements( base64::engine::general_purpose::STANDARD.encode(bytes) }) }); - + PathElement { path: vec![key], value, } }) .collect(); - + serde_wasm_bindgen::to_value(&elements) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -592,15 +677,20 @@ pub async fn get_path_elements( // Proof versions for system queries #[wasm_bindgen] -pub async fn get_total_credits_in_platform_with_proof_info(sdk: &WasmSdk) -> Result { - use dash_sdk::platform::Fetch; - use drive_proof_verifier::types::{TotalCreditsInPlatform as TotalCreditsQuery, NoParamQuery}; +pub async fn get_total_credits_in_platform_with_proof_info( + sdk: &WasmSdk, +) -> Result { use crate::queries::ProofMetadataResponse; - - let (total_credits_result, metadata, proof) = TotalCreditsQuery::fetch_with_metadata_and_proof(sdk.as_ref(), NoParamQuery {}, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch total credits with proof: {}", e)))?; - + use dash_sdk::platform::Fetch; + use drive_proof_verifier::types::{NoParamQuery, TotalCreditsInPlatform as TotalCreditsQuery}; + + let (total_credits_result, metadata, proof) = + TotalCreditsQuery::fetch_with_metadata_and_proof(sdk.as_ref(), NoParamQuery {}, None) + .await + .map_err(|e| { + JsError::new(&format!("Failed to fetch total credits with proof: {}", e)) + })?; + let data = if let Some(credits) = total_credits_result { Some(TotalCreditsResponse { total_credits_in_platform: credits.0.to_string(), @@ -608,16 +698,17 @@ pub async fn get_total_credits_in_platform_with_proof_info(sdk: &WasmSdk) -> Res } else { None }; - + let response = ProofMetadataResponse { data, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -626,35 +717,42 @@ pub async fn get_prefunded_specialized_balance_with_proof_info( sdk: &WasmSdk, identity_id: &str, ) -> Result { - use dash_sdk::platform::{Identifier, Fetch}; - use drive_proof_verifier::types::PrefundedSpecializedBalance as PrefundedBalance; use crate::queries::ProofMetadataResponse; - + use dash_sdk::platform::{Fetch, Identifier}; + use drive_proof_verifier::types::PrefundedSpecializedBalance as PrefundedBalance; + // Parse identity ID let identity_identifier = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Fetch prefunded specialized balance with proof - let (balance_result, metadata, proof) = PrefundedBalance::fetch_with_metadata_and_proof(sdk.as_ref(), identity_identifier, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch prefunded specialized balance with proof: {}", e)))?; - + let (balance_result, metadata, proof) = + PrefundedBalance::fetch_with_metadata_and_proof(sdk.as_ref(), identity_identifier, None) + .await + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch prefunded specialized balance with proof: {}", + e + )) + })?; + let data = PrefundedSpecializedBalance { identity_id: identity_id.to_string(), balance: balance_result.map(|b| b.0).unwrap_or(0), }; - + let response = ProofMetadataResponse { data, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -664,14 +762,15 @@ pub async fn get_path_elements_with_proof_info( path: Vec, keys: Vec, ) -> Result { + use crate::queries::ProofMetadataResponse; + use dash_sdk::drive::grovedb::Element; use dash_sdk::platform::FetchMany; use drive_proof_verifier::types::KeysInPath; - use dash_sdk::drive::grovedb::Element; - use crate::queries::ProofMetadataResponse; - + // Convert string path to byte vectors // Path elements can be either numeric values (like "96" for Balances) or string keys - let path_bytes: Vec> = path.iter() + let path_bytes: Vec> = path + .iter() .map(|p| { // Try to parse as a u8 number first (for root tree paths) if let Ok(num) = p.parse::() { @@ -682,27 +781,30 @@ pub async fn get_path_elements_with_proof_info( } }) .collect(); - + // Convert string keys to byte vectors - let key_bytes: Vec> = keys.iter() - .map(|k| k.as_bytes().to_vec()) - .collect(); - + let key_bytes: Vec> = keys.iter().map(|k| k.as_bytes().to_vec()).collect(); + // Create the query let query = KeysInPath { path: path_bytes, keys: key_bytes, }; - + // Fetch path elements with proof - let (path_elements_result, metadata, proof) = Element::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch path elements with proof: {}", e)))?; - + let (path_elements_result, metadata, proof) = + Element::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) + .await + .map_err(|e| { + JsError::new(&format!("Failed to fetch path elements with proof: {}", e)) + })?; + // Convert the result to our response format - let elements: Vec = keys.into_iter() + let elements: Vec = keys + .into_iter() .map(|key| { - let value = path_elements_result.get(key.as_bytes()) + let value = path_elements_result + .get(key.as_bytes()) .and_then(|element_opt| element_opt.as_ref()) .and_then(|element| { element.as_item_bytes().ok().map(|bytes| { @@ -710,22 +812,23 @@ pub async fn get_path_elements_with_proof_info( base64::engine::general_purpose::STANDARD.encode(bytes) }) }); - + PathElement { path: vec![key], value, } }) .collect(); - + let response = ProofMetadataResponse { data: elements, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) -} \ No newline at end of file +} diff --git a/packages/wasm-sdk/src/queries/token.rs b/packages/wasm-sdk/src/queries/token.rs index a754b5a424..b2f18ae10c 100644 --- a/packages/wasm-sdk/src/queries/token.rs +++ b/packages/wasm-sdk/src/queries/token.rs @@ -1,66 +1,69 @@ -use crate::sdk::WasmSdk; -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::{JsError, JsValue}; -use serde::{Serialize, Deserialize}; use crate::queries::ProofMetadataResponse; -use dash_sdk::platform::{Identifier, FetchMany}; +use crate::sdk::WasmSdk; use dash_sdk::dpp::balances::credits::TokenAmount; -use dash_sdk::dpp::tokens::status::TokenStatus; -use dash_sdk::dpp::tokens::status::v0::TokenStatusV0Accessors; +use dash_sdk::dpp::tokens::calculate_token_id; use dash_sdk::dpp::tokens::info::IdentityTokenInfo; +use dash_sdk::dpp::tokens::status::v0::TokenStatusV0Accessors; +use dash_sdk::dpp::tokens::status::TokenStatus; use dash_sdk::dpp::tokens::token_pricing_schedule::TokenPricingSchedule; -use dash_sdk::dpp::tokens::calculate_token_id; +use dash_sdk::platform::{FetchMany, Identifier}; +use serde::{Deserialize, Serialize}; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::{JsError, JsValue}; /// Calculate token ID from contract ID and token position -/// +/// /// This function calculates the unique token ID based on a data contract ID /// and the position of the token within that contract. -/// +/// /// # Arguments /// * `contract_id` - The data contract ID in base58 format /// * `token_position` - The position of the token in the contract (0-indexed) -/// +/// /// # Returns /// The calculated token ID in base58 format -/// +/// /// # Example /// ```javascript /// const tokenId = await sdk.calculateTokenId("Hqyu8WcRwXCTwbNxdga4CN5gsVEGc67wng4TFzceyLUv", 0); /// ``` #[wasm_bindgen] -pub fn calculate_token_id_from_contract(contract_id: &str, token_position: u16) -> Result { +pub fn calculate_token_id_from_contract( + contract_id: &str, + token_position: u16, +) -> Result { // Parse contract ID let contract_identifier = Identifier::from_string( contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Calculate token ID let token_id = Identifier::from(calculate_token_id( contract_identifier.as_bytes(), token_position, )); - + // Return as base58 string Ok(token_id.to_string(dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58)) } /// Get the current price of a token by contract ID and position -/// +/// /// This is a convenience function that calculates the token ID from the contract ID /// and position, then fetches the current pricing schedule for that token. -/// +/// /// # Arguments /// * `sdk` - The WasmSdk instance /// * `contract_id` - The data contract ID in base58 format /// * `token_position` - The position of the token in the contract (0-indexed) -/// +/// /// # Returns /// An object containing: /// - `tokenId`: The calculated token ID /// - `currentPrice`: The current price of the token /// - `basePrice`: The base price of the token (may be same as current for single price) -/// +/// /// # Example /// ```javascript /// const priceInfo = await sdk.getTokenPriceByContract( @@ -78,19 +81,19 @@ pub async fn get_token_price_by_contract( ) -> Result { // Calculate token ID let token_id_string = calculate_token_id_from_contract(contract_id, token_position)?; - + // Parse token ID for the query let token_identifier = Identifier::from_string( &token_id_string, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Fetch token prices - let prices_result: drive_proof_verifier::types::TokenDirectPurchasePrices = + let prices_result: drive_proof_verifier::types::TokenDirectPurchasePrices = TokenPricingSchedule::fetch_many(sdk.as_ref(), &[token_identifier][..]) .await .map_err(|e| JsError::new(&format!("Failed to fetch token price: {}", e)))?; - + // Extract price information if let Some(price_opt) = prices_result.get(&token_identifier) { if let Some(schedule) = price_opt.as_ref() { @@ -109,20 +112,26 @@ pub async fn get_token_price_by_contract( (base, current) }, }; - + let response = TokenPriceResponse { token_id: token_id_string, current_price, base_price, }; - + serde_wasm_bindgen::to_value(&response) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } else { - Err(JsError::new(&format!("No pricing schedule found for token at contract {} position {}", contract_id, token_position))) + Err(JsError::new(&format!( + "No pricing schedule found for token at contract {} position {}", + contract_id, token_position + ))) } } else { - Err(JsError::new(&format!("Token not found at contract {} position {}", contract_id, token_position))) + Err(JsError::new(&format!( + "Token not found at contract {} position {}", + contract_id, token_position + ))) } } @@ -130,7 +139,7 @@ pub async fn get_token_price_by_contract( #[serde(rename_all = "camelCase")] struct IdentityTokenBalanceResponse { identity_id: String, - balance: String, // String to handle large numbers + balance: String, // String to handle large numbers } #[wasm_bindgen] @@ -141,34 +150,36 @@ pub async fn get_identities_token_balances( ) -> Result { use dash_sdk::platform::tokens::identity_token_balances::IdentitiesTokenBalancesQuery; use drive_proof_verifier::types::identity_token_balance::IdentitiesTokenBalances; - + // Parse token ID let token_identifier = Identifier::from_string( token_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Parse identity IDs let identities: Result, _> = identity_ids .iter() - .map(|id| Identifier::from_string( - id, - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - )) + .map(|id| { + Identifier::from_string( + id, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) + }) .collect(); let identities = identities?; - + // Create query let query = IdentitiesTokenBalancesQuery { identity_ids: identities.clone(), token_id: token_identifier, }; - + // Fetch balances let balances_result: IdentitiesTokenBalances = TokenAmount::fetch_many(sdk.as_ref(), query) .await .map_err(|e| JsError::new(&format!("Failed to fetch identities token balances: {}", e)))?; - + // Convert to response format let responses: Vec = identity_ids .into_iter() @@ -176,19 +187,18 @@ pub async fn get_identities_token_balances( let id = Identifier::from_string( &id_str, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - ).ok()?; - + ) + .ok()?; + balances_result.get(&id).and_then(|balance_opt| { - balance_opt.map(|balance| { - IdentityTokenBalanceResponse { - identity_id: id_str, - balance: balance.to_string(), - } + balance_opt.map(|balance| IdentityTokenBalanceResponse { + identity_id: id_str, + balance: balance.to_string(), }) }) }) .collect(); - + serde_wasm_bindgen::to_value(&responses) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -210,37 +220,40 @@ pub async fn get_identity_token_infos( ) -> Result { use dash_sdk::platform::tokens::token_info::IdentityTokenInfosQuery; use drive_proof_verifier::types::token_info::IdentityTokenInfos; - + // Parse identity ID let identity_identifier = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // If no token IDs specified, we can't query (SDK requires specific token IDs) - let token_id_strings = token_ids.ok_or_else(|| JsError::new("token_ids are required for this query"))?; - + let token_id_strings = + token_ids.ok_or_else(|| JsError::new("token_ids are required for this query"))?; + // Parse token IDs let tokens: Result, _> = token_id_strings .iter() - .map(|id| Identifier::from_string( - id, - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - )) + .map(|id| { + Identifier::from_string( + id, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) + }) .collect(); let tokens = tokens?; - + // Create query let query = IdentityTokenInfosQuery { identity_id: identity_identifier, token_ids: tokens.clone(), }; - + // Fetch token infos let infos_result: IdentityTokenInfos = IdentityTokenInfo::fetch_many(sdk.as_ref(), query) .await .map_err(|e| JsError::new(&format!("Failed to fetch identity token infos: {}", e)))?; - + // Convert to response format let responses: Vec = token_id_strings .into_iter() @@ -248,17 +261,18 @@ pub async fn get_identity_token_infos( let id = Identifier::from_string( &id_str, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - ).ok()?; - + ) + .ok()?; + infos_result.get(&id).and_then(|info_opt| { info_opt.as_ref().map(|info| { use dash_sdk::dpp::tokens::info::v0::IdentityTokenInfoV0Accessors; - + // IdentityTokenInfo only contains frozen status let is_frozen = match &info { dash_sdk::dpp::tokens::info::IdentityTokenInfo::V0(v0) => v0.frozen(), }; - + TokenInfoResponse { token_id: id_str, is_frozen, @@ -267,7 +281,7 @@ pub async fn get_identity_token_infos( }) }) .collect(); - + serde_wasm_bindgen::to_value(&responses) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -287,34 +301,36 @@ pub async fn get_identities_token_infos( ) -> Result { use dash_sdk::platform::tokens::token_info::IdentitiesTokenInfosQuery; use drive_proof_verifier::types::token_info::IdentitiesTokenInfos; - + // Parse token ID let token_identifier = Identifier::from_string( token_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Parse identity IDs let identities: Result, _> = identity_ids .iter() - .map(|id| Identifier::from_string( - id, - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - )) + .map(|id| { + Identifier::from_string( + id, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) + }) .collect(); let identities = identities?; - + // Create query let query = IdentitiesTokenInfosQuery { identity_ids: identities.clone(), token_id: token_identifier, }; - + // Fetch token infos let infos_result: IdentitiesTokenInfos = IdentityTokenInfo::fetch_many(sdk.as_ref(), query) .await .map_err(|e| JsError::new(&format!("Failed to fetch identities token infos: {}", e)))?; - + // Convert to response format let responses: Vec = identity_ids .into_iter() @@ -322,17 +338,18 @@ pub async fn get_identities_token_infos( let id = Identifier::from_string( &id_str, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - ).ok()?; - + ) + .ok()?; + infos_result.get(&id).and_then(|info_opt| { info_opt.as_ref().map(|info| { use dash_sdk::dpp::tokens::info::v0::IdentityTokenInfoV0Accessors; - + // IdentityTokenInfo only contains frozen status let is_frozen = match &info { dash_sdk::dpp::tokens::info::IdentityTokenInfo::V0(v0) => v0.frozen(), }; - + IdentityTokenInfoResponse { identity_id: id_str, is_frozen, @@ -341,7 +358,7 @@ pub async fn get_identities_token_infos( }) }) .collect(); - + serde_wasm_bindgen::to_value(&responses) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -356,22 +373,24 @@ struct TokenStatusResponse { #[wasm_bindgen] pub async fn get_token_statuses(sdk: &WasmSdk, token_ids: Vec) -> Result { use drive_proof_verifier::types::token_status::TokenStatuses; - + // Parse token IDs let tokens: Result, _> = token_ids .iter() - .map(|id| Identifier::from_string( - id, - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - )) + .map(|id| { + Identifier::from_string( + id, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) + }) .collect(); let tokens = tokens?; - + // Fetch token statuses let statuses_result: TokenStatuses = TokenStatus::fetch_many(sdk.as_ref(), tokens.clone()) .await .map_err(|e| JsError::new(&format!("Failed to fetch token statuses: {}", e)))?; - + // Convert to response format let responses: Vec = token_ids .into_iter() @@ -379,19 +398,18 @@ pub async fn get_token_statuses(sdk: &WasmSdk, token_ids: Vec) -> Result let id = Identifier::from_string( &id_str, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - ).ok()?; - + ) + .ok()?; + statuses_result.get(&id).and_then(|status_opt| { - status_opt.as_ref().map(|status| { - TokenStatusResponse { - token_id: id_str, - is_paused: status.paused(), - } + status_opt.as_ref().map(|status| TokenStatusResponse { + token_id: id_str, + is_paused: status.paused(), }) }) }) .collect(); - + serde_wasm_bindgen::to_value(&responses) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -405,24 +423,35 @@ struct TokenPriceResponse { } #[wasm_bindgen] -pub async fn get_token_direct_purchase_prices(sdk: &WasmSdk, token_ids: Vec) -> Result { +pub async fn get_token_direct_purchase_prices( + sdk: &WasmSdk, + token_ids: Vec, +) -> Result { use drive_proof_verifier::types::TokenDirectPurchasePrices; - + // Parse token IDs let tokens: Result, _> = token_ids .iter() - .map(|id| Identifier::from_string( - id, - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - )) + .map(|id| { + Identifier::from_string( + id, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) + }) .collect(); let tokens = tokens?; - + // Fetch token prices - use slice reference - let prices_result: TokenDirectPurchasePrices = TokenPricingSchedule::fetch_many(sdk.as_ref(), &tokens[..]) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch token direct purchase prices: {}", e)))?; - + let prices_result: TokenDirectPurchasePrices = + TokenPricingSchedule::fetch_many(sdk.as_ref(), &tokens[..]) + .await + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch token direct purchase prices: {}", + e + )) + })?; + // Convert to response format let responses: Vec = token_ids .into_iter() @@ -431,7 +460,7 @@ pub async fn get_token_direct_purchase_prices(sdk: &WasmSdk, token_ids: Vec Result { +pub async fn get_token_contract_info( + sdk: &WasmSdk, + data_contract_id: &str, +) -> Result { use dash_sdk::dpp::tokens::contract_info::TokenContractInfo; use dash_sdk::platform::Fetch; - + // Parse contract ID let contract_id = Identifier::from_string( data_contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Fetch token contract info let info_result = TokenContractInfo::fetch(sdk.as_ref(), contract_id) .await .map_err(|e| JsError::new(&format!("Failed to fetch token contract info: {}", e)))?; - + if let Some(info) = info_result { use dash_sdk::dpp::tokens::contract_info::v0::TokenContractInfoV0Accessors; - + // Extract fields based on the enum variant let (contract_id, position) = match &info { dash_sdk::dpp::tokens::contract_info::TokenContractInfo::V0(v0) => { (v0.contract_id(), v0.token_contract_position()) - }, + } }; - + let response = TokenContractInfoResponse { - contract_id: contract_id.to_string(dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58), + contract_id: contract_id + .to_string(dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58), token_contract_position: position, }; - + // Use json_compatible serializer - let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + let serializer = serde_wasm_bindgen::Serializer::json_compatible(); + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } else { Ok(JsValue::NULL) @@ -525,43 +559,50 @@ pub async fn get_token_perpetual_distribution_last_claim( identity_id: &str, token_id: &str, ) -> Result { - // Parse IDs let identity_identifier = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + let token_identifier = Identifier::from_string( token_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Use direct gRPC request instead of high-level SDK fetch to avoid proof verification issues use dapi_grpc::platform::v0::{ - GetTokenPerpetualDistributionLastClaimRequest, get_token_perpetual_distribution_last_claim_request::{ - Version, GetTokenPerpetualDistributionLastClaimRequestV0 - } + GetTokenPerpetualDistributionLastClaimRequestV0, Version, + }, + GetTokenPerpetualDistributionLastClaimRequest, }; use rs_dapi_client::DapiRequestExecutor; - + // Create direct gRPC Request without proofs to avoid context provider issues let request = GetTokenPerpetualDistributionLastClaimRequest { - version: Some(Version::V0(GetTokenPerpetualDistributionLastClaimRequestV0 { - token_id: token_identifier.to_vec(), - identity_id: identity_identifier.to_vec(), - contract_info: None, // Not needed for this query - prove: false, // Use prove: false to avoid proof verification and context provider dependency - })), + version: Some(Version::V0( + GetTokenPerpetualDistributionLastClaimRequestV0 { + token_id: token_identifier.to_vec(), + identity_id: identity_identifier.to_vec(), + contract_info: None, // Not needed for this query + prove: false, // Use prove: false to avoid proof verification and context provider dependency + }, + )), }; - + // Execute the gRPC request - let response = sdk.inner_sdk() + let response = sdk + .inner_sdk() .execute(request, rs_dapi_client::RequestSettings::default()) .await - .map_err(|e| JsError::new(&format!("Failed to fetch token perpetual distribution last claim: {}", e)))?; - + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch token perpetual distribution last claim: {}", + e + )) + })?; + // Extract result from response and convert to our expected format let claim_result = match response.inner.version { Some(dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::Version::V0(v0)) => { @@ -590,7 +631,7 @@ pub async fn get_token_perpetual_distribution_last_claim( if bytes.len() >= 8 { let timestamp = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as u64; let block_height = u32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]) as u64; - + // Validate timestamp: must be 0 (unset) or a reasonable Unix timestamp let validated_timestamp = if timestamp != 0 && timestamp < 1609459200 { web_sys::console::warn_1(&format!("Invalid timestamp in raw bytes: {} (too early)", timestamp).into()); @@ -598,7 +639,7 @@ pub async fn get_token_perpetual_distribution_last_claim( } else { timestamp }; - + // Validate block height: must be a positive value let validated_block_height = if block_height == 0 { web_sys::console::warn_1(&"Invalid block height in raw bytes: 0 (genesis block not expected)".into()); @@ -606,15 +647,15 @@ pub async fn get_token_perpetual_distribution_last_claim( } else { block_height }; - + Some((validated_timestamp * 1000, validated_block_height)) // Convert timestamp to milliseconds } else if bytes.len() >= 4 { // Fallback: decode only the last 4 bytes as block height let block_height = u32::from_be_bytes([ - bytes[bytes.len()-4], bytes[bytes.len()-3], + bytes[bytes.len()-4], bytes[bytes.len()-3], bytes[bytes.len()-2], bytes[bytes.len()-1] ]) as u64; - + // Validate block height let validated_block_height = if block_height == 0 { web_sys::console::warn_1(&"Invalid block height in fallback parsing: 0".into()); @@ -622,7 +663,7 @@ pub async fn get_token_perpetual_distribution_last_claim( } else { block_height }; - + Some((0, validated_block_height)) } else { web_sys::console::warn_1(&format!("Insufficient raw bytes length: {} (expected 8 or 4)", bytes.len()).into()); @@ -644,16 +685,17 @@ pub async fn get_token_perpetual_distribution_last_claim( return Err(JsError::new("Invalid response version")) } }; - + if let Some((timestamp_ms, block_height)) = claim_result { let response = LastClaimResponse { last_claim_timestamp_ms: timestamp_ms, last_claim_block_height: block_height, }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } else { Ok(JsValue::NULL) @@ -670,26 +712,27 @@ struct TokenTotalSupplyResponse { pub async fn get_token_total_supply(sdk: &WasmSdk, token_id: &str) -> Result { use dash_sdk::dpp::balances::total_single_token_balance::TotalSingleTokenBalance; use dash_sdk::platform::Fetch; - + // Parse token ID let token_identifier = Identifier::from_string( token_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Fetch total supply let supply_result = TotalSingleTokenBalance::fetch(sdk.as_ref(), token_identifier) .await .map_err(|e| JsError::new(&format!("Failed to fetch token total supply: {}", e)))?; - + if let Some(supply) = supply_result { let response = TokenTotalSupplyResponse { total_supply: supply.token_supply.to_string(), }; - + // Use json_compatible serializer - let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + let serializer = serde_wasm_bindgen::Serializer::json_compatible(); + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } else { Ok(JsValue::NULL) @@ -705,34 +748,45 @@ pub async fn get_identities_token_balances_with_proof_info( token_id: &str, ) -> Result { use dash_sdk::platform::tokens::identity_token_balances::IdentitiesTokenBalancesQuery; - + // Parse token ID let token_identifier = Identifier::from_string( token_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Parse identity IDs let identities: Result, _> = identity_ids .iter() - .map(|id| Identifier::from_string( - id, - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - )) + .map(|id| { + Identifier::from_string( + id, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) + }) .collect(); let identities = identities?; - + // Create query let query = IdentitiesTokenBalancesQuery { identity_ids: identities.clone(), token_id: token_identifier, }; - + // Fetch balances with proof - let (balances_result, metadata, proof): (drive_proof_verifier::types::identity_token_balance::IdentitiesTokenBalances, _, _) = TokenAmount::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) + let (balances_result, metadata, proof): ( + drive_proof_verifier::types::identity_token_balance::IdentitiesTokenBalances, + _, + _, + ) = TokenAmount::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) .await - .map_err(|e| JsError::new(&format!("Failed to fetch identities token balances with proof: {}", e)))?; - + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch identities token balances with proof: {}", + e + )) + })?; + // Convert to response format let responses: Vec = identity_ids .into_iter() @@ -740,49 +794,56 @@ pub async fn get_identities_token_balances_with_proof_info( let id = Identifier::from_string( &id_str, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - ).ok()?; - + ) + .ok()?; + balances_result.get(&id).and_then(|balance_opt| { - balance_opt.map(|balance| { - IdentityTokenBalanceResponse { - identity_id: id_str, - balance: balance.to_string(), - } + balance_opt.map(|balance| IdentityTokenBalanceResponse { + identity_id: id_str, + balance: balance.to_string(), }) }) }) .collect(); - + let response = ProofMetadataResponse { data: responses, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } #[wasm_bindgen] -pub async fn get_token_statuses_with_proof_info(sdk: &WasmSdk, token_ids: Vec) -> Result { - +pub async fn get_token_statuses_with_proof_info( + sdk: &WasmSdk, + token_ids: Vec, +) -> Result { // Parse token IDs let tokens: Result, _> = token_ids .iter() - .map(|id| Identifier::from_string( - id, - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - )) + .map(|id| { + Identifier::from_string( + id, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) + }) .collect(); let tokens = tokens?; - + // Fetch token statuses with proof - let (statuses_result, metadata, proof) = TokenStatus::fetch_many_with_metadata_and_proof(sdk.as_ref(), tokens.clone(), None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch token statuses with proof: {}", e)))?; - + let (statuses_result, metadata, proof) = + TokenStatus::fetch_many_with_metadata_and_proof(sdk.as_ref(), tokens.clone(), None) + .await + .map_err(|e| { + JsError::new(&format!("Failed to fetch token statuses with proof: {}", e)) + })?; + // Convert to response format let responses: Vec = token_ids .into_iter() @@ -790,47 +851,59 @@ pub async fn get_token_statuses_with_proof_info(sdk: &WasmSdk, token_ids: Vec Result { +pub async fn get_token_total_supply_with_proof_info( + sdk: &WasmSdk, + token_id: &str, +) -> Result { use dash_sdk::dpp::balances::total_single_token_balance::TotalSingleTokenBalance; use dash_sdk::platform::Fetch; - + // Parse token ID let token_identifier = Identifier::from_string( token_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Fetch total supply with proof - let (supply_result, metadata, proof) = TotalSingleTokenBalance::fetch_with_metadata_and_proof(sdk.as_ref(), token_identifier, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch token total supply with proof: {}", e)))?; - + let (supply_result, metadata, proof) = TotalSingleTokenBalance::fetch_with_metadata_and_proof( + sdk.as_ref(), + token_identifier, + None, + ) + .await + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch token total supply with proof: {}", + e + )) + })?; + let data = if let Some(supply) = supply_result { Some(TokenTotalSupplyResponse { total_supply: supply.token_supply.to_string(), @@ -838,16 +911,17 @@ pub async fn get_token_total_supply_with_proof_info(sdk: &WasmSdk, token_id: &st } else { None }; - + let response = ProofMetadataResponse { data, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -863,37 +937,46 @@ pub async fn get_identity_token_infos_with_proof_info( ) -> Result { use dash_sdk::platform::tokens::token_info::IdentityTokenInfosQuery; use drive_proof_verifier::types::token_info::IdentityTokenInfos; - + // Parse identity ID let identity_identifier = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // If no token IDs specified, we can't query (SDK requires specific token IDs) - let token_id_strings = token_ids.ok_or_else(|| JsError::new("token_ids are required for this query"))?; - + let token_id_strings = + token_ids.ok_or_else(|| JsError::new("token_ids are required for this query"))?; + // Parse token IDs let tokens: Result, _> = token_id_strings .iter() - .map(|id| Identifier::from_string( - id, - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - )) + .map(|id| { + Identifier::from_string( + id, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) + }) .collect(); let tokens = tokens?; - + // Create query let query = IdentityTokenInfosQuery { identity_id: identity_identifier, token_ids: tokens.clone(), }; - + // Fetch token infos with proof - let (infos_result, metadata, proof): (IdentityTokenInfos, _, _) = IdentityTokenInfo::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch identity token infos with proof: {}", e)))?; - + let (infos_result, metadata, proof): (IdentityTokenInfos, _, _) = + IdentityTokenInfo::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) + .await + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch identity token infos with proof: {}", + e + )) + })?; + // Convert to response format let responses: Vec = token_id_strings .into_iter() @@ -901,17 +984,18 @@ pub async fn get_identity_token_infos_with_proof_info( let id = Identifier::from_string( &id_str, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - ).ok()?; - + ) + .ok()?; + infos_result.get(&id).and_then(|info_opt| { info_opt.as_ref().map(|info| { use dash_sdk::dpp::tokens::info::v0::IdentityTokenInfoV0Accessors; - + // IdentityTokenInfo only contains frozen status let is_frozen = match &info { dash_sdk::dpp::tokens::info::IdentityTokenInfo::V0(v0) => v0.frozen(), }; - + TokenInfoResponse { token_id: id_str, is_frozen, @@ -920,16 +1004,17 @@ pub async fn get_identity_token_infos_with_proof_info( }) }) .collect(); - + let response = ProofMetadataResponse { data: responses, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -941,34 +1026,42 @@ pub async fn get_identities_token_infos_with_proof_info( ) -> Result { use dash_sdk::platform::tokens::token_info::IdentitiesTokenInfosQuery; use drive_proof_verifier::types::token_info::IdentitiesTokenInfos; - + // Parse token ID let token_identifier = Identifier::from_string( token_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Parse identity IDs let identities: Result, _> = identity_ids .iter() - .map(|id| Identifier::from_string( - id, - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - )) + .map(|id| { + Identifier::from_string( + id, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) + }) .collect(); let identities = identities?; - + // Create query let query = IdentitiesTokenInfosQuery { identity_ids: identities.clone(), token_id: token_identifier, }; - + // Fetch token infos with proof - let (infos_result, metadata, proof): (IdentitiesTokenInfos, _, _) = IdentityTokenInfo::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch identities token infos with proof: {}", e)))?; - + let (infos_result, metadata, proof): (IdentitiesTokenInfos, _, _) = + IdentityTokenInfo::fetch_many_with_metadata_and_proof(sdk.as_ref(), query, None) + .await + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch identities token infos with proof: {}", + e + )) + })?; + // Convert to response format let responses: Vec = identity_ids .into_iter() @@ -976,17 +1069,18 @@ pub async fn get_identities_token_infos_with_proof_info( let id = Identifier::from_string( &id_str, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - ).ok()?; - + ) + .ok()?; + infos_result.get(&id).and_then(|info_opt| { info_opt.as_ref().map(|info| { use dash_sdk::dpp::tokens::info::v0::IdentityTokenInfoV0Accessors; - + // IdentityTokenInfo only contains frozen status let is_frozen = match &info { dash_sdk::dpp::tokens::info::IdentityTokenInfo::V0(v0) => v0.frozen(), }; - + IdentityTokenInfoResponse { identity_id: id_str, is_frozen, @@ -995,38 +1089,50 @@ pub async fn get_identities_token_infos_with_proof_info( }) }) .collect(); - + let response = ProofMetadataResponse { data: responses, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } #[wasm_bindgen] -pub async fn get_token_direct_purchase_prices_with_proof_info(sdk: &WasmSdk, token_ids: Vec) -> Result { +pub async fn get_token_direct_purchase_prices_with_proof_info( + sdk: &WasmSdk, + token_ids: Vec, +) -> Result { use drive_proof_verifier::types::TokenDirectPurchasePrices; - + // Parse token IDs let tokens: Result, _> = token_ids .iter() - .map(|id| Identifier::from_string( - id, - dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, - )) + .map(|id| { + Identifier::from_string( + id, + dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, + ) + }) .collect(); let tokens = tokens?; - + // Fetch token prices with proof - use slice reference - let (prices_result, metadata, proof): (TokenDirectPurchasePrices, _, _) = TokenPricingSchedule::fetch_many_with_metadata_and_proof(sdk.as_ref(), &tokens[..], None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch token direct purchase prices with proof: {}", e)))?; - + let (prices_result, metadata, proof): (TokenDirectPurchasePrices, _, _) = + TokenPricingSchedule::fetch_many_with_metadata_and_proof(sdk.as_ref(), &tokens[..], None) + .await + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch token direct purchase prices with proof: {}", + e + )) + })?; + // Convert to response format let responses: Vec = token_ids .into_iter() @@ -1035,7 +1141,7 @@ pub async fn get_token_direct_purchase_prices_with_proof_info(sdk: &WasmSdk, tok &id_str, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, ).ok()?; - + prices_result.get(&id).and_then(|price_opt| { price_opt.as_ref().map(|schedule| { // Get prices based on the schedule type @@ -1054,7 +1160,7 @@ pub async fn get_token_direct_purchase_prices_with_proof_info(sdk: &WasmSdk, tok (base, current) }, }; - + TokenPriceResponse { token_id: id_str, current_price, @@ -1064,62 +1170,74 @@ pub async fn get_token_direct_purchase_prices_with_proof_info(sdk: &WasmSdk, tok }) }) .collect(); - + let response = ProofMetadataResponse { data: responses, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } #[wasm_bindgen] -pub async fn get_token_contract_info_with_proof_info(sdk: &WasmSdk, data_contract_id: &str) -> Result { +pub async fn get_token_contract_info_with_proof_info( + sdk: &WasmSdk, + data_contract_id: &str, +) -> Result { use dash_sdk::dpp::tokens::contract_info::TokenContractInfo; use dash_sdk::platform::Fetch; - + // Parse contract ID let contract_id = Identifier::from_string( data_contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Fetch token contract info with proof - let (info_result, metadata, proof) = TokenContractInfo::fetch_with_metadata_and_proof(sdk.as_ref(), contract_id, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch token contract info with proof: {}", e)))?; - + let (info_result, metadata, proof) = + TokenContractInfo::fetch_with_metadata_and_proof(sdk.as_ref(), contract_id, None) + .await + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch token contract info with proof: {}", + e + )) + })?; + let data = if let Some(info) = info_result { use dash_sdk::dpp::tokens::contract_info::v0::TokenContractInfoV0Accessors; - + // Extract fields based on the enum variant let (contract_id, position) = match &info { dash_sdk::dpp::tokens::contract_info::TokenContractInfo::V0(v0) => { (v0.contract_id(), v0.token_contract_position()) - }, + } }; - + Some(TokenContractInfoResponse { - contract_id: contract_id.to_string(dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58), + contract_id: contract_id + .to_string(dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58), token_contract_position: position, }) } else { None }; - + let response = ProofMetadataResponse { data, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -1130,31 +1248,37 @@ pub async fn get_token_perpetual_distribution_last_claim_with_proof_info( token_id: &str, ) -> Result { use dash_sdk::platform::query::TokenLastClaimQuery; - use dash_sdk::dpp::data_contract::associated_token::token_perpetual_distribution::reward_distribution_moment::RewardDistributionMoment; + use dash_sdk::dpp::data_contract::associated_token::token_perpetual_distribution::reward_distribution_moment::RewardDistributionMoment; use dash_sdk::platform::Fetch; - + // Parse IDs let identity_identifier = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + let token_identifier = Identifier::from_string( token_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Create query let query = TokenLastClaimQuery { token_id: token_identifier, identity_id: identity_identifier, }; - + // Fetch last claim info with proof - let (claim_result, metadata, proof) = RewardDistributionMoment::fetch_with_metadata_and_proof(sdk.as_ref(), query, None) - .await - .map_err(|e| JsError::new(&format!("Failed to fetch token perpetual distribution last claim with proof: {}", e)))?; - + let (claim_result, metadata, proof) = + RewardDistributionMoment::fetch_with_metadata_and_proof(sdk.as_ref(), query, None) + .await + .map_err(|e| { + JsError::new(&format!( + "Failed to fetch token perpetual distribution last claim with proof: {}", + e + )) + })?; + let data = if let Some(moment) = claim_result { // Extract timestamp and block height based on the moment type // Since we need both timestamp and block height in the response, @@ -1170,7 +1294,7 @@ pub async fn get_token_perpetual_distribution_last_claim_with_proof_info( (0, epoch as u64) // Convert epoch to u64, no timestamp available }, }; - + Some(LastClaimResponse { last_claim_timestamp_ms, last_claim_block_height, @@ -1178,15 +1302,16 @@ pub async fn get_token_perpetual_distribution_last_claim_with_proof_info( } else { None }; - + let response = ProofMetadataResponse { data, metadata: metadata.into(), proof: proof.into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) -} \ No newline at end of file +} diff --git a/packages/wasm-sdk/src/queries/voting.rs b/packages/wasm-sdk/src/queries/voting.rs index 6246779f28..03e25fd1af 100644 --- a/packages/wasm-sdk/src/queries/voting.rs +++ b/packages/wasm-sdk/src/queries/voting.rs @@ -1,29 +1,31 @@ use crate::sdk::WasmSdk; -use wasm_bindgen::prelude::wasm_bindgen; -use wasm_bindgen::{JsError, JsValue}; -use serde::Serialize; -use dash_sdk::platform::Identifier; -use dash_sdk::dpp::platform_value::Value; use dapi_grpc::platform::v0::{ - GetContestedResourcesRequest, GetContestedResourceVoteStateRequest, - GetContestedResourceVotersForIdentityRequest, GetContestedResourceIdentityVotesRequest, - GetVotePollsByEndDateRequest, - get_contested_resources_request::{self, GetContestedResourcesRequestV0}, + get_contested_resource_identity_votes_request::{ + self, GetContestedResourceIdentityVotesRequestV0, + }, get_contested_resource_vote_state_request::{self, GetContestedResourceVoteStateRequestV0}, - get_contested_resource_voters_for_identity_request::{self, GetContestedResourceVotersForIdentityRequestV0}, - get_contested_resource_identity_votes_request::{self, GetContestedResourceIdentityVotesRequestV0}, + get_contested_resource_voters_for_identity_request::{ + self, GetContestedResourceVotersForIdentityRequestV0, + }, + get_contested_resources_request::{self, GetContestedResourcesRequestV0}, get_vote_polls_by_end_date_request::{self, GetVotePollsByEndDateRequestV0}, + GetContestedResourceIdentityVotesRequest, GetContestedResourceVoteStateRequest, + GetContestedResourceVotersForIdentityRequest, GetContestedResourcesRequest, + GetVotePollsByEndDateRequest, }; use dapi_grpc::platform::VersionedGrpcResponse; +use dash_sdk::dpp::platform_value::Value; +use dash_sdk::platform::Identifier; use dash_sdk::RequestSettings; use rs_dapi_client::DapiRequestExecutor; +use serde::Serialize; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::{JsError, JsValue}; // Standard bincode configuration used by Platform use dash_sdk::dpp::bincode; const BINCODE_CONFIG: bincode::config::Configuration = bincode::config::standard(); - - #[wasm_bindgen] pub async fn get_contested_resources( sdk: &WasmSdk, @@ -40,18 +42,19 @@ pub async fn get_contested_resources( data_contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Parse result_type to get start_index_values // The result_type parameter actually specifies what we want to query // For contested domain names in DPNS, we query at the "dash" parent domain level - let start_index_values = if index_name == "parentNameAndLabel" && document_type_name == "domain" { + let start_index_values = if index_name == "parentNameAndLabel" && document_type_name == "domain" + { // For DPNS domains, start at the parent domain level (e.g., "dash") vec![] // Empty to get all contested resources at any parent domain } else { // For other types, may need different index values vec![] }; - + // Create start_at_value_info if provided let start_at_value_info = start_at_value.map(|bytes| { get_contested_resources_request::get_contested_resources_request_v0::StartAtValueInfo { @@ -59,7 +62,7 @@ pub async fn get_contested_resources( start_value_included: true, } }); - + // Create the gRPC request directly let request = GetContestedResourcesRequest { version: Some(get_contested_resources_request::Version::V0( @@ -76,14 +79,14 @@ pub async fn get_contested_resources( }, )), }; - + // Execute the request let response = sdk .as_ref() .execute(request, RequestSettings::default()) .await .map_err(|e| JsError::new(&format!("Failed to get contested resources: {}", e)))?; - + // For now, return a simple response structure // The actual response parsing would require the ContestedResource type let result = serde_json::json!({ @@ -95,10 +98,11 @@ pub async fn get_contested_resources( "protocolVersion": response.inner.metadata().ok().map(|m| m.protocol_version), } }); - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - result.serialize(&serializer) + result + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -119,13 +123,13 @@ pub async fn get_contested_resource_voters_for_identity( contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Parse contestant ID let contestant_id = Identifier::from_string( contestant_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Convert JsValue index values to Vec> using bincode serialization let mut index_values_bytes: Vec> = Vec::new(); for value in index_values { @@ -140,19 +144,23 @@ pub async fn get_contested_resource_voters_for_identity( return Err(JsError::new("Index values must be strings")); } } - + // Parse start_at_voter_info if provided let start_at_identifier_info = if let Some(info_str) = start_at_voter_info { let info: serde_json::Value = serde_json::from_str(&info_str) .map_err(|e| JsError::new(&format!("Invalid start_at_voter_info JSON: {}", e)))?; - - if let (Some(start_id), Some(included)) = (info.get("startIdentifier"), info.get("startIdentifierIncluded")) { - let start_identifier = start_id.as_str() + + if let (Some(start_id), Some(included)) = ( + info.get("startIdentifier"), + info.get("startIdentifierIncluded"), + ) { + let start_identifier = start_id + .as_str() .ok_or_else(|| JsError::new("startIdentifier must be a string"))? .as_bytes() .to_vec(); let start_identifier_included = included.as_bool().unwrap_or(true); - + Some(get_contested_resource_voters_for_identity_request::get_contested_resource_voters_for_identity_request_v0::StartAtIdentifierInfo { start_identifier, start_identifier_included, @@ -163,31 +171,33 @@ pub async fn get_contested_resource_voters_for_identity( } else { None }; - + // Create the gRPC request let request = GetContestedResourceVotersForIdentityRequest { - version: Some(get_contested_resource_voters_for_identity_request::Version::V0( - GetContestedResourceVotersForIdentityRequestV0 { - contract_id: contract_id.to_vec(), - document_type_name: document_type_name.to_string(), - index_name: index_name.to_string(), - index_values: index_values_bytes, - contestant_id: contestant_id.to_vec(), - start_at_identifier_info, - count: limit, - order_ascending: order_ascending.unwrap_or(true), - prove: sdk.prove(), - }, - )), + version: Some( + get_contested_resource_voters_for_identity_request::Version::V0( + GetContestedResourceVotersForIdentityRequestV0 { + contract_id: contract_id.to_vec(), + document_type_name: document_type_name.to_string(), + index_name: index_name.to_string(), + index_values: index_values_bytes, + contestant_id: contestant_id.to_vec(), + start_at_identifier_info, + count: limit, + order_ascending: order_ascending.unwrap_or(true), + prove: sdk.prove(), + }, + ), + ), }; - + // Execute the request let response = sdk .as_ref() .execute(request, RequestSettings::default()) .await .map_err(|e| JsError::new(&format!("Failed to get contested resource voters: {}", e)))?; - + // For now, return a simple response structure let result = serde_json::json!({ "voters": [], @@ -198,10 +208,11 @@ pub async fn get_contested_resource_voters_for_identity( "protocolVersion": response.inner.metadata().ok().map(|m| m.protocol_version), } }); - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - result.serialize(&serializer) + result + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -215,14 +226,20 @@ pub async fn get_contested_resource_identity_votes( ) -> Result { // TODO: Implement get_contested_resource_identity_votes // This function should return all votes made by a specific identity - let _ = (sdk, identity_id, limit, start_at_vote_poll_id_info, order_ascending); - + let _ = ( + sdk, + identity_id, + limit, + start_at_vote_poll_id_info, + order_ascending, + ); + // Return empty result for now let result = serde_json::json!({ "votes": [], "metadata": {} }); - + serde_wasm_bindgen::to_value(&result) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -238,13 +255,13 @@ pub async fn get_vote_polls_by_end_date( // TODO: Implement get_vote_polls_by_end_date // This function should return vote polls filtered by end date let _ = (sdk, start_time_info, end_time_info, limit, order_ascending); - + // Return empty result for now let result = serde_json::json!({ "votePolls": [], "metadata": {} }); - + serde_wasm_bindgen::to_value(&result) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -263,24 +280,25 @@ pub async fn get_contested_resources_with_proof_info( order_ascending: Option, ) -> Result { use crate::queries::ProofMetadataResponse; - + // Parse contract ID let contract_id = Identifier::from_string( data_contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Parse result_type to get start_index_values // The result_type parameter actually specifies what we want to query // For contested domain names in DPNS, we query at the "dash" parent domain level - let start_index_values = if index_name == "parentNameAndLabel" && document_type_name == "domain" { + let start_index_values = if index_name == "parentNameAndLabel" && document_type_name == "domain" + { // For DPNS domains, start at the parent domain level (e.g., "dash") vec![] // Empty to get all contested resources at any parent domain } else { // For other types, may need different index values vec![] }; - + // Create start_at_value_info if provided let start_at_value_info = start_at_value.map(|bytes| { get_contested_resources_request::get_contested_resources_request_v0::StartAtValueInfo { @@ -288,7 +306,7 @@ pub async fn get_contested_resources_with_proof_info( start_value_included: true, } }); - + // Create the gRPC request directly - force prove=true for proof info let request = GetContestedResourcesRequest { version: Some(get_contested_resources_request::Version::V0( @@ -305,35 +323,45 @@ pub async fn get_contested_resources_with_proof_info( }, )), }; - + // Execute the request let response = sdk .as_ref() .execute(request, RequestSettings::default()) .await - .map_err(|e| JsError::new(&format!("Failed to get contested resources with proof: {}", e)))?; - + .map_err(|e| { + JsError::new(&format!( + "Failed to get contested resources with proof: {}", + e + )) + })?; + // Extract metadata and proof from response - let metadata = response.inner.metadata() + let metadata = response + .inner + .metadata() .map_err(|e| JsError::new(&format!("Failed to get metadata: {:?}", e)))?; - - let proof = response.inner.proof() + + let proof = response + .inner + .proof() .map_err(|e| JsError::new(&format!("Failed to get proof: {:?}", e)))?; - + // For now, return a simple response structure let data = serde_json::json!({ "contestedResources": [] }); - + let response = ProofMetadataResponse { data, metadata: metadata.clone().into(), proof: proof.clone().into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -357,25 +385,29 @@ pub async fn get_contested_resource_vote_state_with_proof_info( _order_ascending: Option, ) -> Result { use crate::queries::ProofMetadataResponse; - + // Parse contract ID let contract_id = Identifier::from_string( data_contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Parse start_at_identifier_info if provided let start_at_identifier_info = if let Some(info_str) = start_at_identifier_info { let info: serde_json::Value = serde_json::from_str(&info_str) .map_err(|e| JsError::new(&format!("Invalid start_at_identifier_info JSON: {}", e)))?; - - if let (Some(start_id), Some(included)) = (info.get("startIdentifier"), info.get("startIdentifierIncluded")) { - let start_identifier = start_id.as_str() + + if let (Some(start_id), Some(included)) = ( + info.get("startIdentifier"), + info.get("startIdentifierIncluded"), + ) { + let start_identifier = start_id + .as_str() .ok_or_else(|| JsError::new("startIdentifier must be a string"))? .as_bytes() .to_vec(); let start_identifier_included = included.as_bool().unwrap_or(true); - + Some(get_contested_resource_vote_state_request::get_contested_resource_vote_state_request_v0::StartAtIdentifierInfo { start_identifier, start_identifier_included, @@ -386,7 +418,7 @@ pub async fn get_contested_resource_vote_state_with_proof_info( } else { None }; - + // Convert JsValue index values to Vec> using bincode serialization let mut index_values_bytes: Vec> = Vec::new(); for value in index_values { @@ -401,7 +433,7 @@ pub async fn get_contested_resource_vote_state_with_proof_info( return Err(JsError::new("Index values must be strings")); } } - + // Create the gRPC request directly - force prove=true let request = GetContestedResourceVoteStateRequest { version: Some(get_contested_resource_vote_state_request::Version::V0( @@ -412,29 +444,43 @@ pub async fn get_contested_resource_vote_state_with_proof_info( index_values: index_values_bytes, // TODO: This should use the _result_type parameter instead of allow_include_locked_and_abstaining_vote_tally // Current logic is incorrect - these are independent concerns - result_type: if allow_include_locked_and_abstaining_vote_tally.unwrap_or(false) { 0 } else { 1 }, - allow_include_locked_and_abstaining_vote_tally: allow_include_locked_and_abstaining_vote_tally.unwrap_or(false), + result_type: if allow_include_locked_and_abstaining_vote_tally.unwrap_or(false) { + 0 + } else { + 1 + }, + allow_include_locked_and_abstaining_vote_tally: + allow_include_locked_and_abstaining_vote_tally.unwrap_or(false), start_at_identifier_info, count, prove: true, // Always true for proof info version }, )), }; - + // Execute the request let response = sdk .as_ref() .execute(request, RequestSettings::default()) .await - .map_err(|e| JsError::new(&format!("Failed to get contested resource vote state with proof: {}", e)))?; - + .map_err(|e| { + JsError::new(&format!( + "Failed to get contested resource vote state with proof: {}", + e + )) + })?; + // Extract metadata and proof from response - let metadata = response.inner.metadata() + let metadata = response + .inner + .metadata() .map_err(|e| JsError::new(&format!("Failed to get metadata: {:?}", e)))?; - - let proof = response.inner.proof() + + let proof = response + .inner + .proof() .map_err(|e| JsError::new(&format!("Failed to get proof: {:?}", e)))?; - + // Return a simple response structure let data = serde_json::json!({ "contenders": [], @@ -442,16 +488,17 @@ pub async fn get_contested_resource_vote_state_with_proof_info( "lockVoteTally": null, "finishedVoteInfo": null }); - + let response = ProofMetadataResponse { data, metadata: metadata.clone().into(), proof: proof.clone().into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -468,18 +515,18 @@ pub async fn get_contested_resource_voters_for_identity_with_proof_info( order_ascending: Option, ) -> Result { use crate::queries::ProofMetadataResponse; - + // Parse IDs let contract_id = Identifier::from_string( data_contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + let contestant_identifier = Identifier::from_string( contestant_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Convert JsValue index values to Vec> using bincode serialization let mut index_values_bytes: Vec> = Vec::new(); for value in index_values { @@ -494,19 +541,23 @@ pub async fn get_contested_resource_voters_for_identity_with_proof_info( return Err(JsError::new("Index values must be strings")); } } - + // Parse start_at_identifier_info if provided let start_at_identifier_info = if let Some(info_str) = start_at_identifier_info { let info: serde_json::Value = serde_json::from_str(&info_str) .map_err(|e| JsError::new(&format!("Invalid start_at_identifier_info JSON: {}", e)))?; - - if let (Some(start_id), Some(included)) = (info.get("startIdentifier"), info.get("startIdentifierIncluded")) { - let start_identifier = start_id.as_str() + + if let (Some(start_id), Some(included)) = ( + info.get("startIdentifier"), + info.get("startIdentifierIncluded"), + ) { + let start_identifier = start_id + .as_str() .ok_or_else(|| JsError::new("startIdentifier must be a string"))? .as_bytes() .to_vec(); let start_identifier_included = included.as_bool().unwrap_or(true); - + Some(get_contested_resource_voters_for_identity_request::get_contested_resource_voters_for_identity_request_v0::StartAtIdentifierInfo { start_identifier, start_identifier_included, @@ -517,53 +568,65 @@ pub async fn get_contested_resource_voters_for_identity_with_proof_info( } else { None }; - + // Create the gRPC request directly - force prove=true let request = GetContestedResourceVotersForIdentityRequest { - version: Some(get_contested_resource_voters_for_identity_request::Version::V0( - GetContestedResourceVotersForIdentityRequestV0 { - contract_id: contract_id.to_vec(), - document_type_name: document_type_name.to_string(), - index_name: index_name.to_string(), - index_values: index_values_bytes, - contestant_id: contestant_identifier.to_vec(), - start_at_identifier_info, - count, - order_ascending: order_ascending.unwrap_or(true), - prove: true, // Always true for proof info version - }, - )), + version: Some( + get_contested_resource_voters_for_identity_request::Version::V0( + GetContestedResourceVotersForIdentityRequestV0 { + contract_id: contract_id.to_vec(), + document_type_name: document_type_name.to_string(), + index_name: index_name.to_string(), + index_values: index_values_bytes, + contestant_id: contestant_identifier.to_vec(), + start_at_identifier_info, + count, + order_ascending: order_ascending.unwrap_or(true), + prove: true, // Always true for proof info version + }, + ), + ), }; - + // Execute the request let response = sdk .as_ref() .execute(request, RequestSettings::default()) .await - .map_err(|e| JsError::new(&format!("Failed to get contested resource voters with proof: {}", e)))?; - + .map_err(|e| { + JsError::new(&format!( + "Failed to get contested resource voters with proof: {}", + e + )) + })?; + // Extract metadata and proof from response - let metadata = response.inner.metadata() + let metadata = response + .inner + .metadata() .map_err(|e| JsError::new(&format!("Failed to get metadata: {:?}", e)))?; - - let proof = response.inner.proof() + + let proof = response + .inner + .proof() .map_err(|e| JsError::new(&format!("Failed to get proof: {:?}", e)))?; - + // Return a simple response structure let data = serde_json::json!({ "voters": [], "finishedResults": false }); - + let response = ProofMetadataResponse { data, metadata: metadata.clone().into(), proof: proof.clone().into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -576,13 +639,13 @@ pub async fn get_contested_resource_identity_votes_with_proof_info( order_ascending: Option, ) -> Result { use crate::queries::ProofMetadataResponse; - + // Parse identity ID let identity_identifier = Identifier::from_string( identity_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Create the gRPC request directly - force prove=true let request = GetContestedResourceIdentityVotesRequest { version: Some(get_contested_resource_identity_votes_request::Version::V0( @@ -596,36 +659,46 @@ pub async fn get_contested_resource_identity_votes_with_proof_info( }, )), }; - + // Execute the request let response = sdk .as_ref() .execute(request, RequestSettings::default()) .await - .map_err(|e| JsError::new(&format!("Failed to get contested resource identity votes with proof: {}", e)))?; - + .map_err(|e| { + JsError::new(&format!( + "Failed to get contested resource identity votes with proof: {}", + e + )) + })?; + // Extract metadata and proof from response - let metadata = response.inner.metadata() + let metadata = response + .inner + .metadata() .map_err(|e| JsError::new(&format!("Failed to get metadata: {:?}", e)))?; - - let proof = response.inner.proof() + + let proof = response + .inner + .proof() .map_err(|e| JsError::new(&format!("Failed to get proof: {:?}", e)))?; - + // Return a simple response structure let data = serde_json::json!({ "votes": [], "finishedResults": false }); - + let response = ProofMetadataResponse { data, metadata: metadata.clone().into(), proof: proof.clone().into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } @@ -639,9 +712,9 @@ pub async fn get_vote_polls_by_end_date_with_proof_info( order_ascending: Option, ) -> Result { use crate::queries::ProofMetadataResponse; - + // Note: GetVotePollsByEndDateRequestV0 doesn't have start_at_poll_info, only offset - + // Create the gRPC request directly - force prove=true let request = GetVotePollsByEndDateRequest { version: Some(get_vote_polls_by_end_date_request::Version::V0( @@ -665,41 +738,49 @@ pub async fn get_vote_polls_by_end_date_with_proof_info( }, )), }; - + // Execute the request let response = sdk .as_ref() .execute(request, RequestSettings::default()) .await - .map_err(|e| JsError::new(&format!("Failed to get vote polls by end date with proof: {}", e)))?; - + .map_err(|e| { + JsError::new(&format!( + "Failed to get vote polls by end date with proof: {}", + e + )) + })?; + // Extract metadata and proof from response - let metadata = response.inner.metadata() + let metadata = response + .inner + .metadata() .map_err(|e| JsError::new(&format!("Failed to get metadata: {:?}", e)))?; - - let proof = response.inner.proof() + + let proof = response + .inner + .proof() .map_err(|e| JsError::new(&format!("Failed to get proof: {:?}", e)))?; - + // Return a simple response structure let data = serde_json::json!({ "votePollsByTimestamps": {}, "finishedResults": false }); - + let response = ProofMetadataResponse { data, metadata: metadata.clone().into(), proof: proof.clone().into(), }; - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - response.serialize(&serializer) + response + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } - - #[wasm_bindgen] pub async fn get_contested_resource_vote_state( sdk: &WasmSdk, @@ -724,19 +805,23 @@ pub async fn get_contested_resource_vote_state( data_contract_id, dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - + // Parse start_at_identifier_info if provided let start_at_identifier_info = if let Some(info_str) = start_at_identifier_info { let info: serde_json::Value = serde_json::from_str(&info_str) .map_err(|e| JsError::new(&format!("Invalid start_at_identifier_info JSON: {}", e)))?; - - if let (Some(start_id), Some(included)) = (info.get("startIdentifier"), info.get("startIdentifierIncluded")) { - let start_identifier = start_id.as_str() + + if let (Some(start_id), Some(included)) = ( + info.get("startIdentifier"), + info.get("startIdentifierIncluded"), + ) { + let start_identifier = start_id + .as_str() .ok_or_else(|| JsError::new("startIdentifier must be a string"))? .as_bytes() .to_vec(); let start_identifier_included = included.as_bool().unwrap_or(true); - + Some(get_contested_resource_vote_state_request::get_contested_resource_vote_state_request_v0::StartAtIdentifierInfo { start_identifier, start_identifier_included, @@ -747,7 +832,7 @@ pub async fn get_contested_resource_vote_state( } else { None }; - + // Convert JsValue index values to Vec> using bincode serialization let mut index_values_bytes: Vec> = Vec::new(); for value in index_values { @@ -762,7 +847,7 @@ pub async fn get_contested_resource_vote_state( return Err(JsError::new("Index values must be strings")); } } - + // Create the gRPC request directly let request = GetContestedResourceVoteStateRequest { version: Some(get_contested_resource_vote_state_request::Version::V0( @@ -773,22 +858,32 @@ pub async fn get_contested_resource_vote_state( index_values: index_values_bytes, // TODO: This should use the _result_type parameter instead of allow_include_locked_and_abstaining_vote_tally // Current logic is incorrect - these are independent concerns - result_type: if allow_include_locked_and_abstaining_vote_tally.unwrap_or(false) { 0 } else { 1 }, - allow_include_locked_and_abstaining_vote_tally: allow_include_locked_and_abstaining_vote_tally.unwrap_or(false), + result_type: if allow_include_locked_and_abstaining_vote_tally.unwrap_or(false) { + 0 + } else { + 1 + }, + allow_include_locked_and_abstaining_vote_tally: + allow_include_locked_and_abstaining_vote_tally.unwrap_or(false), start_at_identifier_info, count, prove: sdk.prove(), }, )), }; - + // Execute the request let response = sdk .as_ref() .execute(request, RequestSettings::default()) .await - .map_err(|e| JsError::new(&format!("Failed to get contested resource vote state: {}", e)))?; - + .map_err(|e| { + JsError::new(&format!( + "Failed to get contested resource vote state: {}", + e + )) + })?; + // Return a simple response structure let result = serde_json::json!({ "contenders": [], @@ -802,10 +897,10 @@ pub async fn get_contested_resource_vote_state( "protocolVersion": response.inner.metadata().ok().map(|m| m.protocol_version), } }); - + // Use json_compatible serializer let serializer = serde_wasm_bindgen::Serializer::json_compatible(); - result.serialize(&serializer) + result + .serialize(&serializer) .map_err(|e| JsError::new(&format!("Failed to serialize response: {}", e))) } - diff --git a/packages/wasm-sdk/src/sdk.rs b/packages/wasm-sdk/src/sdk.rs index f08df76766..c72c48bdd6 100644 --- a/packages/wasm-sdk/src/sdk.rs +++ b/packages/wasm-sdk/src/sdk.rs @@ -8,14 +8,15 @@ use dash_sdk::dpp::identity::signer::Signer; use dash_sdk::dpp::identity::IdentityV0; use dash_sdk::dpp::prelude::AssetLockProof; use dash_sdk::dpp::serialization::PlatformSerializableWithPlatformVersion; +use dash_sdk::dpp::version::PlatformVersion; use dash_sdk::platform::transition::broadcast::BroadcastStateTransition; use dash_sdk::platform::transition::put_identity::PutIdentity; use dash_sdk::platform::{DataContract, Document, DocumentQuery, Fetch, Identifier, Identity}; use dash_sdk::sdk::AddressList; use dash_sdk::{Sdk, SdkBuilder}; use platform_value::platform_value; -use dash_sdk::dpp::version::PlatformVersion; use rs_dapi_client::RequestSettings; +use serde_json; use std::collections::BTreeMap; use std::fmt::Debug; use std::ops::{Deref, DerefMut}; @@ -24,7 +25,6 @@ use std::time::Duration; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::{JsError, JsValue}; use web_sys::{console, js_sys}; -use serde_json; #[wasm_bindgen] pub struct WasmSdk(Sdk); @@ -53,29 +53,30 @@ impl WasmSdk { pub fn version(&self) -> u32 { self.0.version().protocol_version } - + /// Get reference to the inner SDK for direct gRPC calls pub(crate) fn inner_sdk(&self) -> &Sdk { &self.0 } - + /// Get the network this SDK is configured for pub(crate) fn network(&self) -> dash_sdk::dpp::dashcore::Network { self.0.network } - + /// Test serialization of different object types #[wasm_bindgen(js_name = testSerialization)] pub fn test_serialization(&self, test_type: &str) -> Result { use serde_wasm_bindgen::to_value; - + match test_type { "simple" => { let simple = serde_json::json!({ "type": "simple", "value": "test" }); - to_value(&simple).map_err(|e| JsValue::from_str(&format!("Simple serialization failed: {}", e))) + to_value(&simple) + .map_err(|e| JsValue::from_str(&format!("Simple serialization failed: {}", e))) } "complex" => { let complex = serde_json::json!({ @@ -88,7 +89,8 @@ impl WasmSdk { "bool_value": true } }); - to_value(&complex).map_err(|e| JsValue::from_str(&format!("Complex serialization failed: {}", e))) + to_value(&complex) + .map_err(|e| JsValue::from_str(&format!("Complex serialization failed: {}", e))) } "document" => { // Simulate the exact structure we're trying to return @@ -105,9 +107,11 @@ impl WasmSdk { "updatedAt": 1736300191752i64, } }); - to_value(&doc).map_err(|e| JsValue::from_str(&format!("Document serialization failed: {}", e))) + to_value(&doc).map_err(|e| { + JsValue::from_str(&format!("Document serialization failed: {}", e)) + }) } - _ => Err(JsValue::from_str("Unknown test type")) + _ => Err(JsValue::from_str("Unknown test type")), } } } @@ -667,24 +671,28 @@ impl WasmSdkBuilder { pub fn with_context_provider(self, context_provider: WasmContext) -> Self { WasmSdkBuilder(self.0.with_context_provider(context_provider)) } - + /// Configure platform version to use. - /// + /// /// Available versions: /// - 1: Platform version 1 /// - 2: Platform version 2 /// - ... up to latest version - /// + /// /// Defaults to latest version if not specified. pub fn with_version(self, version_number: u32) -> Result { - let version = PlatformVersion::get(version_number) - .map_err(|e| JsError::new(&format!("Invalid platform version {}: {}", version_number, e)))?; - + let version = PlatformVersion::get(version_number).map_err(|e| { + JsError::new(&format!( + "Invalid platform version {}: {}", + version_number, e + )) + })?; + Ok(WasmSdkBuilder(self.0.with_version(version))) } - + /// Configure request settings for the SDK. - /// + /// /// Settings include: /// - connect_timeout_ms: Timeout for establishing connection (in milliseconds) /// - timeout_ms: Timeout for single request (in milliseconds) @@ -698,26 +706,26 @@ impl WasmSdkBuilder { ban_failed_address: Option, ) -> Self { let mut settings = RequestSettings::default(); - + if let Some(connect_timeout) = connect_timeout_ms { settings.connect_timeout = Some(Duration::from_millis(connect_timeout as u64)); } - + if let Some(timeout) = timeout_ms { settings.timeout = Some(Duration::from_millis(timeout as u64)); } - + if let Some(retries) = retries { settings.retries = Some(retries as usize); } - + if let Some(ban) = ban_failed_address { settings.ban_failed_address = Some(ban); } - + WasmSdkBuilder(self.0.with_settings(settings)) } - + // TODO: Add with_proofs method when it's available in the SDK builder // pub fn with_proofs(self, enable_proofs: bool) -> Self { // WasmSdkBuilder(self.0.with_proofs(enable_proofs)) @@ -728,10 +736,12 @@ impl WasmSdkBuilder { use once_cell::sync::Lazy; use std::sync::Mutex; -pub(crate) static MAINNET_TRUSTED_CONTEXT: Lazy>> = - Lazy::new(|| Mutex::new(None)); -pub(crate) static TESTNET_TRUSTED_CONTEXT: Lazy>> = - Lazy::new(|| Mutex::new(None)); +pub(crate) static MAINNET_TRUSTED_CONTEXT: Lazy< + Mutex>, +> = Lazy::new(|| Mutex::new(None)); +pub(crate) static TESTNET_TRUSTED_CONTEXT: Lazy< + Mutex>, +> = Lazy::new(|| Mutex::new(None)); #[wasm_bindgen] pub async fn prefetch_trusted_quorums_mainnet() -> Result<(), JsError> { diff --git a/packages/wasm-sdk/src/state_transitions/contracts/mod.rs b/packages/wasm-sdk/src/state_transitions/contracts/mod.rs index 1232c90b6b..5e1dc8afdb 100644 --- a/packages/wasm-sdk/src/state_transitions/contracts/mod.rs +++ b/packages/wasm-sdk/src/state_transitions/contracts/mod.rs @@ -1,21 +1,21 @@ use crate::sdk::WasmSdk; +use dash_sdk::dpp::data_contract::accessors::v0::DataContractV0Getters; +use dash_sdk::dpp::data_contract::conversion::json::DataContractJsonConversionMethodsV0; +use dash_sdk::dpp::data_contract::DataContract; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; use dash_sdk::dpp::identity::{KeyType, Purpose}; -use dash_sdk::dpp::platform_value::{Identifier, string_encoding::Encoding}; -use dash_sdk::dpp::data_contract::DataContract; -use dash_sdk::dpp::data_contract::accessors::v0::DataContractV0Getters; -use dash_sdk::dpp::data_contract::conversion::json::DataContractJsonConversionMethodsV0; +use dash_sdk::dpp::platform_value::{string_encoding::Encoding, Identifier}; use dash_sdk::dpp::state_transition::data_contract_update_transition::methods::DataContractUpdateTransitionMethodsV0; use dash_sdk::dpp::state_transition::data_contract_update_transition::DataContractUpdateTransition; use dash_sdk::platform::transition::broadcast::BroadcastStateTransition; use dash_sdk::platform::transition::put_contract::PutContract; use dash_sdk::platform::Fetch; +use js_sys; use simple_signer::SingleKeySigner; +use std::collections::BTreeMap; use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; -use js_sys; -use std::collections::BTreeMap; #[wasm_bindgen] impl WasmSdk { @@ -40,113 +40,155 @@ impl WasmSdk { key_id: Option, ) -> Result { let sdk = self.inner_clone(); - + // Parse owner identifier let owner_identifier = Identifier::from_string(&owner_id, Encoding::Base58) .map_err(|e| JsValue::from_str(&format!("Invalid owner ID: {}", e)))?; - + // Parse contract definition JSON let contract_json: serde_json::Value = serde_json::from_str(&contract_definition) .map_err(|e| JsValue::from_str(&format!("Invalid contract definition JSON: {}", e)))?; - + // Fetch owner identity let owner_identity = dash_sdk::platform::Identity::fetch(&sdk, owner_identifier) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch owner identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Owner identity not found"))?; - + // Parse private key and find matching public key let private_key_bytes = dash_sdk::dpp::dashcore::PrivateKey::from_wif(&private_key_wif) .map_err(|e| JsValue::from_str(&format!("Invalid private key: {}", e)))? .inner .secret_bytes(); - + let secp = dash_sdk::dpp::dashcore::secp256k1::Secp256k1::new(); - let secret_key = dash_sdk::dpp::dashcore::secp256k1::SecretKey::from_slice(&private_key_bytes) - .map_err(|e| JsValue::from_str(&format!("Invalid secret key: {}", e)))?; - let public_key = dash_sdk::dpp::dashcore::secp256k1::PublicKey::from_secret_key(&secp, &secret_key); + let secret_key = + dash_sdk::dpp::dashcore::secp256k1::SecretKey::from_slice(&private_key_bytes) + .map_err(|e| JsValue::from_str(&format!("Invalid secret key: {}", e)))?; + let public_key = + dash_sdk::dpp::dashcore::secp256k1::PublicKey::from_secret_key(&secp, &secret_key); let public_key_bytes = public_key.serialize(); - + // Create public key hash using hash160 let public_key_hash160 = { - use dash_sdk::dpp::dashcore::hashes::{Hash, hash160}; - hash160::Hash::hash(&public_key_bytes[..]).to_byte_array().to_vec() + use dash_sdk::dpp::dashcore::hashes::{hash160, Hash}; + hash160::Hash::hash(&public_key_bytes[..]) + .to_byte_array() + .to_vec() }; - + // Find matching key - prioritize key_id if provided, otherwise find any authentication key let matching_key = if let Some(requested_key_id) = key_id { // Find specific key by ID - owner_identity.public_keys() + owner_identity + .public_keys() .get(&requested_key_id) .filter(|key| { - key.purpose() == Purpose::AUTHENTICATION && - key.key_type() == KeyType::ECDSA_HASH160 && - key.data().as_slice() == public_key_hash160.as_slice() + key.purpose() == Purpose::AUTHENTICATION + && key.key_type() == KeyType::ECDSA_HASH160 + && key.data().as_slice() == public_key_hash160.as_slice() }) - .ok_or_else(|| JsValue::from_str(&format!("Key with ID {} not found or doesn't match private key", requested_key_id)))? + .ok_or_else(|| { + JsValue::from_str(&format!( + "Key with ID {} not found or doesn't match private key", + requested_key_id + )) + })? .clone() } else { // Find any matching authentication key - owner_identity.public_keys().iter() + owner_identity + .public_keys() + .iter() .find(|(_, key)| { - key.purpose() == Purpose::AUTHENTICATION && - key.key_type() == KeyType::ECDSA_HASH160 && - key.data().as_slice() == public_key_hash160.as_slice() + key.purpose() == Purpose::AUTHENTICATION + && key.key_type() == KeyType::ECDSA_HASH160 + && key.data().as_slice() == public_key_hash160.as_slice() }) .map(|(_, key)| key.clone()) - .ok_or_else(|| JsValue::from_str("No matching authentication key found for the provided private key"))? + .ok_or_else(|| { + JsValue::from_str( + "No matching authentication key found for the provided private key", + ) + })? }; - + // Create the data contract from JSON definition let data_contract = DataContract::from_json( contract_json, true, // validate sdk.version(), ) - .map_err(|e| JsValue::from_str(&format!("Failed to create data contract from JSON: {}", e)))?; - + .map_err(|e| { + JsValue::from_str(&format!("Failed to create data contract from JSON: {}", e)) + })?; + // Create signer let signer = SingleKeySigner::from_string(&private_key_wif, self.network()) .map_err(|e| JsValue::from_str(&e))?; - + // Create and broadcast the contract let created_contract = data_contract .put_to_platform_and_wait_for_response(&sdk, matching_key, &signer, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to create contract: {}", e)))?; - + // Create JavaScript result object let result_obj = js_sys::Object::new(); - - js_sys::Reflect::set(&result_obj, &JsValue::from_str("status"), &JsValue::from_str("success")) - .map_err(|e| JsValue::from_str(&format!("Failed to set status: {:?}", e)))?; - + + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("status"), + &JsValue::from_str("success"), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set status: {:?}", e)))?; + // Convert contract ID to base58 let contract_id_base58 = created_contract.id().to_string(Encoding::Base58); - js_sys::Reflect::set(&result_obj, &JsValue::from_str("contractId"), &JsValue::from_str(&contract_id_base58)) - .map_err(|e| JsValue::from_str(&format!("Failed to set contractId: {:?}", e)))?; - - js_sys::Reflect::set(&result_obj, &JsValue::from_str("ownerId"), &JsValue::from_str(&owner_id)) - .map_err(|e| JsValue::from_str(&format!("Failed to set ownerId: {:?}", e)))?; - - js_sys::Reflect::set(&result_obj, &JsValue::from_str("version"), &JsValue::from_f64(created_contract.version() as f64)) - .map_err(|e| JsValue::from_str(&format!("Failed to set version: {:?}", e)))?; - + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("contractId"), + &JsValue::from_str(&contract_id_base58), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set contractId: {:?}", e)))?; + + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("ownerId"), + &JsValue::from_str(&owner_id), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set ownerId: {:?}", e)))?; + + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("version"), + &JsValue::from_f64(created_contract.version() as f64), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set version: {:?}", e)))?; + // Add document type names let schema = created_contract.document_types(); let doc_types_array = js_sys::Array::new(); for (doc_type_name, _) in schema.iter() { doc_types_array.push(&JsValue::from_str(doc_type_name)); } - js_sys::Reflect::set(&result_obj, &JsValue::from_str("documentTypes"), &doc_types_array) - .map_err(|e| JsValue::from_str(&format!("Failed to set documentTypes: {:?}", e)))?; - - js_sys::Reflect::set(&result_obj, &JsValue::from_str("message"), &JsValue::from_str("Data contract created successfully")) - .map_err(|e| JsValue::from_str(&format!("Failed to set message: {:?}", e)))?; - + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("documentTypes"), + &doc_types_array, + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set documentTypes: {:?}", e)))?; + + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("message"), + &JsValue::from_str("Data contract created successfully"), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set message: {:?}", e)))?; + Ok(result_obj.into()) } - + /// Update an existing data contract on Dash Platform. /// /// # Arguments @@ -170,77 +212,93 @@ impl WasmSdk { key_id: Option, ) -> Result { let sdk = self.inner_clone(); - + // Parse identifiers let contract_identifier = Identifier::from_string(&contract_id, Encoding::Base58) .map_err(|e| JsValue::from_str(&format!("Invalid contract ID: {}", e)))?; - + let owner_identifier = Identifier::from_string(&owner_id, Encoding::Base58) .map_err(|e| JsValue::from_str(&format!("Invalid owner ID: {}", e)))?; - + // Parse contract updates JSON let updates_json: serde_json::Value = serde_json::from_str(&contract_updates) .map_err(|e| JsValue::from_str(&format!("Invalid contract updates JSON: {}", e)))?; - + // Fetch the existing contract let existing_contract = DataContract::fetch(&sdk, contract_identifier) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch contract: {}", e)))? .ok_or_else(|| JsValue::from_str("Contract not found"))?; - + // Verify ownership if existing_contract.owner_id() != owner_identifier { return Err(JsValue::from_str("Identity does not own this contract")); } - + // Fetch owner identity let owner_identity = dash_sdk::platform::Identity::fetch(&sdk, owner_identifier) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch owner identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Owner identity not found"))?; - + // Parse private key and find matching public key let private_key_bytes = dash_sdk::dpp::dashcore::PrivateKey::from_wif(&private_key_wif) .map_err(|e| JsValue::from_str(&format!("Invalid private key: {}", e)))? .inner .secret_bytes(); - + let secp = dash_sdk::dpp::dashcore::secp256k1::Secp256k1::new(); - let secret_key = dash_sdk::dpp::dashcore::secp256k1::SecretKey::from_slice(&private_key_bytes) - .map_err(|e| JsValue::from_str(&format!("Invalid secret key: {}", e)))?; - let public_key = dash_sdk::dpp::dashcore::secp256k1::PublicKey::from_secret_key(&secp, &secret_key); + let secret_key = + dash_sdk::dpp::dashcore::secp256k1::SecretKey::from_slice(&private_key_bytes) + .map_err(|e| JsValue::from_str(&format!("Invalid secret key: {}", e)))?; + let public_key = + dash_sdk::dpp::dashcore::secp256k1::PublicKey::from_secret_key(&secp, &secret_key); let public_key_bytes = public_key.serialize(); - + // Create public key hash using hash160 let public_key_hash160 = { - use dash_sdk::dpp::dashcore::hashes::{Hash, hash160}; - hash160::Hash::hash(&public_key_bytes[..]).to_byte_array().to_vec() + use dash_sdk::dpp::dashcore::hashes::{hash160, Hash}; + hash160::Hash::hash(&public_key_bytes[..]) + .to_byte_array() + .to_vec() }; - + // Find matching key - prioritize key_id if provided, otherwise find any authentication key let matching_key = if let Some(requested_key_id) = key_id { // Find specific key by ID - owner_identity.public_keys() + owner_identity + .public_keys() .get(&requested_key_id) .filter(|key| { - key.purpose() == Purpose::AUTHENTICATION && - key.key_type() == KeyType::ECDSA_HASH160 && - key.data().as_slice() == public_key_hash160.as_slice() + key.purpose() == Purpose::AUTHENTICATION + && key.key_type() == KeyType::ECDSA_HASH160 + && key.data().as_slice() == public_key_hash160.as_slice() }) - .ok_or_else(|| JsValue::from_str(&format!("Key with ID {} not found or doesn't match private key", requested_key_id)))? + .ok_or_else(|| { + JsValue::from_str(&format!( + "Key with ID {} not found or doesn't match private key", + requested_key_id + )) + })? .clone() } else { // Find any matching authentication key - owner_identity.public_keys().iter() + owner_identity + .public_keys() + .iter() .find(|(_, key)| { - key.purpose() == Purpose::AUTHENTICATION && - key.key_type() == KeyType::ECDSA_HASH160 && - key.data().as_slice() == public_key_hash160.as_slice() + key.purpose() == Purpose::AUTHENTICATION + && key.key_type() == KeyType::ECDSA_HASH160 + && key.data().as_slice() == public_key_hash160.as_slice() }) .map(|(_, key)| key.clone()) - .ok_or_else(|| JsValue::from_str("No matching authentication key found for the provided private key"))? + .ok_or_else(|| { + JsValue::from_str( + "No matching authentication key found for the provided private key", + ) + })? }; - + // Create updated contract from JSON definition // Note: The updates should be a complete contract definition with incremented version let updated_contract = DataContract::from_json( @@ -248,23 +306,30 @@ impl WasmSdk { true, // validate sdk.version(), ) - .map_err(|e| JsValue::from_str(&format!("Failed to create updated contract from JSON: {}", e)))?; - + .map_err(|e| { + JsValue::from_str(&format!( + "Failed to create updated contract from JSON: {}", + e + )) + })?; + // Verify the version was incremented if updated_contract.version() <= existing_contract.version() { return Err(JsValue::from_str(&format!( - "Contract version must be incremented. Current: {}, Provided: {}", - existing_contract.version(), + "Contract version must be incremented. Current: {}, Provided: {}", + existing_contract.version(), updated_contract.version() ))); } - + // Get identity contract nonce (contract updates use per-contract nonces) let identity_contract_nonce = sdk .get_identity_contract_nonce(owner_identifier, contract_identifier, true, None) .await - .map_err(|e| JsValue::from_str(&format!("Failed to get identity contract nonce: {}", e)))?; - + .map_err(|e| { + JsValue::from_str(&format!("Failed to get identity contract nonce: {}", e)) + })?; + // Create partial identity for signing let partial_identity = dash_sdk::dpp::identity::PartialIdentity { id: owner_identifier, @@ -273,11 +338,11 @@ impl WasmSdk { revision: None, not_found_public_keys: Default::default(), }; - + // Create signer let signer = SingleKeySigner::from_string(&private_key_wif, self.network()) .map_err(|e| JsValue::from_str(&e))?; - + // Create the update transition let state_transition = DataContractUpdateTransition::new_from_data_contract( updated_contract.clone(), @@ -290,32 +355,48 @@ impl WasmSdk { None, ) .map_err(|e| JsValue::from_str(&format!("Failed to create update transition: {}", e)))?; - + // Broadcast the transition use dash_sdk::dpp::state_transition::proof_result::StateTransitionProofResult; let result = state_transition .broadcast_and_wait::(&sdk, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to broadcast update: {}", e)))?; - + // Extract updated contract from result let updated_version = match result { StateTransitionProofResult::VerifiedDataContract(contract) => contract.version(), _ => updated_contract.version(), }; - + // Create JavaScript result object let result_obj = js_sys::Object::new(); - - js_sys::Reflect::set(&result_obj, &JsValue::from_str("status"), &JsValue::from_str("success")) - .map_err(|e| JsValue::from_str(&format!("Failed to set status: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("contractId"), &JsValue::from_str(&contract_id)) - .map_err(|e| JsValue::from_str(&format!("Failed to set contractId: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("version"), &JsValue::from_f64(updated_version as f64)) - .map_err(|e| JsValue::from_str(&format!("Failed to set version: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("message"), &JsValue::from_str("Data contract updated successfully")) - .map_err(|e| JsValue::from_str(&format!("Failed to set message: {:?}", e)))?; - + + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("status"), + &JsValue::from_str("success"), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set status: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("contractId"), + &JsValue::from_str(&contract_id), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set contractId: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("version"), + &JsValue::from_f64(updated_version as f64), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set version: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("message"), + &JsValue::from_str("Data contract updated successfully"), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set message: {:?}", e)))?; + Ok(result_obj.into()) } -} \ No newline at end of file +} diff --git a/packages/wasm-sdk/src/state_transitions/documents/mod.rs b/packages/wasm-sdk/src/state_transitions/documents/mod.rs index 3097255ceb..643fc28575 100644 --- a/packages/wasm-sdk/src/state_transitions/documents/mod.rs +++ b/packages/wasm-sdk/src/state_transitions/documents/mod.rs @@ -4,28 +4,30 @@ use crate::sdk::{WasmSdk, MAINNET_TRUSTED_CONTEXT, TESTNET_TRUSTED_CONTEXT}; use dash_sdk::dpp::dashcore::PrivateKey; -use dash_sdk::dpp::identity::{IdentityPublicKey, KeyType, Purpose}; -use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; +use dash_sdk::dpp::data_contract::accessors::v0::DataContractV0Getters; +use dash_sdk::dpp::data_contract::document_type::methods::DocumentTypeV0Methods; +use dash_sdk::dpp::document::{Document, DocumentV0, DocumentV0Getters}; +use dash_sdk::dpp::fee::Credits; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; -use dash_sdk::dpp::platform_value::{Identifier, string_encoding::Encoding, Value as PlatformValue}; +use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; +use dash_sdk::dpp::identity::{IdentityPublicKey, KeyType, Purpose}; +use dash_sdk::dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; +use dash_sdk::dpp::platform_value::{ + string_encoding::Encoding, Identifier, Value as PlatformValue, +}; use dash_sdk::dpp::prelude::UserFeeIncrease; -use dash_sdk::dpp::state_transition::batch_transition::BatchTransition; use dash_sdk::dpp::state_transition::batch_transition::methods::v0::DocumentsBatchTransitionMethodsV0; -use dash_sdk::dpp::fee::Credits; +use dash_sdk::dpp::state_transition::batch_transition::BatchTransition; use dash_sdk::dpp::state_transition::proof_result::StateTransitionProofResult; use dash_sdk::dpp::state_transition::StateTransition; -use dash_sdk::dpp::document::{Document, DocumentV0Getters, DocumentV0}; -use dash_sdk::dpp::data_contract::accessors::v0::DataContractV0Getters; -use dash_sdk::dpp::data_contract::document_type::methods::DocumentTypeV0Methods; use dash_sdk::platform::transition::broadcast::BroadcastStateTransition; use dash_sdk::platform::Fetch; -use dash_sdk::dpp::platform_value::btreemap_extensions::BTreeValueMapHelper; -use simple_signer::SingleKeySigner; +use js_sys; use serde_json; +use simple_signer::SingleKeySigner; use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; use web_sys; -use js_sys; // WasmSigner has been replaced with SingleKeySigner from simple-signer crate @@ -39,18 +41,18 @@ impl WasmSdk { ) -> Result<(Identifier, Identifier, Option), JsValue> { let contract_id = Identifier::from_string(contract_id_str, Encoding::Base58) .map_err(|e| JsValue::from_str(&format!("Invalid contract ID: {}", e)))?; - + let owner_id = Identifier::from_string(owner_id_str, Encoding::Base58) .map_err(|e| JsValue::from_str(&format!("Invalid owner ID: {}", e)))?; - + let doc_id = doc_id_str .map(|id| Identifier::from_string(id, Encoding::Base58)) .transpose() .map_err(|e| JsValue::from_str(&format!("Invalid document ID: {}", e)))?; - + Ok((contract_id, owner_id, doc_id)) } - + /// Fetch and cache data contract async fn fetch_and_cache_contract( &self, @@ -62,7 +64,7 @@ impl WasmSdk { .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch data contract: {}", e)))? .ok_or_else(|| JsValue::from_str("Data contract not found"))?; - + // Cache the contract in the trusted context if self.network() == dash_sdk::dpp::dashcore::Network::Testnet { if let Some(ref context) = *TESTNET_TRUSTED_CONTEXT.lock().unwrap() { @@ -73,10 +75,10 @@ impl WasmSdk { context.add_known_contract(contract.clone()); } } - + Ok(contract) } - + /// Find authentication key matching the provided private key pub(crate) fn find_authentication_key<'a>( identity: &'a dash_sdk::platform::Identity, @@ -85,20 +87,24 @@ impl WasmSdk { // Derive public key from private key let private_key = PrivateKey::from_wif(private_key_wif) .map_err(|e| JsValue::from_str(&format!("Invalid private key: {}", e)))?; - + let secp = dash_sdk::dpp::dashcore::secp256k1::Secp256k1::new(); let private_key_bytes = private_key.inner.secret_bytes(); - let secret_key = dash_sdk::dpp::dashcore::secp256k1::SecretKey::from_slice(&private_key_bytes) - .map_err(|e| JsValue::from_str(&format!("Invalid private key: {}", e)))?; - let public_key = dash_sdk::dpp::dashcore::secp256k1::PublicKey::from_secret_key(&secp, &secret_key); + let secret_key = + dash_sdk::dpp::dashcore::secp256k1::SecretKey::from_slice(&private_key_bytes) + .map_err(|e| JsValue::from_str(&format!("Invalid private key: {}", e)))?; + let public_key = + dash_sdk::dpp::dashcore::secp256k1::PublicKey::from_secret_key(&secp, &secret_key); let public_key_bytes = public_key.serialize().to_vec(); - + // Calculate hash160 for ECDSA_HASH160 keys let public_key_hash160 = { - use dash_sdk::dpp::dashcore::hashes::{Hash, hash160}; - hash160::Hash::hash(&public_key_bytes).to_byte_array().to_vec() + use dash_sdk::dpp::dashcore::hashes::{hash160, Hash}; + hash160::Hash::hash(&public_key_bytes) + .to_byte_array() + .to_vec() }; - + // Log debug information web_sys::console::log_1(&JsValue::from_str(&format!( "Looking for authentication key with public key: {}", @@ -108,7 +114,7 @@ impl WasmSdk { "Public key hash160: {}", hex::encode(&public_key_hash160) ))); - + // Find matching authentication key let (key_id, matching_key) = identity .public_keys() @@ -117,17 +123,17 @@ impl WasmSdk { if key.purpose() != Purpose::AUTHENTICATION { return false; } - + let matches = match key.key_type() { KeyType::ECDSA_SECP256K1 => { key.data().as_slice() == public_key_bytes.as_slice() - }, + } KeyType::ECDSA_HASH160 => { key.data().as_slice() == public_key_hash160.as_slice() - }, - _ => false + } + _ => false, }; - + if matches { web_sys::console::log_1(&JsValue::from_str(&format!( "Found matching key: ID={}, Type={:?}", @@ -135,23 +141,26 @@ impl WasmSdk { key.key_type() ))); } - + matches }) - .ok_or_else(|| JsValue::from_str("No matching authentication key found for the provided private key"))?; - + .ok_or_else(|| { + JsValue::from_str( + "No matching authentication key found for the provided private key", + ) + })?; + Ok((*key_id, matching_key)) } - + /// Create a signer from WIF private key pub(crate) fn create_signer_from_wif( private_key_wif: &str, network: dash_sdk::dpp::dashcore::Network, ) -> Result { - SingleKeySigner::from_string(private_key_wif, network) - .map_err(|e| JsValue::from_str(&e)) + SingleKeySigner::from_string(private_key_wif, network).map_err(|e| JsValue::from_str(&e)) } - + /// Build JavaScript result object for state transition results fn build_js_result_object( transition_type: &str, @@ -159,39 +168,40 @@ impl WasmSdk { additional_fields: Vec<(&str, JsValue)>, ) -> Result { let result_obj = js_sys::Object::new(); - + // Set type js_sys::Reflect::set( &result_obj, &JsValue::from_str("type"), &JsValue::from_str(transition_type), - ).map_err(|_| JsValue::from_str("Failed to set type"))?; - + ) + .map_err(|_| JsValue::from_str("Failed to set type"))?; + // Set document ID js_sys::Reflect::set( &result_obj, &JsValue::from_str("documentId"), &JsValue::from_str(document_id), - ).map_err(|_| JsValue::from_str("Failed to set documentId"))?; - + ) + .map_err(|_| JsValue::from_str("Failed to set documentId"))?; + // Set additional fields for (key, value) in additional_fields { - js_sys::Reflect::set( - &result_obj, - &JsValue::from_str(key), - &value, - ).map_err(|_| JsValue::from_str(&format!("Failed to set {}", key)))?; + js_sys::Reflect::set(&result_obj, &JsValue::from_str(key), &value) + .map_err(|_| JsValue::from_str(&format!("Failed to set {}", key)))?; } - + Ok(result_obj.into()) } - + /// Get the next revision for a document, handling errors for missing revisions and overflow fn get_next_revision(document: &dash_sdk::platform::Document) -> Result { - let current_revision = document.revision() + let current_revision = document + .revision() .ok_or_else(|| JsValue::from_str("Document revision is missing"))?; - - current_revision.checked_add(1) + + current_revision + .checked_add(1) .ok_or_else(|| JsValue::from_str("Document revision overflow")) } } @@ -223,64 +233,71 @@ impl WasmSdk { private_key_wif: String, ) -> Result { let sdk = self.inner_clone(); - + // Parse identifiers - let (contract_id, owner_identifier, _) = Self::parse_identifiers(&data_contract_id, &owner_id, None)?; - + let (contract_id, owner_identifier, _) = + Self::parse_identifiers(&data_contract_id, &owner_id, None)?; + // Parse entropy let entropy_bytes = hex::decode(&entropy) .map_err(|e| JsValue::from_str(&format!("Invalid entropy hex: {}", e)))?; - + if entropy_bytes.len() != 32 { return Err(JsValue::from_str("Entropy must be exactly 32 bytes")); } - + let mut entropy_array = [0u8; 32]; entropy_array.copy_from_slice(&entropy_bytes); - + // Parse document data let document_data_value: serde_json::Value = serde_json::from_str(&document_data) .map_err(|e| JsValue::from_str(&format!("Invalid JSON document data: {}", e)))?; - + // Fetch and cache the data contract let data_contract = self.fetch_and_cache_contract(contract_id).await?; - + // Get document type let document_type_result = data_contract.document_type_for_name(&document_type); - let document_type_ref = document_type_result - .map_err(|e| JsValue::from_str(&format!("Document type '{}' not found: {}", document_type, e)))?; - + let document_type_ref = document_type_result.map_err(|e| { + JsValue::from_str(&format!( + "Document type '{}' not found: {}", + document_type, e + )) + })?; + // Convert JSON data to platform value let document_data_platform_value: PlatformValue = document_data_value.into(); - + // Create the document directly using the document type's method let platform_version = sdk.version(); - let document = document_type_ref.create_document_from_data( - document_data_platform_value, - owner_identifier, - 0, // block_time (will be set by platform) - 0, // core_block_height (will be set by platform) - entropy_array, - platform_version, - ).map_err(|e| JsValue::from_str(&format!("Failed to create document: {}", e)))?; - + let document = document_type_ref + .create_document_from_data( + document_data_platform_value, + owner_identifier, + 0, // block_time (will be set by platform) + 0, // core_block_height (will be set by platform) + entropy_array, + platform_version, + ) + .map_err(|e| JsValue::from_str(&format!("Failed to create document: {}", e)))?; + // Fetch the identity to get the correct key let identity = dash_sdk::platform::Identity::fetch(&sdk, owner_identifier) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Identity not found"))?; - + // Get identity contract nonce let identity_contract_nonce = sdk .get_identity_contract_nonce(owner_identifier, contract_id, true, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch nonce: {}", e)))?; - + // Find matching authentication key and create signer let (_, matching_key) = Self::find_authentication_key(&identity, &private_key_wif)?; let signer = Self::create_signer_from_wif(&private_key_wif, self.network())?; let public_key = matching_key.clone(); - + // Create the state transition let state_transition = BatchTransition::new_document_creation_transition_from_document( document.clone(), @@ -293,17 +310,20 @@ impl WasmSdk { &signer, platform_version, None, // state_transition_creation_options - ).map_err(|e| JsValue::from_str(&format!("Failed to create document transition: {}", e)))?; - + ) + .map_err(|e| JsValue::from_str(&format!("Failed to create document transition: {}", e)))?; + // Broadcast the transition let proof_result = state_transition .broadcast_and_wait::(&sdk, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to broadcast transition: {}", e)))?; - + // Log the result for debugging - web_sys::console::log_1(&JsValue::from_str("Processing state transition proof result")); - + web_sys::console::log_1(&JsValue::from_str( + "Processing state transition proof result", + )); + // Convert result to JsValue based on the type match proof_result { StateTransitionProofResult::VerifiedDocuments(documents) => { @@ -311,7 +331,7 @@ impl WasmSdk { "Documents in result: {}", documents.len() ))); - + // Try to find the created document for (doc_id, maybe_doc) in documents.iter() { web_sys::console::log_1(&JsValue::from_str(&format!( @@ -320,79 +340,88 @@ impl WasmSdk { maybe_doc.is_some() ))); } - + if let Some((doc_id, maybe_doc)) = documents.into_iter().next() { if let Some(doc) = maybe_doc { // Create JsValue directly instead of using serde_wasm_bindgen let js_result = js_sys::Object::new(); - + js_sys::Reflect::set( &js_result, &JsValue::from_str("type"), &JsValue::from_str("DocumentCreated"), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &js_result, &JsValue::from_str("documentId"), &JsValue::from_str(&doc_id.to_string(Encoding::Base58)), - ).unwrap(); - + ) + .unwrap(); + // Create document object let js_document = js_sys::Object::new(); - + js_sys::Reflect::set( &js_document, &JsValue::from_str("id"), &JsValue::from_str(&doc.id().to_string(Encoding::Base58)), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &js_document, &JsValue::from_str("ownerId"), &JsValue::from_str(&doc.owner_id().to_string(Encoding::Base58)), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &js_document, &JsValue::from_str("dataContractId"), &JsValue::from_str(&data_contract_id), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &js_document, &JsValue::from_str("documentType"), &JsValue::from_str(&document_type), - ).unwrap(); - + ) + .unwrap(); + if let Some(revision) = doc.revision() { js_sys::Reflect::set( &js_document, &JsValue::from_str("revision"), &JsValue::from_f64(revision as f64), - ).unwrap(); + ) + .unwrap(); } - + if let Some(created_at) = doc.created_at() { js_sys::Reflect::set( &js_document, &JsValue::from_str("createdAt"), &JsValue::from_f64(created_at as f64), - ).unwrap(); + ) + .unwrap(); } - + if let Some(updated_at) = doc.updated_at() { js_sys::Reflect::set( &js_document, &JsValue::from_str("updatedAt"), &JsValue::from_f64(updated_at as f64), - ).unwrap(); + ) + .unwrap(); } - + // Add document properties in a "data" field (like DocumentResponse does) let data_obj = js_sys::Object::new(); let properties = doc.properties(); - + for (key, value) in properties { // Convert platform Value to JSON value first, then to JsValue if let Ok(json_value) = serde_json::to_value(value) { @@ -401,97 +430,107 @@ impl WasmSdk { &data_obj, &JsValue::from_str(key), &js_value, - ).unwrap(); + ) + .unwrap(); } } } - - js_sys::Reflect::set( - &js_document, - &JsValue::from_str("data"), - &data_obj, - ).unwrap(); - + + js_sys::Reflect::set(&js_document, &JsValue::from_str("data"), &data_obj) + .unwrap(); + js_sys::Reflect::set( &js_result, &JsValue::from_str("document"), &js_document, - ).unwrap(); - - web_sys::console::log_1(&JsValue::from_str("Document created successfully, returning JS object")); - + ) + .unwrap(); + + web_sys::console::log_1(&JsValue::from_str( + "Document created successfully, returning JS object", + )); + Ok(js_result.into()) } else { // Document was created but not included in response (this is normal) let js_result = js_sys::Object::new(); - + js_sys::Reflect::set( &js_result, &JsValue::from_str("type"), &JsValue::from_str("DocumentCreated"), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &js_result, &JsValue::from_str("documentId"), &JsValue::from_str(&doc_id.to_string(Encoding::Base58)), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &js_result, &JsValue::from_str("message"), &JsValue::from_str("Document created successfully"), - ).unwrap(); - + ) + .unwrap(); + Ok(js_result.into()) } } else { // No documents in result, but transition was successful let js_result = js_sys::Object::new(); - + js_sys::Reflect::set( &js_result, &JsValue::from_str("type"), &JsValue::from_str("DocumentCreated"), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &js_result, &JsValue::from_str("documentId"), &JsValue::from_str(&document.id().to_string(Encoding::Base58)), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &js_result, &JsValue::from_str("message"), &JsValue::from_str("Document created successfully"), - ).unwrap(); - + ) + .unwrap(); + Ok(js_result.into()) } } _ => { // For other result types, just indicate success let js_result = js_sys::Object::new(); - + js_sys::Reflect::set( &js_result, &JsValue::from_str("type"), &JsValue::from_str("DocumentCreated"), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &js_result, &JsValue::from_str("documentId"), &JsValue::from_str(&document.id().to_string(Encoding::Base58)), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &js_result, &JsValue::from_str("message"), &JsValue::from_str("Document created successfully"), - ).unwrap(); - + ) + .unwrap(); + Ok(js_result.into()) } } @@ -524,30 +563,31 @@ impl WasmSdk { private_key_wif: String, ) -> Result { let sdk = self.inner_clone(); - + // Parse identifiers - let (contract_id, owner_identifier, doc_id) = Self::parse_identifiers( - &data_contract_id, - &owner_id, - Some(&document_id) - )?; + let (contract_id, owner_identifier, doc_id) = + Self::parse_identifiers(&data_contract_id, &owner_id, Some(&document_id))?; let doc_id = doc_id.unwrap(); - + // Parse document data let document_data_value: serde_json::Value = serde_json::from_str(&document_data) .map_err(|e| JsValue::from_str(&format!("Invalid JSON document data: {}", e)))?; - + // Fetch and cache the data contract let data_contract = self.fetch_and_cache_contract(contract_id).await?; - + // Get document type let document_type_result = data_contract.document_type_for_name(&document_type); - let document_type_ref = document_type_result - .map_err(|e| JsValue::from_str(&format!("Document type '{}' not found: {}", document_type, e)))?; - + let document_type_ref = document_type_result.map_err(|e| { + JsValue::from_str(&format!( + "Document type '{}' not found: {}", + document_type, e + )) + })?; + // Convert JSON data to platform value let document_data_platform_value: PlatformValue = document_data_value.into(); - + // Create the document using the DocumentV0 constructor let platform_version = sdk.version(); let document = Document::V0(DocumentV0 { @@ -555,7 +595,9 @@ impl WasmSdk { owner_id: owner_identifier, properties: document_data_platform_value .into_btree_string_map() - .map_err(|e| JsValue::from_str(&format!("Failed to convert document data: {}", e)))?, + .map_err(|e| { + JsValue::from_str(&format!("Failed to convert document data: {}", e)) + })?, revision: Some(revision + 1), created_at: None, updated_at: None, @@ -567,24 +609,24 @@ impl WasmSdk { updated_at_core_block_height: None, transferred_at_core_block_height: None, }); - + // Fetch the identity to get the correct key let identity = dash_sdk::platform::Identity::fetch(&sdk, owner_identifier) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Identity not found"))?; - + // Get identity contract nonce let identity_contract_nonce = sdk .get_identity_contract_nonce(owner_identifier, contract_id, true, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch nonce: {}", e)))?; - + // Find matching authentication key and create signer let (_, matching_key) = Self::find_authentication_key(&identity, &private_key_wif)?; let public_key = matching_key.clone(); let signer = Self::create_signer_from_wif(&private_key_wif, self.network())?; - + // Create the state transition let state_transition = BatchTransition::new_document_replacement_transition_from_document( document, @@ -596,14 +638,20 @@ impl WasmSdk { &signer, platform_version, None, // state_transition_creation_options - ).map_err(|e| JsValue::from_str(&format!("Failed to create document replace transition: {}", e)))?; - + ) + .map_err(|e| { + JsValue::from_str(&format!( + "Failed to create document replace transition: {}", + e + )) + })?; + // Broadcast the transition let proof_result = state_transition .broadcast_and_wait::(&sdk, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to broadcast transition: {}", e)))?; - + // Convert result to JsValue based on the type match proof_result { StateTransitionProofResult::VerifiedDocuments(documents) => { @@ -611,74 +659,83 @@ impl WasmSdk { if let Some(doc) = maybe_doc { // Create JsValue directly instead of using serde_wasm_bindgen let js_result = js_sys::Object::new(); - + js_sys::Reflect::set( &js_result, &JsValue::from_str("type"), &JsValue::from_str("DocumentReplaced"), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &js_result, &JsValue::from_str("documentId"), &JsValue::from_str(&doc_id.to_string(Encoding::Base58)), - ).unwrap(); - + ) + .unwrap(); + // Create document object let js_document = js_sys::Object::new(); - + js_sys::Reflect::set( &js_document, &JsValue::from_str("id"), &JsValue::from_str(&doc.id().to_string(Encoding::Base58)), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &js_document, &JsValue::from_str("ownerId"), &JsValue::from_str(&doc.owner_id().to_string(Encoding::Base58)), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &js_document, &JsValue::from_str("dataContractId"), &JsValue::from_str(&data_contract_id), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &js_document, &JsValue::from_str("documentType"), &JsValue::from_str(&document_type), - ).unwrap(); - + ) + .unwrap(); + if let Some(revision) = doc.revision() { js_sys::Reflect::set( &js_document, &JsValue::from_str("revision"), &JsValue::from_f64(revision as f64), - ).unwrap(); + ) + .unwrap(); } - + if let Some(created_at) = doc.created_at() { js_sys::Reflect::set( &js_document, &JsValue::from_str("createdAt"), &JsValue::from_f64(created_at as f64), - ).unwrap(); + ) + .unwrap(); } - + if let Some(updated_at) = doc.updated_at() { js_sys::Reflect::set( &js_document, &JsValue::from_str("updatedAt"), &JsValue::from_f64(updated_at as f64), - ).unwrap(); + ) + .unwrap(); } - + // Add document properties in a "data" field (like DocumentResponse does) let data_obj = js_sys::Object::new(); let properties = doc.properties(); - + for (key, value) in properties { // Convert platform Value to JSON value first, then to JsValue if let Ok(json_value) = serde_json::to_value(value) { @@ -687,97 +744,107 @@ impl WasmSdk { &data_obj, &JsValue::from_str(key), &js_value, - ).unwrap(); + ) + .unwrap(); } } } - - js_sys::Reflect::set( - &js_document, - &JsValue::from_str("data"), - &data_obj, - ).unwrap(); - + + js_sys::Reflect::set(&js_document, &JsValue::from_str("data"), &data_obj) + .unwrap(); + js_sys::Reflect::set( &js_result, &JsValue::from_str("document"), &js_document, - ).unwrap(); - - web_sys::console::log_1(&JsValue::from_str("Document replaced successfully")); - + ) + .unwrap(); + + web_sys::console::log_1(&JsValue::from_str( + "Document replaced successfully", + )); + Ok(js_result.into()) } else { // Document was replaced but not included in response let js_result = js_sys::Object::new(); - + js_sys::Reflect::set( &js_result, &JsValue::from_str("type"), &JsValue::from_str("DocumentReplaced"), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &js_result, &JsValue::from_str("documentId"), &JsValue::from_str(&doc_id.to_string(Encoding::Base58)), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &js_result, &JsValue::from_str("message"), &JsValue::from_str("Document replaced successfully"), - ).unwrap(); - + ) + .unwrap(); + Ok(js_result.into()) } } else { // No documents in result, but transition was successful let js_result = js_sys::Object::new(); - + js_sys::Reflect::set( &js_result, &JsValue::from_str("type"), &JsValue::from_str("DocumentReplaced"), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &js_result, &JsValue::from_str("documentId"), &JsValue::from_str(&document_id), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &js_result, &JsValue::from_str("message"), &JsValue::from_str("Document replaced successfully"), - ).unwrap(); - + ) + .unwrap(); + Ok(js_result.into()) } } _ => { // For other result types, just indicate success let js_result = js_sys::Object::new(); - + js_sys::Reflect::set( &js_result, &JsValue::from_str("type"), &JsValue::from_str("DocumentReplaced"), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &js_result, &JsValue::from_str("documentId"), &JsValue::from_str(&document_id), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &js_result, &JsValue::from_str("message"), &JsValue::from_str("Document replaced successfully"), - ).unwrap(); - + ) + .unwrap(); + Ok(js_result.into()) } } @@ -806,59 +873,57 @@ impl WasmSdk { private_key_wif: String, ) -> Result { let sdk = self.inner_clone(); - + // Parse identifiers - let (contract_id, owner_identifier, doc_id) = Self::parse_identifiers( - &data_contract_id, - &owner_id, - Some(&document_id) - )?; + let (contract_id, owner_identifier, doc_id) = + Self::parse_identifiers(&data_contract_id, &owner_id, Some(&document_id))?; let doc_id = doc_id.unwrap(); - + // Fetch and cache the data contract let data_contract = self.fetch_and_cache_contract(contract_id).await?; - + // Get document type let document_type_result = data_contract.document_type_for_name(&document_type); - let document_type_ref = document_type_result - .map_err(|e| JsValue::from_str(&format!("Document type '{}' not found: {}", document_type, e)))?; - + let document_type_ref = document_type_result.map_err(|e| { + JsValue::from_str(&format!( + "Document type '{}' not found: {}", + document_type, e + )) + })?; + // Fetch the document to get its current revision use dash_sdk::platform::DocumentQuery; - - let query = DocumentQuery::new_with_data_contract_id( - &sdk, - contract_id, - &document_type, - ) - .await - .map_err(|e| JsValue::from_str(&format!("Failed to create document query: {}", e)))? - .with_document_id(&doc_id); - + + let query = DocumentQuery::new_with_data_contract_id(&sdk, contract_id, &document_type) + .await + .map_err(|e| JsValue::from_str(&format!("Failed to create document query: {}", e)))? + .with_document_id(&doc_id); + let existing_doc = dash_sdk::platform::Document::fetch(&sdk, query) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch document: {}", e)))? .ok_or_else(|| JsValue::from_str("Document not found"))?; - - let current_revision = existing_doc.revision() + + let current_revision = existing_doc + .revision() .ok_or_else(|| JsValue::from_str("Document revision is missing"))?; - + // Fetch the identity to get the correct key let identity = dash_sdk::platform::Identity::fetch(&sdk, owner_identifier) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Identity not found"))?; - + // Get identity contract nonce let identity_contract_nonce = sdk .get_identity_contract_nonce(owner_identifier, contract_id, true, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch nonce: {}", e)))?; - + // Find matching authentication key and create signer let (_, matching_key) = Self::find_authentication_key(&identity, &private_key_wif)?; let signer = Self::create_signer_from_wif(&private_key_wif, self.network())?; - + // Create a document for deletion with the correct revision let document = Document::V0(DocumentV0 { id: doc_id, @@ -875,7 +940,7 @@ impl WasmSdk { updated_at_core_block_height: None, transferred_at_core_block_height: None, }); - + // Create a delete transition let transition = BatchTransition::new_document_deletion_transition_from_document( document, @@ -889,16 +954,16 @@ impl WasmSdk { None, // options ) .map_err(|e| JsValue::from_str(&format!("Failed to create transition: {}", e)))?; - + // The transition is already signed, convert to StateTransition let state_transition: StateTransition = transition.into(); - + // Broadcast the state transition state_transition .broadcast(&sdk, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to broadcast: {}", e)))?; - + // Return the result with document ID Self::build_js_result_object( "DocumentDeleted", @@ -932,46 +997,43 @@ impl WasmSdk { private_key_wif: String, ) -> Result { let sdk = self.inner_clone(); - + // Parse identifiers - let (contract_id, owner_identifier, doc_id) = Self::parse_identifiers( - &data_contract_id, - &owner_id, - Some(&document_id), - )?; + let (contract_id, owner_identifier, doc_id) = + Self::parse_identifiers(&data_contract_id, &owner_id, Some(&document_id))?; let doc_id = doc_id.expect("Document ID was provided"); - + let recipient_identifier = Identifier::from_string(&recipient_id, Encoding::Base58) .map_err(|e| JsValue::from_str(&format!("Invalid recipient ID: {}", e)))?; - + // Fetch and cache the data contract let data_contract = self.fetch_and_cache_contract(contract_id).await?; - + // Get document type let document_type_result = data_contract.document_type_for_name(&document_type); - let document_type_ref = document_type_result - .map_err(|e| JsValue::from_str(&format!("Document type '{}' not found: {}", document_type, e)))?; - + let document_type_ref = document_type_result.map_err(|e| { + JsValue::from_str(&format!( + "Document type '{}' not found: {}", + document_type, e + )) + })?; + // Fetch the document to get its current state use dash_sdk::platform::DocumentQuery; - - let query = DocumentQuery::new_with_data_contract_id( - &sdk, - contract_id, - &document_type, - ) - .await - .map_err(|e| JsValue::from_str(&format!("Failed to create document query: {}", e)))? - .with_document_id(&doc_id); - + + let query = DocumentQuery::new_with_data_contract_id(&sdk, contract_id, &document_type) + .await + .map_err(|e| JsValue::from_str(&format!("Failed to create document query: {}", e)))? + .with_document_id(&doc_id); + let document = dash_sdk::platform::Document::fetch(&sdk, query) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch document: {}", e)))? .ok_or_else(|| JsValue::from_str("Document not found"))?; - + // Get the current revision and increment it let next_revision = Self::get_next_revision(&document)?; - + // Create a modified document with incremented revision for the transfer transition let transfer_document = Document::V0(DocumentV0 { id: document.id(), @@ -988,23 +1050,23 @@ impl WasmSdk { updated_at_core_block_height: document.updated_at_core_block_height(), transferred_at_core_block_height: document.transferred_at_core_block_height(), }); - + // Fetch the identity to get the correct key let identity = dash_sdk::platform::Identity::fetch(&sdk, owner_identifier) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Identity not found"))?; - + // Get identity contract nonce let identity_contract_nonce = sdk .get_identity_contract_nonce(owner_identifier, contract_id, true, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch nonce: {}", e)))?; - + // Find matching authentication key and create signer let (_, matching_key) = Self::find_authentication_key(&identity, &private_key_wif)?; let signer = Self::create_signer_from_wif(&private_key_wif, self.network())?; - + // Create a transfer transition let transition = BatchTransition::new_document_transfer_transition_from_document( transfer_document, @@ -1019,16 +1081,16 @@ impl WasmSdk { None, // options ) .map_err(|e| JsValue::from_str(&format!("Failed to create transition: {}", e)))?; - + // The transition is already signed, convert to StateTransition let state_transition: StateTransition = transition.into(); - + // Broadcast the state transition state_transition .broadcast(&sdk, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to broadcast: {}", e)))?; - + // Return the result with document ID and new owner Self::build_js_result_object( "DocumentTransferred", @@ -1065,23 +1127,20 @@ impl WasmSdk { private_key_wif: String, ) -> Result { let sdk = self.inner_clone(); - + // Parse identifiers - let (contract_id, buyer_identifier, doc_id) = Self::parse_identifiers( - &data_contract_id, - &buyer_id, - Some(&document_id), - )?; + let (contract_id, buyer_identifier, doc_id) = + Self::parse_identifiers(&data_contract_id, &buyer_id, Some(&document_id))?; let doc_id = doc_id.expect("Document ID was provided"); - + // Fetch and cache the data contract let data_contract = self.fetch_and_cache_contract(contract_id).await?; - + // Get document type from contract let document_type_ref = data_contract .document_type_for_name(&document_type) .map_err(|e| JsValue::from_str(&format!("Document type not found: {}", e)))?; - + // Fetch the document to purchase let query = dash_sdk::platform::documents::document_query::DocumentQuery::new_with_data_contract_id( &sdk, @@ -1091,29 +1150,29 @@ impl WasmSdk { .await .map_err(|e| JsValue::from_str(&format!("Failed to create document query: {}", e)))? .with_document_id(&doc_id); - + let document = dash_sdk::platform::Document::fetch(&sdk, query) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch document: {}", e)))? .ok_or_else(|| JsValue::from_str("Document not found"))?; - + // Verify the document has a price and it matches let listed_price = document .properties() .get_optional_integer::("$price") .map_err(|e| JsValue::from_str(&format!("Failed to get document price: {}", e)))? .ok_or_else(|| JsValue::from_str("Document is not for sale (no price set)"))?; - + if listed_price != price { return Err(JsValue::from_str(&format!( - "Price mismatch: document is listed for {} but purchase attempted with {}", + "Price mismatch: document is listed for {} but purchase attempted with {}", listed_price, price ))); } - + // Get the current revision and increment it let next_revision = Self::get_next_revision(&document)?; - + // Create a modified document with incremented revision for the purchase transition let purchase_document = Document::V0(DocumentV0 { id: document.id(), @@ -1130,23 +1189,25 @@ impl WasmSdk { updated_at_core_block_height: document.updated_at_core_block_height(), transferred_at_core_block_height: document.transferred_at_core_block_height(), }); - + // Fetch buyer identity let buyer_identity = dash_sdk::platform::Identity::fetch(&sdk, buyer_identifier) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch buyer identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Buyer identity not found"))?; - + // Find matching authentication key and create signer let (_, matching_key) = Self::find_authentication_key(&buyer_identity, &private_key_wif)?; let signer = Self::create_signer_from_wif(&private_key_wif, self.network())?; - + // Get identity contract nonce let identity_contract_nonce = sdk .get_identity_contract_nonce(buyer_identifier, contract_id, true, None) .await - .map_err(|e| JsValue::from_str(&format!("Failed to get identity contract nonce: {}", e)))?; - + .map_err(|e| { + JsValue::from_str(&format!("Failed to get identity contract nonce: {}", e)) + })?; + // Create document purchase transition let transition = BatchTransition::new_document_purchase_transition_from_document( purchase_document, @@ -1162,13 +1223,13 @@ impl WasmSdk { None, // Default options ) .map_err(|e| JsValue::from_str(&format!("Failed to create purchase transition: {}", e)))?; - + // Broadcast the transition let proof_result = transition .broadcast_and_wait::(&sdk, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to broadcast purchase: {}", e)))?; - + // Handle the proof result match proof_result { StateTransitionProofResult::VerifiedDocuments(documents) => { @@ -1177,19 +1238,29 @@ impl WasmSdk { ("status", JsValue::from_str("success")), ("newOwnerId", JsValue::from_str(&buyer_id)), ("pricePaid", JsValue::from_f64(price as f64)), - ("message", JsValue::from_str("Document purchased successfully")), + ( + "message", + JsValue::from_str("Document purchased successfully"), + ), ]; - + // If we have the updated document in the response, include basic info if let Some((_, maybe_doc)) = documents.into_iter().next() { if let Some(doc) = maybe_doc { additional_fields.push(("documentUpdated", JsValue::from_bool(true))); - additional_fields.push(("revision", JsValue::from_f64(doc.revision().unwrap_or(0) as f64))); + additional_fields.push(( + "revision", + JsValue::from_f64(doc.revision().unwrap_or(0) as f64), + )); } } - - Self::build_js_result_object("DocumentPurchased", &doc_id.to_string(Encoding::Base58), additional_fields) - }, + + Self::build_js_result_object( + "DocumentPurchased", + &doc_id.to_string(Encoding::Base58), + additional_fields, + ) + } _ => { // Purchase was processed but document not returned Self::build_js_result_object( @@ -1229,23 +1300,20 @@ impl WasmSdk { private_key_wif: String, ) -> Result { let sdk = self.inner_clone(); - + // Parse identifiers - let (contract_id, owner_identifier, doc_id) = Self::parse_identifiers( - &data_contract_id, - &owner_id, - Some(&document_id), - )?; + let (contract_id, owner_identifier, doc_id) = + Self::parse_identifiers(&data_contract_id, &owner_id, Some(&document_id))?; let doc_id = doc_id.expect("Document ID was provided"); - + // Fetch and cache the data contract let data_contract = self.fetch_and_cache_contract(contract_id).await?; - + // Get document type from contract let document_type_ref = data_contract .document_type_for_name(&document_type) .map_err(|e| JsValue::from_str(&format!("Document type not found: {}", e)))?; - + // Fetch the existing document to update its price let query = dash_sdk::platform::documents::document_query::DocumentQuery::new_with_data_contract_id( &sdk, @@ -1255,20 +1323,22 @@ impl WasmSdk { .await .map_err(|e| JsValue::from_str(&format!("Failed to create document query: {}", e)))? .with_document_id(&doc_id); - + let existing_doc = Document::fetch(&sdk, query) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch document: {}", e)))? .ok_or_else(|| JsValue::from_str("Document not found"))?; - + // Verify ownership if existing_doc.owner_id() != owner_identifier { - return Err(JsValue::from_str("Only the document owner can set its price")); + return Err(JsValue::from_str( + "Only the document owner can set its price", + )); } - + // Get the current revision and increment it let next_revision = Self::get_next_revision(&existing_doc)?; - + // Create a modified document with incremented revision for the price update transition let price_update_document = Document::V0(DocumentV0 { id: existing_doc.id(), @@ -1285,23 +1355,23 @@ impl WasmSdk { updated_at_core_block_height: existing_doc.updated_at_core_block_height(), transferred_at_core_block_height: existing_doc.transferred_at_core_block_height(), }); - + // Fetch the identity to get the authentication key let identity = dash_sdk::platform::Identity::fetch(&sdk, owner_identifier) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Identity not found"))?; - + // Find matching authentication key and create signer let (_, matching_key) = Self::find_authentication_key(&identity, &private_key_wif)?; let signer = Self::create_signer_from_wif(&private_key_wif, self.network())?; - + // Get identity contract nonce let identity_contract_nonce = sdk .get_identity_contract_nonce(owner_identifier, contract_id, true, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch nonce: {}", e)))?; - + // Create the price update transition using the dedicated method let transition = BatchTransition::new_document_update_price_transition_from_document( price_update_document, @@ -1315,17 +1385,19 @@ impl WasmSdk { sdk.version(), None, // options ) - .map_err(|e| JsValue::from_str(&format!("Failed to create price update transition: {}", e)))?; - + .map_err(|e| { + JsValue::from_str(&format!("Failed to create price update transition: {}", e)) + })?; + // The transition is already signed, convert to StateTransition let state_transition: StateTransition = transition.into(); - + // Broadcast the state transition state_transition .broadcast(&sdk, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to broadcast: {}", e)))?; - + // Return the result with document ID and price Self::build_js_result_object( "DocumentPriceSet", @@ -1337,4 +1409,3 @@ impl WasmSdk { ) } } - diff --git a/packages/wasm-sdk/src/state_transitions/identity/mod.rs b/packages/wasm-sdk/src/state_transitions/identity/mod.rs index 0b21433ee4..f7ce2cb64b 100644 --- a/packages/wasm-sdk/src/state_transitions/identity/mod.rs +++ b/packages/wasm-sdk/src/state_transitions/identity/mod.rs @@ -1,22 +1,22 @@ use crate::sdk::WasmSdk; use dash_sdk::dpp::dashcore::PrivateKey; -use dash_sdk::dpp::identity::{Identity, IdentityPublicKey, KeyType, Purpose, SecurityLevel}; use dash_sdk::dpp::identity::accessors::IdentityGettersV0; use dash_sdk::dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0; -use dash_sdk::dpp::platform_value::{BinaryData, Identifier, string_encoding::Encoding}; +use dash_sdk::dpp::identity::{Identity, IdentityPublicKey, KeyType, Purpose, SecurityLevel}; +use dash_sdk::dpp::platform_value::{string_encoding::Encoding, BinaryData, Identifier}; +use dash_sdk::dpp::prelude::AssetLockProof; use dash_sdk::dpp::prelude::UserFeeIncrease; -use dash_sdk::dpp::state_transition::identity_credit_transfer_transition::IdentityCreditTransferTransition; use dash_sdk::dpp::state_transition::identity_credit_transfer_transition::methods::IdentityCreditTransferTransitionMethodsV0; +use dash_sdk::dpp::state_transition::identity_credit_transfer_transition::IdentityCreditTransferTransition; use dash_sdk::platform::transition::broadcast::BroadcastStateTransition; +use dash_sdk::platform::transition::put_identity::PutIdentity; +use dash_sdk::platform::transition::top_up_identity::TopUpIdentity; use dash_sdk::platform::Fetch; -use simple_signer::{SingleKeySigner, signer::SimpleSigner}; +use js_sys; +use simple_signer::{signer::SimpleSigner, SingleKeySigner}; use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; -use js_sys; use web_sys; -use dash_sdk::platform::transition::put_identity::PutIdentity; -use dash_sdk::platform::transition::top_up_identity::TopUpIdentity; -use dash_sdk::dpp::prelude::AssetLockProof; #[wasm_bindgen] impl WasmSdk { @@ -49,59 +49,82 @@ impl WasmSdk { public_keys: String, ) -> Result { let sdk = self.inner_clone(); - + // Debug log all parameters web_sys::console::log_1(&JsValue::from_str(&format!("identityCreate called with:"))); - web_sys::console::log_1(&JsValue::from_str(&format!(" asset_lock_proof (length {}): {}", asset_lock_proof.len(), if asset_lock_proof.len() > 100 { format!("{}...", &asset_lock_proof[..100]) } else { asset_lock_proof.clone() }))); - web_sys::console::log_1(&JsValue::from_str(&format!(" asset_lock_proof_private_key: [REDACTED] (length: {})", asset_lock_proof_private_key.len()))); - web_sys::console::log_1(&JsValue::from_str(&format!(" public_keys: {}", public_keys))); - + web_sys::console::log_1(&JsValue::from_str(&format!( + " asset_lock_proof (length {}): {}", + asset_lock_proof.len(), + if asset_lock_proof.len() > 100 { + format!("{}...", &asset_lock_proof[..100]) + } else { + asset_lock_proof.clone() + } + ))); + web_sys::console::log_1(&JsValue::from_str(&format!( + " asset_lock_proof_private_key: [REDACTED] (length: {})", + asset_lock_proof_private_key.len() + ))); + web_sys::console::log_1(&JsValue::from_str(&format!( + " public_keys: {}", + public_keys + ))); + // Parse asset lock proof - try hex first, then JSON - let asset_lock_proof: AssetLockProof = if asset_lock_proof.chars().all(|c| c.is_ascii_hexdigit()) { + let asset_lock_proof: AssetLockProof = if asset_lock_proof + .chars() + .all(|c| c.is_ascii_hexdigit()) + { // It's hex encoded - decode and parse as JSON from the decoded bytes let asset_lock_proof_bytes = hex::decode(&asset_lock_proof) .map_err(|e| JsValue::from_str(&format!("Invalid asset lock proof hex: {}", e)))?; - + // Convert bytes to string and parse as JSON - let json_str = String::from_utf8(asset_lock_proof_bytes) - .map_err(|e| JsValue::from_str(&format!("Invalid UTF-8 in asset lock proof: {}", e)))?; - - serde_json::from_str(&json_str) - .map_err(|e| JsValue::from_str(&format!("Failed to parse asset lock proof JSON: {}", e)))? + let json_str = String::from_utf8(asset_lock_proof_bytes).map_err(|e| { + JsValue::from_str(&format!("Invalid UTF-8 in asset lock proof: {}", e)) + })?; + + serde_json::from_str(&json_str).map_err(|e| { + JsValue::from_str(&format!("Failed to parse asset lock proof JSON: {}", e)) + })? } else { // Try JSON directly serde_json::from_str(&asset_lock_proof) .map_err(|e| JsValue::from_str(&format!("Invalid asset lock proof JSON: {}", e)))? }; - + // Parse private key - WIF format // Log the private key format for debugging - web_sys::console::log_1(&JsValue::from_str(&format!("Private key format validation - length: {}", - asset_lock_proof_private_key.len()))); - + web_sys::console::log_1(&JsValue::from_str(&format!( + "Private key format validation - length: {}", + asset_lock_proof_private_key.len() + ))); + let private_key = PrivateKey::from_wif(&asset_lock_proof_private_key) .map_err(|e| JsValue::from_str(&format!("Invalid private key: {}", e)))?; - + // Parse public keys from JSON let keys_data: serde_json::Value = serde_json::from_str(&public_keys) .map_err(|e| JsValue::from_str(&format!("Invalid JSON for public_keys: {}", e)))?; - - let keys_array = keys_data.as_array() + + let keys_array = keys_data + .as_array() .ok_or_else(|| JsValue::from_str("public_keys must be a JSON array"))?; - + // Create identity public keys and collect private keys for signing let mut identity_public_keys = std::collections::BTreeMap::new(); let mut signer = SimpleSigner::default(); let mut key_id = 0u32; - + for key_data in keys_array { - let key_type_str = key_data["keyType"].as_str() + let key_type_str = key_data["keyType"] + .as_str() .ok_or_else(|| JsValue::from_str("keyType is required"))?; - let purpose_str = key_data["purpose"].as_str() + let purpose_str = key_data["purpose"] + .as_str() .ok_or_else(|| JsValue::from_str("purpose is required"))?; - let security_level_str = key_data["securityLevel"].as_str() - .unwrap_or("HIGH"); - + let security_level_str = key_data["securityLevel"].as_str().unwrap_or("HIGH"); + // Parse key type first let key_type = match key_type_str { "ECDSA_SECP256K1" => KeyType::ECDSA_SECP256K1, @@ -109,9 +132,14 @@ impl WasmSdk { "ECDSA_HASH160" => KeyType::ECDSA_HASH160, "BIP13_SCRIPT_HASH" => KeyType::BIP13_SCRIPT_HASH, "EDDSA_25519_HASH160" => KeyType::EDDSA_25519_HASH160, - _ => return Err(JsValue::from_str(&format!("Unknown key type: {}", key_type_str))) + _ => { + return Err(JsValue::from_str(&format!( + "Unknown key type: {}", + key_type_str + ))) + } }; - + // Parse purpose let purpose = match purpose_str { "AUTHENTICATION" => Purpose::AUTHENTICATION, @@ -120,26 +148,32 @@ impl WasmSdk { "TRANSFER" => Purpose::TRANSFER, "SYSTEM" => Purpose::SYSTEM, "VOTING" => Purpose::VOTING, - _ => return Err(JsValue::from_str(&format!("Unknown purpose: {}", purpose_str))) + _ => { + return Err(JsValue::from_str(&format!( + "Unknown purpose: {}", + purpose_str + ))) + } }; - + // Parse security level let security_level = match security_level_str { "MASTER" => SecurityLevel::MASTER, "CRITICAL" => SecurityLevel::CRITICAL, "HIGH" => SecurityLevel::HIGH, "MEDIUM" => SecurityLevel::MEDIUM, - _ => SecurityLevel::HIGH + _ => SecurityLevel::HIGH, }; - + // Handle key data based on key type let (public_key_data, private_key_bytes) = match key_type { KeyType::ECDSA_HASH160 => { // Derive HASH160 data from the private key if provided if let Some(private_key_hex) = key_data["privateKeyHex"].as_str() { // Decode private key from hex - let bytes = hex::decode(private_key_hex) - .map_err(|e| JsValue::from_str(&format!("Invalid private key hex: {}", e)))?; + let bytes = hex::decode(private_key_hex).map_err(|e| { + JsValue::from_str(&format!("Invalid private key hex: {}", e)) + })?; if bytes.len() != 32 { return Err(JsValue::from_str(&format!( @@ -153,7 +187,10 @@ impl WasmSdk { // Derive HASH160 public key data from private key using network let derived_data = key_type - .public_key_data_from_private_key_data(&private_key_array, self.network()) + .public_key_data_from_private_key_data( + &private_key_array, + self.network(), + ) .map_err(|e| { JsValue::from_str(&format!( "Failed to derive ECDSA_HASH160 public key data: {}", @@ -165,7 +202,9 @@ impl WasmSdk { (derived_data, [0u8; 32]) } else if let Some(data_str) = key_data["data"].as_str() { let key_data_bytes = dash_sdk::dpp::dashcore::base64::decode(data_str) - .map_err(|e| JsValue::from_str(&format!("Invalid base64 key data: {}", e)))?; + .map_err(|e| { + JsValue::from_str(&format!("Invalid base64 key data: {}", e)) + })?; // Enforce correct HASH160 size (20 bytes). if key_data_bytes.len() != 20 { @@ -181,73 +220,100 @@ impl WasmSdk { "ECDSA_HASH160 requires either 'privateKeyHex' to derive from or 'data' (base64-encoded 20-byte hash)", )); } - }, + } KeyType::ECDSA_SECP256K1 => { // For ECDSA signing keys, support both hex and WIF formats - let private_key_bytes = if let Some(private_key_hex) = key_data["privateKeyHex"].as_str() { + let private_key_bytes = if let Some(private_key_hex) = + key_data["privateKeyHex"].as_str() + { // Decode private key from hex - let bytes = hex::decode(private_key_hex) - .map_err(|e| JsValue::from_str(&format!("Invalid private key hex: {}", e)))?; - + let bytes = hex::decode(private_key_hex).map_err(|e| { + JsValue::from_str(&format!("Invalid private key hex: {}", e)) + })?; + if bytes.len() != 32 { - return Err(JsValue::from_str(&format!("Private key must be 32 bytes, got {}", bytes.len()))); + return Err(JsValue::from_str(&format!( + "Private key must be 32 bytes, got {}", + bytes.len() + ))); } - + let mut private_key_array = [0u8; 32]; private_key_array.copy_from_slice(&bytes); private_key_array } else if let Some(private_key_wif) = key_data["privateKeyWif"].as_str() { // Parse WIF format private key - let private_key = PrivateKey::from_wif(private_key_wif) - .map_err(|e| JsValue::from_str(&format!("Invalid WIF private key: {}", e)))?; + let private_key = PrivateKey::from_wif(private_key_wif).map_err(|e| { + JsValue::from_str(&format!("Invalid WIF private key: {}", e)) + })?; private_key.inner.secret_bytes() } else { - return Err(JsValue::from_str("ECDSA_SECP256K1 keys require either privateKeyHex or privateKeyWif")); + return Err(JsValue::from_str( + "ECDSA_SECP256K1 keys require either privateKeyHex or privateKeyWif", + )); }; - + // Derive public key data from private key - let public_key_data = key_type.public_key_data_from_private_key_data( - &private_key_bytes, - self.network() - ).map_err(|e| JsValue::from_str(&format!("Failed to derive ECDSA_SECP256K1 public key data: {}", e)))?; - + let public_key_data = key_type + .public_key_data_from_private_key_data(&private_key_bytes, self.network()) + .map_err(|e| { + JsValue::from_str(&format!( + "Failed to derive ECDSA_SECP256K1 public key data: {}", + e + )) + })?; + (public_key_data, private_key_bytes) - }, + } KeyType::BLS12_381 => { // BLS12_381 keys only support hex format (WIF is not valid for BLS keys) if key_data["privateKeyWif"].is_string() { - return Err(JsValue::from_str("BLS12_381 keys do not support WIF format, use privateKeyHex only")); + return Err(JsValue::from_str( + "BLS12_381 keys do not support WIF format, use privateKeyHex only", + )); } - - let private_key_bytes = if let Some(private_key_hex) = key_data["privateKeyHex"].as_str() { - // Decode private key from hex - let bytes = hex::decode(private_key_hex) - .map_err(|e| JsValue::from_str(&format!("Invalid private key hex: {}", e)))?; - - if bytes.len() != 32 { - return Err(JsValue::from_str(&format!("Private key must be 32 bytes, got {}", bytes.len()))); - } - - let mut private_key_array = [0u8; 32]; - private_key_array.copy_from_slice(&bytes); - private_key_array - } else { - return Err(JsValue::from_str("BLS12_381 keys require privateKeyHex")); - }; - + + let private_key_bytes = + if let Some(private_key_hex) = key_data["privateKeyHex"].as_str() { + // Decode private key from hex + let bytes = hex::decode(private_key_hex).map_err(|e| { + JsValue::from_str(&format!("Invalid private key hex: {}", e)) + })?; + + if bytes.len() != 32 { + return Err(JsValue::from_str(&format!( + "Private key must be 32 bytes, got {}", + bytes.len() + ))); + } + + let mut private_key_array = [0u8; 32]; + private_key_array.copy_from_slice(&bytes); + private_key_array + } else { + return Err(JsValue::from_str("BLS12_381 keys require privateKeyHex")); + }; + // Derive public key data from private key - let public_key_data = key_type.public_key_data_from_private_key_data( - &private_key_bytes, - self.network() - ).map_err(|e| JsValue::from_str(&format!("Failed to derive BLS12_381 public key data: {}", e)))?; - + let public_key_data = key_type + .public_key_data_from_private_key_data(&private_key_bytes, self.network()) + .map_err(|e| { + JsValue::from_str(&format!( + "Failed to derive BLS12_381 public key data: {}", + e + )) + })?; + (public_key_data, private_key_bytes) - }, + } _ => { - return Err(JsValue::from_str(&format!("Unsupported key type for identity creation: {}", key_type_str))); + return Err(JsValue::from_str(&format!( + "Unsupported key type for identity creation: {}", + key_type_str + ))); } }; - + // Create the identity public key use dash_sdk::dpp::identity::identity_public_key::v0::IdentityPublicKeyV0; let public_key = IdentityPublicKey::V0(IdentityPublicKeyV0 { @@ -260,16 +326,16 @@ impl WasmSdk { data: BinaryData::new(public_key_data), disabled_at: None, }); - + // Add the public key and its private key to the signer (only for signing key types) if key_type != KeyType::ECDSA_HASH160 { signer.add_key(public_key.clone(), private_key_bytes); } - + identity_public_keys.insert(key_id, public_key); key_id += 1; } - + // Create identity use dash_sdk::dpp::identity::v0::IdentityV0; let identity = Identity::V0(IdentityV0 { @@ -278,44 +344,77 @@ impl WasmSdk { balance: 0, revision: 0, }); - + // Use the SimpleSigner we built with all the identity keys // The signer now contains all private keys for signing each public key individually - + // Put identity to platform and wait let created_identity = match identity - .put_to_platform_and_wait_for_response(&sdk, asset_lock_proof, &private_key, &signer, None) + .put_to_platform_and_wait_for_response( + &sdk, + asset_lock_proof, + &private_key, + &signer, + None, + ) .await { Ok(identity) => identity, Err(e) => { // Extract more detailed error information let error_msg = format!("Failed to create identity: {}", e); - - web_sys::console::error_1(&JsValue::from_str(&format!("Identity creation failed: {}", error_msg))); + + web_sys::console::error_1(&JsValue::from_str(&format!( + "Identity creation failed: {}", + error_msg + ))); return Err(JsValue::from_str(&error_msg)); } }; - + // Create JavaScript result object let result_obj = js_sys::Object::new(); - - js_sys::Reflect::set(&result_obj, &JsValue::from_str("status"), &JsValue::from_str("success")) - .map_err(|e| JsValue::from_str(&format!("Failed to set status: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("identityId"), &JsValue::from_str(&created_identity.id().to_string(Encoding::Base58))) - .map_err(|e| JsValue::from_str(&format!("Failed to set identityId: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("balance"), &JsValue::from_f64(created_identity.balance() as f64)) - .map_err(|e| JsValue::from_str(&format!("Failed to set balance: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("revision"), &JsValue::from_f64(created_identity.revision() as f64)) - .map_err(|e| JsValue::from_str(&format!("Failed to set revision: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("publicKeys"), &JsValue::from_f64(created_identity.public_keys().len() as f64)) - .map_err(|e| JsValue::from_str(&format!("Failed to set publicKeys: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("message"), &JsValue::from_str("Identity created successfully")) - .map_err(|e| JsValue::from_str(&format!("Failed to set message: {:?}", e)))?; - + + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("status"), + &JsValue::from_str("success"), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set status: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("identityId"), + &JsValue::from_str(&created_identity.id().to_string(Encoding::Base58)), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set identityId: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("balance"), + &JsValue::from_f64(created_identity.balance() as f64), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set balance: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("revision"), + &JsValue::from_f64(created_identity.revision() as f64), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set revision: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("publicKeys"), + &JsValue::from_f64(created_identity.public_keys().len() as f64), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set publicKeys: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("message"), + &JsValue::from_str("Identity created successfully"), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set message: {:?}", e)))?; + Ok(result_obj.into()) } - + /// Top up an existing identity with additional credits. /// /// # Arguments @@ -335,37 +434,44 @@ impl WasmSdk { asset_lock_proof_private_key: String, ) -> Result { let sdk = self.inner_clone(); - + // Parse identity identifier let identifier = Identifier::from_string(&identity_id, Encoding::Base58) .map_err(|e| JsValue::from_str(&format!("Invalid identity ID: {}", e)))?; - + // Parse asset lock proof - try hex first, then JSON - let asset_lock_proof: AssetLockProof = if asset_lock_proof.chars().all(|c| c.is_ascii_hexdigit()) { + let asset_lock_proof: AssetLockProof = if asset_lock_proof + .chars() + .all(|c| c.is_ascii_hexdigit()) + { // It's hex encoded - decode and parse as JSON from the decoded bytes let asset_lock_proof_bytes = hex::decode(&asset_lock_proof) .map_err(|e| JsValue::from_str(&format!("Invalid asset lock proof hex: {}", e)))?; - + // Convert bytes to string and parse as JSON - let json_str = String::from_utf8(asset_lock_proof_bytes) - .map_err(|e| JsValue::from_str(&format!("Invalid UTF-8 in asset lock proof: {}", e)))?; - - serde_json::from_str(&json_str) - .map_err(|e| JsValue::from_str(&format!("Failed to parse asset lock proof JSON: {}", e)))? + let json_str = String::from_utf8(asset_lock_proof_bytes).map_err(|e| { + JsValue::from_str(&format!("Invalid UTF-8 in asset lock proof: {}", e)) + })?; + + serde_json::from_str(&json_str).map_err(|e| { + JsValue::from_str(&format!("Failed to parse asset lock proof JSON: {}", e)) + })? } else { // Try JSON directly serde_json::from_str(&asset_lock_proof) .map_err(|e| JsValue::from_str(&format!("Invalid asset lock proof JSON: {}", e)))? }; - + // Parse private key - WIF format // Log the private key format for debugging - web_sys::console::log_1(&JsValue::from_str(&format!("Private key format validation - length: {}", - asset_lock_proof_private_key.len()))); - + web_sys::console::log_1(&JsValue::from_str(&format!( + "Private key format validation - length: {}", + asset_lock_proof_private_key.len() + ))); + let private_key = PrivateKey::from_wif(&asset_lock_proof_private_key) .map_err(|e| JsValue::from_str(&format!("Invalid private key: {}", e)))?; - + // Fetch the identity let identity = match dash_sdk::platform::Identity::fetch(&sdk, identifier).await { Ok(Some(identity)) => identity, @@ -373,17 +479,17 @@ impl WasmSdk { let error_msg = format!("Identity not found: {}", identifier); web_sys::console::error_1(&JsValue::from_str(&error_msg)); return Err(JsValue::from_str(&error_msg)); - }, + } Err(e) => { let error_msg = format!("Failed to fetch identity: {}", e); web_sys::console::error_1(&JsValue::from_str(&error_msg)); return Err(JsValue::from_str(&error_msg)); } }; - + // Get the initial balance let initial_balance = identity.balance(); - + // Top up the identity let new_balance = match identity .top_up_identity(&sdk, asset_lock_proof, &private_key, None, None) @@ -396,26 +502,46 @@ impl WasmSdk { return Err(JsValue::from_str(&error_msg)); } }; - + let topped_up_amount = new_balance.saturating_sub(initial_balance); - + // Create JavaScript result object let result_obj = js_sys::Object::new(); - - js_sys::Reflect::set(&result_obj, &JsValue::from_str("status"), &JsValue::from_str("success")) - .map_err(|e| JsValue::from_str(&format!("Failed to set status: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("identityId"), &JsValue::from_str(&identity_id)) - .map_err(|e| JsValue::from_str(&format!("Failed to set identityId: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("newBalance"), &JsValue::from_f64(new_balance as f64)) - .map_err(|e| JsValue::from_str(&format!("Failed to set newBalance: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("toppedUpAmount"), &JsValue::from_f64(topped_up_amount as f64)) - .map_err(|e| JsValue::from_str(&format!("Failed to set toppedUpAmount: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("message"), &JsValue::from_str("Identity topped up successfully")) - .map_err(|e| JsValue::from_str(&format!("Failed to set message: {:?}", e)))?; - + + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("status"), + &JsValue::from_str("success"), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set status: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("identityId"), + &JsValue::from_str(&identity_id), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set identityId: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("newBalance"), + &JsValue::from_f64(new_balance as f64), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set newBalance: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("toppedUpAmount"), + &JsValue::from_f64(topped_up_amount as f64), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set toppedUpAmount: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("message"), + &JsValue::from_str("Identity topped up successfully"), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set message: {:?}", e)))?; + Ok(result_obj.into()) } - + /// Transfer credits from one identity to another. /// /// # Arguments @@ -439,81 +565,95 @@ impl WasmSdk { key_id: Option, ) -> Result { let sdk = self.inner_clone(); - + // Parse identifiers let sender_identifier = Identifier::from_string(&sender_id, Encoding::Base58) .map_err(|e| JsValue::from_str(&format!("Invalid sender ID: {}", e)))?; - + let recipient_identifier = Identifier::from_string(&recipient_id, Encoding::Base58) .map_err(|e| JsValue::from_str(&format!("Invalid recipient ID: {}", e)))?; - + // Validate not sending to self if sender_identifier == recipient_identifier { return Err(JsValue::from_str("Cannot transfer credits to yourself")); } - + // Validate amount if amount == 0 { return Err(JsValue::from_str("Transfer amount must be greater than 0")); } - + // Fetch sender identity let sender_identity = dash_sdk::platform::Identity::fetch(&sdk, sender_identifier) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch sender identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Sender identity not found"))?; - + // Parse private key and find matching public key let private_key_bytes = dash_sdk::dpp::dashcore::PrivateKey::from_wif(&private_key_wif) .map_err(|e| JsValue::from_str(&format!("Invalid private key: {}", e)))? .inner .secret_bytes(); - + let secp = dash_sdk::dpp::dashcore::secp256k1::Secp256k1::new(); - let secret_key = dash_sdk::dpp::dashcore::secp256k1::SecretKey::from_slice(&private_key_bytes) - .map_err(|e| JsValue::from_str(&format!("Invalid secret key: {}", e)))?; - let public_key = dash_sdk::dpp::dashcore::secp256k1::PublicKey::from_secret_key(&secp, &secret_key); + let secret_key = + dash_sdk::dpp::dashcore::secp256k1::SecretKey::from_slice(&private_key_bytes) + .map_err(|e| JsValue::from_str(&format!("Invalid secret key: {}", e)))?; + let public_key = + dash_sdk::dpp::dashcore::secp256k1::PublicKey::from_secret_key(&secp, &secret_key); let public_key_bytes = public_key.serialize(); - + // Create public key hash using hash160 let public_key_hash160 = { - use dash_sdk::dpp::dashcore::hashes::{Hash, hash160}; - hash160::Hash::hash(&public_key_bytes[..]).to_byte_array().to_vec() + use dash_sdk::dpp::dashcore::hashes::{hash160, Hash}; + hash160::Hash::hash(&public_key_bytes[..]) + .to_byte_array() + .to_vec() }; - + // Find matching key - prioritize key_id if provided, otherwise find any matching key let matching_key = if let Some(requested_key_id) = key_id { // Find specific key by ID - sender_identity.public_keys() + sender_identity + .public_keys() .get(&requested_key_id) .filter(|key| { - key.purpose() == Purpose::TRANSFER && - key.key_type() == KeyType::ECDSA_HASH160 && - key.data().as_slice() == public_key_hash160.as_slice() + key.purpose() == Purpose::TRANSFER + && key.key_type() == KeyType::ECDSA_HASH160 + && key.data().as_slice() == public_key_hash160.as_slice() }) - .ok_or_else(|| JsValue::from_str(&format!("Key with ID {} not found or doesn't match private key", requested_key_id)))? + .ok_or_else(|| { + JsValue::from_str(&format!( + "Key with ID {} not found or doesn't match private key", + requested_key_id + )) + })? } else { // Find any matching transfer key - sender_identity.public_keys().iter() + sender_identity + .public_keys() + .iter() .find(|(_, key)| { - key.purpose() == Purpose::TRANSFER && - key.key_type() == KeyType::ECDSA_HASH160 && - key.data().as_slice() == public_key_hash160.as_slice() + key.purpose() == Purpose::TRANSFER + && key.key_type() == KeyType::ECDSA_HASH160 + && key.data().as_slice() == public_key_hash160.as_slice() }) .map(|(_, key)| key) - .ok_or_else(|| JsValue::from_str("No matching transfer key found for the provided private key"))? + .ok_or_else(|| { + JsValue::from_str("No matching transfer key found for the provided private key") + })? }; - + // Get identity nonce let identity_nonce = sdk .get_identity_nonce(sender_identifier, true, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to get identity nonce: {}", e)))?; - + // Create signer let signer = SingleKeySigner::from_string(&private_key_wif, self.network()) .map_err(|e| JsValue::from_str(&e))?; - + // Create the credit transfer transition let state_transition = IdentityCreditTransferTransition::try_from_identity( &sender_identity, @@ -527,32 +667,51 @@ impl WasmSdk { None, // No version override ) .map_err(|e| JsValue::from_str(&format!("Failed to create transfer transition: {}", e)))?; - + // Broadcast the transition use dash_sdk::dpp::state_transition::proof_result::StateTransitionProofResult; let _result = state_transition .broadcast_and_wait::(&sdk, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to broadcast transfer: {}", e)))?; - - + // Create JavaScript result object let result_obj = js_sys::Object::new(); - - js_sys::Reflect::set(&result_obj, &JsValue::from_str("status"), &JsValue::from_str("success")) - .map_err(|e| JsValue::from_str(&format!("Failed to set status: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("senderId"), &JsValue::from_str(&sender_id)) - .map_err(|e| JsValue::from_str(&format!("Failed to set senderId: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("recipientId"), &JsValue::from_str(&recipient_id)) - .map_err(|e| JsValue::from_str(&format!("Failed to set recipientId: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("amount"), &JsValue::from_f64(amount as f64)) - .map_err(|e| JsValue::from_str(&format!("Failed to set amount: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("message"), &JsValue::from_str("Credits transferred successfully")) - .map_err(|e| JsValue::from_str(&format!("Failed to set message: {:?}", e)))?; - + + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("status"), + &JsValue::from_str("success"), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set status: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("senderId"), + &JsValue::from_str(&sender_id), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set senderId: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("recipientId"), + &JsValue::from_str(&recipient_id), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set recipientId: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("amount"), + &JsValue::from_f64(amount as f64), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set amount: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("message"), + &JsValue::from_str("Credits transferred successfully"), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set message: {:?}", e)))?; + Ok(result_obj.into()) } - + /// Withdraw credits from an identity to a Dash address. /// /// # Arguments @@ -578,86 +737,103 @@ impl WasmSdk { key_id: Option, ) -> Result { let sdk = self.inner_clone(); - + // Parse identity identifier let identifier = Identifier::from_string(&identity_id, Encoding::Base58) .map_err(|e| JsValue::from_str(&format!("Invalid identity ID: {}", e)))?; - + // Parse the Dash address use dash_sdk::dpp::dashcore::Address; use std::str::FromStr; let address = Address::from_str(&to_address) .map_err(|e| JsValue::from_str(&format!("Invalid Dash address: {}", e)))? .assume_checked(); - + // Validate amount if amount == 0 { - return Err(JsValue::from_str("Withdrawal amount must be greater than 0")); + return Err(JsValue::from_str( + "Withdrawal amount must be greater than 0", + )); } - + // Fetch the identity let identity = dash_sdk::platform::Identity::fetch(&sdk, identifier) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Identity not found"))?; - + // Parse private key and find matching public key let private_key_bytes = dash_sdk::dpp::dashcore::PrivateKey::from_wif(&private_key_wif) .map_err(|e| JsValue::from_str(&format!("Invalid private key: {}", e)))? .inner .secret_bytes(); - + let secp = dash_sdk::dpp::dashcore::secp256k1::Secp256k1::new(); - let secret_key = dash_sdk::dpp::dashcore::secp256k1::SecretKey::from_slice(&private_key_bytes) - .map_err(|e| JsValue::from_str(&format!("Invalid secret key: {}", e)))?; - let public_key = dash_sdk::dpp::dashcore::secp256k1::PublicKey::from_secret_key(&secp, &secret_key); + let secret_key = + dash_sdk::dpp::dashcore::secp256k1::SecretKey::from_slice(&private_key_bytes) + .map_err(|e| JsValue::from_str(&format!("Invalid secret key: {}", e)))?; + let public_key = + dash_sdk::dpp::dashcore::secp256k1::PublicKey::from_secret_key(&secp, &secret_key); let public_key_bytes = public_key.serialize(); - + // Create public key hash using hash160 let public_key_hash160 = { - use dash_sdk::dpp::dashcore::hashes::{Hash, hash160}; - hash160::Hash::hash(&public_key_bytes[..]).to_byte_array().to_vec() + use dash_sdk::dpp::dashcore::hashes::{hash160, Hash}; + hash160::Hash::hash(&public_key_bytes[..]) + .to_byte_array() + .to_vec() }; - + // Find matching key - prioritize key_id if provided, otherwise find any matching key // For withdrawals, we can use either TRANSFER or OWNER keys let matching_key = if let Some(requested_key_id) = key_id { // Find specific key by ID - identity.public_keys() + identity + .public_keys() .get(&requested_key_id) .filter(|key| { - (key.purpose() == Purpose::TRANSFER || key.purpose() == Purpose::OWNER) && - key.key_type() == KeyType::ECDSA_HASH160 && - key.data().as_slice() == public_key_hash160.as_slice() + (key.purpose() == Purpose::TRANSFER || key.purpose() == Purpose::OWNER) + && key.key_type() == KeyType::ECDSA_HASH160 + && key.data().as_slice() == public_key_hash160.as_slice() }) - .ok_or_else(|| JsValue::from_str(&format!("Key with ID {} not found or doesn't match private key", requested_key_id)))? + .ok_or_else(|| { + JsValue::from_str(&format!( + "Key with ID {} not found or doesn't match private key", + requested_key_id + )) + })? } else { // Find any matching withdrawal-capable key (prefer TRANSFER keys) - identity.public_keys().iter() + identity + .public_keys() + .iter() .find(|(_, key)| { - key.purpose() == Purpose::TRANSFER && - key.key_type() == KeyType::ECDSA_HASH160 && - key.data().as_slice() == public_key_hash160.as_slice() + key.purpose() == Purpose::TRANSFER + && key.key_type() == KeyType::ECDSA_HASH160 + && key.data().as_slice() == public_key_hash160.as_slice() }) .or_else(|| { - identity.public_keys().iter() - .find(|(_, key)| { - key.purpose() == Purpose::OWNER && - key.key_type() == KeyType::ECDSA_HASH160 && - key.data().as_slice() == public_key_hash160.as_slice() - }) + identity.public_keys().iter().find(|(_, key)| { + key.purpose() == Purpose::OWNER + && key.key_type() == KeyType::ECDSA_HASH160 + && key.data().as_slice() == public_key_hash160.as_slice() + }) }) .map(|(_, key)| key) - .ok_or_else(|| JsValue::from_str("No matching withdrawal key found for the provided private key"))? + .ok_or_else(|| { + JsValue::from_str( + "No matching withdrawal key found for the provided private key", + ) + })? }; - + // Create signer let signer = SingleKeySigner::from_string(&private_key_wif, self.network()) .map_err(|e| JsValue::from_str(&e))?; - + // Import the withdraw trait use dash_sdk::platform::transition::withdraw_from_identity::WithdrawFromIdentity; - + // Perform the withdrawal let remaining_balance = identity .withdraw( @@ -671,26 +847,50 @@ impl WasmSdk { ) .await .map_err(|e| JsValue::from_str(&format!("Withdrawal failed: {}", e)))?; - + // Create JavaScript result object let result_obj = js_sys::Object::new(); - - js_sys::Reflect::set(&result_obj, &JsValue::from_str("status"), &JsValue::from_str("success")) - .map_err(|e| JsValue::from_str(&format!("Failed to set status: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("identityId"), &JsValue::from_str(&identity_id)) - .map_err(|e| JsValue::from_str(&format!("Failed to set identityId: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("toAddress"), &JsValue::from_str(&to_address)) - .map_err(|e| JsValue::from_str(&format!("Failed to set toAddress: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("amount"), &JsValue::from_f64(amount as f64)) - .map_err(|e| JsValue::from_str(&format!("Failed to set amount: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("remainingBalance"), &JsValue::from_f64(remaining_balance as f64)) - .map_err(|e| JsValue::from_str(&format!("Failed to set remainingBalance: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("message"), &JsValue::from_str("Credits withdrawn successfully")) - .map_err(|e| JsValue::from_str(&format!("Failed to set message: {:?}", e)))?; - + + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("status"), + &JsValue::from_str("success"), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set status: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("identityId"), + &JsValue::from_str(&identity_id), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set identityId: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("toAddress"), + &JsValue::from_str(&to_address), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set toAddress: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("amount"), + &JsValue::from_f64(amount as f64), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set amount: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("remainingBalance"), + &JsValue::from_f64(remaining_balance as f64), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set remainingBalance: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("message"), + &JsValue::from_str("Credits withdrawn successfully"), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set message: {:?}", e)))?; + Ok(result_obj.into()) } - + /// Update an identity by adding or disabling public keys. /// /// # Arguments @@ -712,71 +912,85 @@ impl WasmSdk { private_key_wif: String, ) -> Result { let sdk = self.inner_clone(); - + // Parse identity identifier let identifier = Identifier::from_string(&identity_id, Encoding::Base58) .map_err(|e| JsValue::from_str(&format!("Invalid identity ID: {}", e)))?; - + // Fetch the identity let identity = dash_sdk::platform::Identity::fetch(&sdk, identifier) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Identity not found"))?; - + // Get current revision let current_revision = identity.revision(); - + // Parse private key and verify it's a master key let private_key = PrivateKey::from_wif(&private_key_wif) .map_err(|e| JsValue::from_str(&format!("Invalid private key: {}", e)))?; - + // Create public key hash to find matching master key let secp = dash_sdk::dpp::dashcore::secp256k1::Secp256k1::new(); - let secret_key = dash_sdk::dpp::dashcore::secp256k1::SecretKey::from_slice(&private_key.inner.secret_bytes()) - .map_err(|e| JsValue::from_str(&format!("Invalid secret key: {}", e)))?; - let public_key = dash_sdk::dpp::dashcore::secp256k1::PublicKey::from_secret_key(&secp, &secret_key); + let secret_key = dash_sdk::dpp::dashcore::secp256k1::SecretKey::from_slice( + &private_key.inner.secret_bytes(), + ) + .map_err(|e| JsValue::from_str(&format!("Invalid secret key: {}", e)))?; + let public_key = + dash_sdk::dpp::dashcore::secp256k1::PublicKey::from_secret_key(&secp, &secret_key); let public_key_bytes = public_key.serialize(); - + // Create public key hash using hash160 let public_key_hash160 = { - use dash_sdk::dpp::dashcore::hashes::{Hash, hash160}; - hash160::Hash::hash(&public_key_bytes[..]).to_byte_array().to_vec() + use dash_sdk::dpp::dashcore::hashes::{hash160, Hash}; + hash160::Hash::hash(&public_key_bytes[..]) + .to_byte_array() + .to_vec() }; - + // Find matching master key - let master_key = identity.public_keys().iter() + let master_key = identity + .public_keys() + .iter() .find(|(_, key)| { - key.purpose() == Purpose::AUTHENTICATION && - key.security_level() == SecurityLevel::MASTER && - key.key_type() == KeyType::ECDSA_HASH160 && - key.data().as_slice() == public_key_hash160.as_slice() + key.purpose() == Purpose::AUTHENTICATION + && key.security_level() == SecurityLevel::MASTER + && key.key_type() == KeyType::ECDSA_HASH160 + && key.data().as_slice() == public_key_hash160.as_slice() }) .map(|(id, _)| *id) - .ok_or_else(|| JsValue::from_str("Provided private key does not match any master key"))?; - + .ok_or_else(|| { + JsValue::from_str("Provided private key does not match any master key") + })?; + // Parse and prepare keys to add let keys_to_add: Vec = if let Some(keys_json) = add_public_keys { // Parse JSON array of keys - let keys_data: serde_json::Value = serde_json::from_str(&keys_json) - .map_err(|e| JsValue::from_str(&format!("Invalid JSON for add_public_keys: {}", e)))?; - - let keys_array = keys_data.as_array() + let keys_data: serde_json::Value = serde_json::from_str(&keys_json).map_err(|e| { + JsValue::from_str(&format!("Invalid JSON for add_public_keys: {}", e)) + })?; + + let keys_array = keys_data + .as_array() .ok_or_else(|| JsValue::from_str("add_public_keys must be a JSON array"))?; - + // Get the current max key ID let mut next_key_id = identity.public_keys().keys().max().copied().unwrap_or(0) + 1; - - keys_array.iter() + + keys_array + .iter() .map(|key_data| { - let key_type_str = key_data["keyType"].as_str() + let key_type_str = key_data["keyType"] + .as_str() .ok_or_else(|| JsValue::from_str("keyType is required"))?; - let purpose_str = key_data["purpose"].as_str() + let purpose_str = key_data["purpose"] + .as_str() .ok_or_else(|| JsValue::from_str("purpose is required"))?; - let security_level_str = key_data["securityLevel"].as_str() - .unwrap_or("HIGH"); - let data_str = key_data["data"].as_str() + let security_level_str = key_data["securityLevel"].as_str().unwrap_or("HIGH"); + let data_str = key_data["data"] + .as_str() .ok_or_else(|| JsValue::from_str("data is required"))?; - + // Parse key type let key_type = match key_type_str { "ECDSA_SECP256K1" => KeyType::ECDSA_SECP256K1, @@ -784,9 +998,14 @@ impl WasmSdk { "ECDSA_HASH160" => KeyType::ECDSA_HASH160, "BIP13_SCRIPT_HASH" => KeyType::BIP13_SCRIPT_HASH, "EDDSA_25519_HASH160" => KeyType::EDDSA_25519_HASH160, - _ => return Err(JsValue::from_str(&format!("Unknown key type: {}", key_type_str))) + _ => { + return Err(JsValue::from_str(&format!( + "Unknown key type: {}", + key_type_str + ))) + } }; - + // Parse purpose let purpose = match purpose_str { "AUTHENTICATION" => Purpose::AUTHENTICATION, @@ -795,22 +1014,29 @@ impl WasmSdk { "TRANSFER" => Purpose::TRANSFER, "SYSTEM" => Purpose::SYSTEM, "VOTING" => Purpose::VOTING, - _ => return Err(JsValue::from_str(&format!("Unknown purpose: {}", purpose_str))) + _ => { + return Err(JsValue::from_str(&format!( + "Unknown purpose: {}", + purpose_str + ))) + } }; - + // Parse security level let security_level = match security_level_str { "MASTER" => SecurityLevel::MASTER, "CRITICAL" => SecurityLevel::CRITICAL, "HIGH" => SecurityLevel::HIGH, "MEDIUM" => SecurityLevel::MEDIUM, - _ => SecurityLevel::HIGH + _ => SecurityLevel::HIGH, }; - + // Decode key data from base64 - let key_data = dash_sdk::dpp::dashcore::base64::decode(data_str) - .map_err(|e| JsValue::from_str(&format!("Invalid base64 key data: {}", e)))?; - + let key_data = + dash_sdk::dpp::dashcore::base64::decode(data_str).map_err(|e| { + JsValue::from_str(&format!("Invalid base64 key data: {}", e)) + })?; + // Create the identity public key use dash_sdk::dpp::identity::identity_public_key::v0::IdentityPublicKeyV0; let public_key = IdentityPublicKey::V0(IdentityPublicKeyV0 { @@ -823,7 +1049,7 @@ impl WasmSdk { data: BinaryData::new(key_data), disabled_at: None, }); - + next_key_id += 1; Ok(public_key) }) @@ -831,47 +1057,57 @@ impl WasmSdk { } else { Vec::new() }; - + // Get keys to disable let keys_to_disable = disable_public_keys.unwrap_or_default(); - + // Save counts before moving let added_keys_count = keys_to_add.len(); let disabled_keys_count = keys_to_disable.len(); - + // Validate keys to disable (cannot disable master, critical auth, or transfer keys) for key_id in &keys_to_disable { if let Some(key) = identity.public_keys().get(key_id) { if key.security_level() == SecurityLevel::MASTER { - return Err(JsValue::from_str(&format!("Cannot disable master key {}", key_id))); + return Err(JsValue::from_str(&format!( + "Cannot disable master key {}", + key_id + ))); } - if key.purpose() == Purpose::AUTHENTICATION && - key.security_level() == SecurityLevel::CRITICAL && - key.key_type() == KeyType::ECDSA_SECP256K1 { - return Err(JsValue::from_str(&format!("Cannot disable critical authentication key {}", key_id))); + if key.purpose() == Purpose::AUTHENTICATION + && key.security_level() == SecurityLevel::CRITICAL + && key.key_type() == KeyType::ECDSA_SECP256K1 + { + return Err(JsValue::from_str(&format!( + "Cannot disable critical authentication key {}", + key_id + ))); } if key.purpose() == Purpose::TRANSFER { - return Err(JsValue::from_str(&format!("Cannot disable transfer key {}", key_id))); + return Err(JsValue::from_str(&format!( + "Cannot disable transfer key {}", + key_id + ))); } } else { return Err(JsValue::from_str(&format!("Key {} not found", key_id))); } } - + // Get identity nonce let identity_nonce = sdk .get_identity_nonce(identifier, true, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to get identity nonce: {}", e)))?; - + // Create signer let signer = SingleKeySigner::from_string(&private_key_wif, self.network()) .map_err(|e| JsValue::from_str(&e))?; - + // Create the identity update transition use dash_sdk::dpp::state_transition::identity_update_transition::methods::IdentityUpdateTransitionMethodsV0; use dash_sdk::dpp::state_transition::identity_update_transition::IdentityUpdateTransition; - + let state_transition = IdentityUpdateTransition::try_from_identity_with_signer( &identity, &master_key, @@ -884,14 +1120,14 @@ impl WasmSdk { None, // No version override ) .map_err(|e| JsValue::from_str(&format!("Failed to create update transition: {}", e)))?; - + // Broadcast the transition use dash_sdk::dpp::state_transition::proof_result::StateTransitionProofResult; let result = state_transition .broadcast_and_wait::(&sdk, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to broadcast update: {}", e)))?; - + // Extract updated identity from result let updated_revision = match result { StateTransitionProofResult::VerifiedIdentity(updated_identity) => { @@ -902,26 +1138,50 @@ impl WasmSdk { } _ => current_revision + 1, }; - + // Create JavaScript result object let result_obj = js_sys::Object::new(); - - js_sys::Reflect::set(&result_obj, &JsValue::from_str("status"), &JsValue::from_str("success")) - .map_err(|e| JsValue::from_str(&format!("Failed to set status: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("identityId"), &JsValue::from_str(&identity_id)) - .map_err(|e| JsValue::from_str(&format!("Failed to set identityId: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("revision"), &JsValue::from_f64(updated_revision as f64)) - .map_err(|e| JsValue::from_str(&format!("Failed to set revision: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("addedKeys"), &JsValue::from_f64(added_keys_count as f64)) - .map_err(|e| JsValue::from_str(&format!("Failed to set addedKeys: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("disabledKeys"), &JsValue::from_f64(disabled_keys_count as f64)) - .map_err(|e| JsValue::from_str(&format!("Failed to set disabledKeys: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("message"), &JsValue::from_str("Identity updated successfully")) - .map_err(|e| JsValue::from_str(&format!("Failed to set message: {:?}", e)))?; - + + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("status"), + &JsValue::from_str("success"), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set status: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("identityId"), + &JsValue::from_str(&identity_id), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set identityId: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("revision"), + &JsValue::from_f64(updated_revision as f64), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set revision: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("addedKeys"), + &JsValue::from_f64(added_keys_count as f64), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set addedKeys: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("disabledKeys"), + &JsValue::from_f64(disabled_keys_count as f64), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set disabledKeys: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("message"), + &JsValue::from_str("Identity updated successfully"), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set message: {:?}", e)))?; + Ok(result_obj.into()) } - + /// Submit a masternode vote for a contested resource. /// /// # Arguments @@ -949,9 +1209,13 @@ impl WasmSdk { voting_key_wif: String, ) -> Result { let sdk = self.inner_clone(); - + // Parse ProTxHash (try hex first, then base58) - let pro_tx_hash = if masternode_pro_tx_hash.len() == 64 && masternode_pro_tx_hash.chars().all(|c| c.is_ascii_hexdigit()) { + let pro_tx_hash = if masternode_pro_tx_hash.len() == 64 + && masternode_pro_tx_hash + .chars() + .all(|c| c.is_ascii_hexdigit()) + { // Looks like hex Identifier::from_string(&masternode_pro_tx_hash, Encoding::Hex) .map_err(|e| JsValue::from_str(&format!("Invalid ProTxHash (hex): {}", e)))? @@ -960,37 +1224,41 @@ impl WasmSdk { Identifier::from_string(&masternode_pro_tx_hash, Encoding::Base58) .map_err(|e| JsValue::from_str(&format!("Invalid ProTxHash (base58): {}", e)))? }; - + // Parse contract ID let data_contract_id = Identifier::from_string(&contract_id, Encoding::Base58) .map_err(|e| JsValue::from_str(&format!("Invalid contract ID: {}", e)))?; - + // Parse index values from JSON let index_values_json: serde_json::Value = serde_json::from_str(&index_values) .map_err(|e| JsValue::from_str(&format!("Invalid index values JSON: {}", e)))?; - - let index_values_array = index_values_json.as_array() + + let index_values_array = index_values_json + .as_array() .ok_or_else(|| JsValue::from_str("index_values must be a JSON array"))?; - - let index_values_vec: Vec = index_values_array.iter() - .map(|v| { - match v { - serde_json::Value::String(s) => Ok(dash_sdk::dpp::platform_value::Value::Text(s.clone())), - serde_json::Value::Number(n) => { - if let Some(i) = n.as_i64() { - Ok(dash_sdk::dpp::platform_value::Value::I64(i)) - } else if let Some(u) = n.as_u64() { - Ok(dash_sdk::dpp::platform_value::Value::U64(u)) - } else { - Ok(dash_sdk::dpp::platform_value::Value::Float(n.as_f64().unwrap())) - } + + let index_values_vec: Vec = index_values_array + .iter() + .map(|v| match v { + serde_json::Value::String(s) => { + Ok(dash_sdk::dpp::platform_value::Value::Text(s.clone())) + } + serde_json::Value::Number(n) => { + if let Some(i) = n.as_i64() { + Ok(dash_sdk::dpp::platform_value::Value::I64(i)) + } else if let Some(u) = n.as_u64() { + Ok(dash_sdk::dpp::platform_value::Value::U64(u)) + } else { + Ok(dash_sdk::dpp::platform_value::Value::Float( + n.as_f64().unwrap(), + )) } - serde_json::Value::Bool(b) => Ok(dash_sdk::dpp::platform_value::Value::Bool(*b)), - _ => Err(JsValue::from_str("Unsupported index value type")) } + serde_json::Value::Bool(b) => Ok(dash_sdk::dpp::platform_value::Value::Bool(*b)), + _ => Err(JsValue::from_str("Unsupported index value type")), }) .collect::, _>>()?; - + // Parse vote choice use dash_sdk::dpp::voting::vote_choices::resource_vote_choice::ResourceVoteChoice; let resource_vote_choice = if vote_choice == "abstain" { @@ -998,17 +1266,22 @@ impl WasmSdk { } else if vote_choice == "lock" { ResourceVoteChoice::Lock } else if vote_choice.starts_with("towardsIdentity:") { - let identity_id_str = vote_choice.strip_prefix("towardsIdentity:") + let identity_id_str = vote_choice + .strip_prefix("towardsIdentity:") .ok_or_else(|| JsValue::from_str("Invalid vote choice format"))?; - let identity_id = Identifier::from_string(identity_id_str, Encoding::Base58) - .map_err(|e| JsValue::from_str(&format!("Invalid identity ID in vote choice: {}", e)))?; + let identity_id = + Identifier::from_string(identity_id_str, Encoding::Base58).map_err(|e| { + JsValue::from_str(&format!("Invalid identity ID in vote choice: {}", e)) + })?; ResourceVoteChoice::TowardsIdentity(identity_id) } else { return Err(JsValue::from_str("Invalid vote choice. Must be 'abstain', 'lock', or 'towardsIdentity:'")); }; - + // Parse private key (try WIF first, then hex) - let private_key = if voting_key_wif.len() == 64 && voting_key_wif.chars().all(|c| c.is_ascii_hexdigit()) { + let private_key = if voting_key_wif.len() == 64 + && voting_key_wif.chars().all(|c| c.is_ascii_hexdigit()) + { // Looks like hex let key_bytes = hex::decode(&voting_key_wif) .map_err(|e| JsValue::from_str(&format!("Invalid hex private key: {}", e)))?; @@ -1022,20 +1295,25 @@ impl WasmSdk { PrivateKey::from_wif(&voting_key_wif) .map_err(|e| JsValue::from_str(&format!("Invalid WIF private key: {}", e)))? }; - + // Create the voting public key from the private key let secp = dash_sdk::dpp::dashcore::secp256k1::Secp256k1::new(); - let secret_key = dash_sdk::dpp::dashcore::secp256k1::SecretKey::from_slice(&private_key.inner.secret_bytes()) - .map_err(|e| JsValue::from_str(&format!("Invalid secret key: {}", e)))?; - let public_key = dash_sdk::dpp::dashcore::secp256k1::PublicKey::from_secret_key(&secp, &secret_key); + let secret_key = dash_sdk::dpp::dashcore::secp256k1::SecretKey::from_slice( + &private_key.inner.secret_bytes(), + ) + .map_err(|e| JsValue::from_str(&format!("Invalid secret key: {}", e)))?; + let public_key = + dash_sdk::dpp::dashcore::secp256k1::PublicKey::from_secret_key(&secp, &secret_key); let public_key_bytes = public_key.serialize(); - + // Create voting public key hash using hash160 let voting_key_hash = { - use dash_sdk::dpp::dashcore::hashes::{Hash, hash160}; - hash160::Hash::hash(&public_key_bytes[..]).to_byte_array().to_vec() + use dash_sdk::dpp::dashcore::hashes::{hash160, Hash}; + hash160::Hash::hash(&public_key_bytes[..]) + .to_byte_array() + .to_vec() }; - + // Create the voting identity public key use dash_sdk::dpp::identity::identity_public_key::v0::IdentityPublicKeyV0; let voting_public_key = IdentityPublicKey::V0(IdentityPublicKeyV0 { @@ -1048,18 +1326,19 @@ impl WasmSdk { data: BinaryData::new(voting_key_hash), disabled_at: None, }); - + // Create the contested document resource vote poll use dash_sdk::dpp::voting::vote_polls::contested_document_resource_vote_poll::ContestedDocumentResourceVotePoll; - let vote_poll = dash_sdk::dpp::voting::vote_polls::VotePoll::ContestedDocumentResourceVotePoll( - ContestedDocumentResourceVotePoll { - contract_id: data_contract_id, - document_type_name: document_type_name.clone(), - index_name: index_name.clone(), - index_values: index_values_vec, - } - ); - + let vote_poll = + dash_sdk::dpp::voting::vote_polls::VotePoll::ContestedDocumentResourceVotePoll( + ContestedDocumentResourceVotePoll { + contract_id: data_contract_id, + document_type_name: document_type_name.clone(), + index_name: index_name.clone(), + index_values: index_values_vec, + }, + ); + // Create the resource vote use dash_sdk::dpp::voting::votes::resource_vote::v0::ResourceVoteV0; use dash_sdk::dpp::voting::votes::resource_vote::ResourceVote; @@ -1067,44 +1346,62 @@ impl WasmSdk { vote_poll, resource_vote_choice, }); - + // Create the vote use dash_sdk::dpp::voting::votes::Vote; let vote = Vote::ResourceVote(resource_vote); - + // Create signer let signer = SingleKeySigner::from_string(&voting_key_wif, self.network()) .map_err(|e| JsValue::from_str(&e))?; - + // Submit the vote using PutVote trait use dash_sdk::platform::transition::vote::PutVote; - - vote.put_to_platform( - pro_tx_hash, - &voting_public_key, - &sdk, - &signer, - None, - ) - .await - .map_err(|e| JsValue::from_str(&format!("Failed to submit vote: {}", e)))?; - + + vote.put_to_platform(pro_tx_hash, &voting_public_key, &sdk, &signer, None) + .await + .map_err(|e| JsValue::from_str(&format!("Failed to submit vote: {}", e)))?; + // Create JavaScript result object let result_obj = js_sys::Object::new(); - - js_sys::Reflect::set(&result_obj, &JsValue::from_str("status"), &JsValue::from_str("success")) - .map_err(|e| JsValue::from_str(&format!("Failed to set status: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("proTxHash"), &JsValue::from_str(&masternode_pro_tx_hash)) - .map_err(|e| JsValue::from_str(&format!("Failed to set proTxHash: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("contractId"), &JsValue::from_str(&contract_id)) - .map_err(|e| JsValue::from_str(&format!("Failed to set contractId: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("documentType"), &JsValue::from_str(&document_type_name)) - .map_err(|e| JsValue::from_str(&format!("Failed to set documentType: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("voteChoice"), &JsValue::from_str(&vote_choice)) - .map_err(|e| JsValue::from_str(&format!("Failed to set voteChoice: {:?}", e)))?; - js_sys::Reflect::set(&result_obj, &JsValue::from_str("message"), &JsValue::from_str("Vote submitted successfully")) - .map_err(|e| JsValue::from_str(&format!("Failed to set message: {:?}", e)))?; - + + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("status"), + &JsValue::from_str("success"), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set status: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("proTxHash"), + &JsValue::from_str(&masternode_pro_tx_hash), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set proTxHash: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("contractId"), + &JsValue::from_str(&contract_id), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set contractId: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("documentType"), + &JsValue::from_str(&document_type_name), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set documentType: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("voteChoice"), + &JsValue::from_str(&vote_choice), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set voteChoice: {:?}", e)))?; + js_sys::Reflect::set( + &result_obj, + &JsValue::from_str("message"), + &JsValue::from_str("Vote submitted successfully"), + ) + .map_err(|e| JsValue::from_str(&format!("Failed to set message: {:?}", e)))?; + Ok(result_obj.into()) } -} \ No newline at end of file +} diff --git a/packages/wasm-sdk/src/state_transitions/tokens/mod.rs b/packages/wasm-sdk/src/state_transitions/tokens/mod.rs index 2fcc69735d..951d7982fe 100644 --- a/packages/wasm-sdk/src/state_transitions/tokens/mod.rs +++ b/packages/wasm-sdk/src/state_transitions/tokens/mod.rs @@ -4,17 +4,17 @@ use crate::sdk::WasmSdk; use dash_sdk::dpp::balances::credits::TokenAmount; -use dash_sdk::dpp::platform_value::{Identifier, string_encoding::Encoding}; +use dash_sdk::dpp::document::DocumentV0Getters; +use dash_sdk::dpp::platform_value::{string_encoding::Encoding, Identifier}; use dash_sdk::dpp::prelude::UserFeeIncrease; -use dash_sdk::dpp::state_transition::batch_transition::BatchTransition; use dash_sdk::dpp::state_transition::batch_transition::methods::v1::DocumentsBatchTransitionMethodsV1; +use dash_sdk::dpp::state_transition::batch_transition::BatchTransition; use dash_sdk::dpp::state_transition::proof_result::StateTransitionProofResult; use dash_sdk::dpp::tokens::calculate_token_id; -use dash_sdk::dpp::document::DocumentV0Getters; use dash_sdk::platform::transition::broadcast::BroadcastStateTransition; use dash_sdk::platform::Fetch; -use serde_wasm_bindgen::to_value; use serde_json; +use serde_wasm_bindgen::to_value; use wasm_bindgen::prelude::*; use wasm_bindgen::JsValue; @@ -33,37 +33,40 @@ impl WasmSdk { // Parse identifiers let contract_id = Identifier::from_string(data_contract_id, Encoding::Base58) .map_err(|e| JsValue::from_str(&format!("Invalid contract ID: {}", e)))?; - + let identity_identifier = Identifier::from_string(identity_id, Encoding::Base58) .map_err(|e| JsValue::from_str(&format!("Invalid identity ID: {}", e)))?; - + let recipient = if let Some(recipient_str) = recipient_id { - Some(Identifier::from_string(&recipient_str, Encoding::Base58) - .map_err(|e| JsValue::from_str(&format!("Invalid recipient ID: {}", e)))?) + Some( + Identifier::from_string(&recipient_str, Encoding::Base58) + .map_err(|e| JsValue::from_str(&format!("Invalid recipient ID: {}", e)))?, + ) } else { None }; - + // Parse amount - let token_amount = amount.parse::() + let token_amount = amount + .parse::() .map_err(|e| JsValue::from_str(&format!("Invalid amount: {}", e)))?; - + Ok((contract_id, identity_identifier, token_amount, recipient)) } - + /// Fetch and cache data contract in trusted context async fn fetch_and_cache_token_contract( &self, contract_id: Identifier, ) -> Result { let sdk = self.inner_clone(); - + // Fetch the data contract let data_contract = dash_sdk::platform::DataContract::fetch(&sdk, contract_id) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch data contract: {}", e)))? .ok_or_else(|| JsValue::from_str("Data contract not found"))?; - + // Add the contract to the context provider's cache if using trusted mode match sdk.network { dash_sdk::dpp::dashcore::Network::Testnet => { @@ -78,11 +81,10 @@ impl WasmSdk { } _ => {} // Other networks don't use trusted context } - + Ok(data_contract) } - - + /// Convert state transition proof result to JsValue fn format_token_result( &self, @@ -94,31 +96,39 @@ impl WasmSdk { "type": "VerifiedTokenBalance", "recipientId": recipient_id.to_string(Encoding::Base58), "newBalance": new_balance.to_string() - })).map_err(|e| JsValue::from_str(&format!("Failed to serialize result: {}", e))) + })) + .map_err(|e| JsValue::from_str(&format!("Failed to serialize result: {}", e))) } StateTransitionProofResult::VerifiedTokenActionWithDocument(doc) => { to_value(&serde_json::json!({ "type": "VerifiedTokenActionWithDocument", "documentId": doc.id().to_string(Encoding::Base58), "message": "Token operation recorded successfully" - })).map_err(|e| JsValue::from_str(&format!("Failed to serialize result: {}", e))) + })) + .map_err(|e| JsValue::from_str(&format!("Failed to serialize result: {}", e))) } StateTransitionProofResult::VerifiedTokenGroupActionWithDocument(power, doc) => { to_value(&serde_json::json!({ "type": "VerifiedTokenGroupActionWithDocument", "groupPower": power, "document": doc.is_some() - })).map_err(|e| JsValue::from_str(&format!("Failed to serialize result: {}", e))) + })) + .map_err(|e| JsValue::from_str(&format!("Failed to serialize result: {}", e))) } - StateTransitionProofResult::VerifiedTokenGroupActionWithTokenBalance(power, status, balance) => { - to_value(&serde_json::json!({ - "type": "VerifiedTokenGroupActionWithTokenBalance", - "groupPower": power, - "status": format!("{:?}", status), - "balance": balance.map(|b| b.to_string()) - })).map_err(|e| JsValue::from_str(&format!("Failed to serialize result: {}", e))) - } - _ => Err(JsValue::from_str("Unexpected result type for token transition")) + StateTransitionProofResult::VerifiedTokenGroupActionWithTokenBalance( + power, + status, + balance, + ) => to_value(&serde_json::json!({ + "type": "VerifiedTokenGroupActionWithTokenBalance", + "groupPower": power, + "status": format!("{:?}", status), + "balance": balance.map(|b| b.to_string()) + })) + .map_err(|e| JsValue::from_str(&format!("Failed to serialize result: {}", e))), + _ => Err(JsValue::from_str( + "Unexpected result type for token transition", + )), } } } @@ -152,41 +162,36 @@ impl WasmSdk { public_note: Option, ) -> Result { let sdk = self.inner_clone(); - + // Parse and validate parameters - let (contract_id, issuer_id, token_amount, recipient) = self.parse_token_params( - &data_contract_id, - &identity_id, - &amount, - recipient_id, - ).await?; - + let (contract_id, issuer_id, token_amount, recipient) = self + .parse_token_params(&data_contract_id, &identity_id, &amount, recipient_id) + .await?; + // Fetch and cache the data contract let _data_contract = self.fetch_and_cache_token_contract(contract_id).await?; - + // Get identity to find matching authentication key let identity = dash_sdk::platform::Identity::fetch(&sdk, issuer_id) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Identity not found"))?; - + // Get identity contract nonce let identity_contract_nonce = sdk .get_identity_contract_nonce(issuer_id, contract_id, true, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch nonce: {}", e)))?; - + // Find matching authentication key and create signer - let (_, matching_key) = crate::sdk::WasmSdk::find_authentication_key(&identity, &private_key_wif)?; + let (_, matching_key) = + crate::sdk::WasmSdk::find_authentication_key(&identity, &private_key_wif)?; let signer = crate::sdk::WasmSdk::create_signer_from_wif(&private_key_wif, sdk.network)?; let public_key = matching_key.clone(); - + // Calculate token ID - let token_id = Identifier::from(calculate_token_id( - contract_id.as_bytes(), - token_position, - )); - + let token_id = Identifier::from(calculate_token_id(contract_id.as_bytes(), token_position)); + // Create the state transition let platform_version = sdk.version(); let state_transition = BatchTransition::new_token_mint_transition( @@ -204,14 +209,15 @@ impl WasmSdk { &signer, platform_version, None, // state_transition_creation_options - ).map_err(|e| JsValue::from_str(&format!("Failed to create mint transition: {}", e)))?; - + ) + .map_err(|e| JsValue::from_str(&format!("Failed to create mint transition: {}", e)))?; + // Broadcast the transition let proof_result = state_transition .broadcast_and_wait::(&sdk, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to broadcast transition: {}", e)))?; - + // Format and return result self.format_token_result(proof_result) } @@ -241,41 +247,36 @@ impl WasmSdk { public_note: Option, ) -> Result { let sdk = self.inner_clone(); - + // Parse and validate parameters (no recipient for burn) - let (contract_id, burner_id, token_amount, _) = self.parse_token_params( - &data_contract_id, - &identity_id, - &amount, - None, - ).await?; - + let (contract_id, burner_id, token_amount, _) = self + .parse_token_params(&data_contract_id, &identity_id, &amount, None) + .await?; + // Fetch and cache the data contract let _data_contract = self.fetch_and_cache_token_contract(contract_id).await?; - + // Get identity to find matching authentication key let identity = dash_sdk::platform::Identity::fetch(&sdk, burner_id) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Identity not found"))?; - + // Get identity contract nonce let identity_contract_nonce = sdk .get_identity_contract_nonce(burner_id, contract_id, true, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch nonce: {}", e)))?; - + // Find matching authentication key and create signer - let (_, matching_key) = crate::sdk::WasmSdk::find_authentication_key(&identity, &private_key_wif)?; + let (_, matching_key) = + crate::sdk::WasmSdk::find_authentication_key(&identity, &private_key_wif)?; let signer = crate::sdk::WasmSdk::create_signer_from_wif(&private_key_wif, sdk.network)?; let public_key = matching_key.clone(); - + // Calculate token ID - let token_id = Identifier::from(calculate_token_id( - contract_id.as_bytes(), - token_position, - )); - + let token_id = Identifier::from(calculate_token_id(contract_id.as_bytes(), token_position)); + // Create the state transition let platform_version = sdk.version(); let state_transition = BatchTransition::new_token_burn_transition( @@ -292,14 +293,15 @@ impl WasmSdk { &signer, platform_version, None, // state_transition_creation_options - ).map_err(|e| JsValue::from_str(&format!("Failed to create burn transition: {}", e)))?; - + ) + .map_err(|e| JsValue::from_str(&format!("Failed to create burn transition: {}", e)))?; + // Broadcast the transition let proof_result = state_transition .broadcast_and_wait::(&sdk, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to broadcast transition: {}", e)))?; - + // Format and return result self.format_token_result(proof_result) } @@ -331,45 +333,40 @@ impl WasmSdk { public_note: Option, ) -> Result { let sdk = self.inner_clone(); - + // Parse and validate parameters - let (contract_id, sender_identifier, token_amount, _) = self.parse_token_params( - &data_contract_id, - &sender_id, - &amount, - None, - ).await?; - + let (contract_id, sender_identifier, token_amount, _) = self + .parse_token_params(&data_contract_id, &sender_id, &amount, None) + .await?; + // Parse recipient ID let recipient_identifier = Identifier::from_string(&recipient_id, Encoding::Base58) .map_err(|e| JsValue::from_str(&format!("Invalid recipient ID: {}", e)))?; - + // Fetch and cache the data contract let _data_contract = self.fetch_and_cache_token_contract(contract_id).await?; - + // Get identity to find matching authentication key let identity = dash_sdk::platform::Identity::fetch(&sdk, sender_identifier) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Identity not found"))?; - + // Get identity contract nonce let identity_contract_nonce = sdk .get_identity_contract_nonce(sender_identifier, contract_id, true, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch nonce: {}", e)))?; - + // Find matching authentication key and create signer - let (_, matching_key) = crate::sdk::WasmSdk::find_authentication_key(&identity, &private_key_wif)?; + let (_, matching_key) = + crate::sdk::WasmSdk::find_authentication_key(&identity, &private_key_wif)?; let signer = crate::sdk::WasmSdk::create_signer_from_wif(&private_key_wif, sdk.network)?; let public_key = matching_key.clone(); - + // Calculate token ID - let token_id = Identifier::from(calculate_token_id( - contract_id.as_bytes(), - token_position, - )); - + let token_id = Identifier::from(calculate_token_id(contract_id.as_bytes(), token_position)); + // Create the state transition let platform_version = sdk.version(); let state_transition = BatchTransition::new_token_transfer_transition( @@ -388,14 +385,15 @@ impl WasmSdk { &signer, platform_version, None, // state_transition_creation_options - ).map_err(|e| JsValue::from_str(&format!("Failed to create transfer transition: {}", e)))?; - + ) + .map_err(|e| JsValue::from_str(&format!("Failed to create transfer transition: {}", e)))?; + // Broadcast the transition let proof_result = state_transition .broadcast_and_wait::(&sdk, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to broadcast transition: {}", e)))?; - + // Format and return result self.format_token_result(proof_result) } @@ -425,45 +423,45 @@ impl WasmSdk { public_note: Option, ) -> Result { let sdk = self.inner_clone(); - + // Parse and validate parameters - let (contract_id, freezer_identifier, _, _) = self.parse_token_params( - &data_contract_id, - &freezer_id, - "0", // Amount not needed for freeze - None, - ).await?; - + let (contract_id, freezer_identifier, _, _) = self + .parse_token_params( + &data_contract_id, + &freezer_id, + "0", // Amount not needed for freeze + None, + ) + .await?; + // Parse identity to freeze let frozen_identity_id = Identifier::from_string(&identity_to_freeze, Encoding::Base58) .map_err(|e| JsValue::from_str(&format!("Invalid identity to freeze: {}", e)))?; - + // Fetch and cache the data contract let _data_contract = self.fetch_and_cache_token_contract(contract_id).await?; - + // Get identity to find matching authentication key let identity = dash_sdk::platform::Identity::fetch(&sdk, freezer_identifier) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Identity not found"))?; - + // Get identity contract nonce let identity_contract_nonce = sdk .get_identity_contract_nonce(freezer_identifier, contract_id, true, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch nonce: {}", e)))?; - + // Find matching authentication key and create signer - let (_, matching_key) = crate::sdk::WasmSdk::find_authentication_key(&identity, &private_key_wif)?; + let (_, matching_key) = + crate::sdk::WasmSdk::find_authentication_key(&identity, &private_key_wif)?; let signer = crate::sdk::WasmSdk::create_signer_from_wif(&private_key_wif, sdk.network)?; let public_key = matching_key.clone(); - + // Calculate token ID - let token_id = Identifier::from(calculate_token_id( - contract_id.as_bytes(), - token_position, - )); - + let token_id = Identifier::from(calculate_token_id(contract_id.as_bytes(), token_position)); + // Create the state transition let platform_version = sdk.version(); let state_transition = BatchTransition::new_token_freeze_transition( @@ -480,14 +478,15 @@ impl WasmSdk { &signer, platform_version, None, // state_transition_creation_options - ).map_err(|e| JsValue::from_str(&format!("Failed to create freeze transition: {}", e)))?; - + ) + .map_err(|e| JsValue::from_str(&format!("Failed to create freeze transition: {}", e)))?; + // Broadcast the transition let proof_result = state_transition .broadcast_and_wait::(&sdk, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to broadcast transition: {}", e)))?; - + // Format and return result self.format_token_result(proof_result) } @@ -517,45 +516,46 @@ impl WasmSdk { public_note: Option, ) -> Result { let sdk = self.inner_clone(); - + // Parse and validate parameters - let (contract_id, unfreezer_identifier, _, _) = self.parse_token_params( - &data_contract_id, - &unfreezer_id, - "0", // Amount not needed for unfreeze - None, - ).await?; - + let (contract_id, unfreezer_identifier, _, _) = self + .parse_token_params( + &data_contract_id, + &unfreezer_id, + "0", // Amount not needed for unfreeze + None, + ) + .await?; + // Parse identity to unfreeze - let frozen_identity_id = Identifier::from_string(&identity_to_unfreeze, Encoding::Base58) - .map_err(|e| JsValue::from_str(&format!("Invalid identity to unfreeze: {}", e)))?; - + let frozen_identity_id = + Identifier::from_string(&identity_to_unfreeze, Encoding::Base58) + .map_err(|e| JsValue::from_str(&format!("Invalid identity to unfreeze: {}", e)))?; + // Fetch and cache the data contract let _data_contract = self.fetch_and_cache_token_contract(contract_id).await?; - + // Get identity to find matching authentication key let identity = dash_sdk::platform::Identity::fetch(&sdk, unfreezer_identifier) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Identity not found"))?; - + // Get identity contract nonce let identity_contract_nonce = sdk .get_identity_contract_nonce(unfreezer_identifier, contract_id, true, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch nonce: {}", e)))?; - + // Find matching authentication key and create signer - let (_, matching_key) = crate::sdk::WasmSdk::find_authentication_key(&identity, &private_key_wif)?; + let (_, matching_key) = + crate::sdk::WasmSdk::find_authentication_key(&identity, &private_key_wif)?; let signer = crate::sdk::WasmSdk::create_signer_from_wif(&private_key_wif, sdk.network)?; let public_key = matching_key.clone(); - + // Calculate token ID - let token_id = Identifier::from(calculate_token_id( - contract_id.as_bytes(), - token_position, - )); - + let token_id = Identifier::from(calculate_token_id(contract_id.as_bytes(), token_position)); + // Create the state transition let platform_version = sdk.version(); let state_transition = BatchTransition::new_token_unfreeze_transition( @@ -572,14 +572,15 @@ impl WasmSdk { &signer, platform_version, None, // state_transition_creation_options - ).map_err(|e| JsValue::from_str(&format!("Failed to create unfreeze transition: {}", e)))?; - + ) + .map_err(|e| JsValue::from_str(&format!("Failed to create unfreeze transition: {}", e)))?; + // Broadcast the transition let proof_result = state_transition .broadcast_and_wait::(&sdk, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to broadcast transition: {}", e)))?; - + // Format and return result self.format_token_result(proof_result) } @@ -609,45 +610,47 @@ impl WasmSdk { public_note: Option, ) -> Result { let sdk = self.inner_clone(); - + // Parse and validate parameters - let (contract_id, destroyer_identifier, _, _) = self.parse_token_params( - &data_contract_id, - &destroyer_id, - "0", // Amount not needed for destroy frozen - None, - ).await?; - + let (contract_id, destroyer_identifier, _, _) = self + .parse_token_params( + &data_contract_id, + &destroyer_id, + "0", // Amount not needed for destroy frozen + None, + ) + .await?; + // Parse identity whose frozen tokens to destroy - let frozen_identity_id = Identifier::from_string(&identity_id, Encoding::Base58) - .map_err(|e| JsValue::from_str(&format!("Invalid identity to destroy frozen funds: {}", e)))?; - + let frozen_identity_id = + Identifier::from_string(&identity_id, Encoding::Base58).map_err(|e| { + JsValue::from_str(&format!("Invalid identity to destroy frozen funds: {}", e)) + })?; + // Fetch and cache the data contract let _data_contract = self.fetch_and_cache_token_contract(contract_id).await?; - + // Get identity to find matching authentication key let identity = dash_sdk::platform::Identity::fetch(&sdk, destroyer_identifier) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Identity not found"))?; - + // Get identity contract nonce let identity_contract_nonce = sdk .get_identity_contract_nonce(destroyer_identifier, contract_id, true, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch nonce: {}", e)))?; - + // Find matching authentication key and create signer - let (_, matching_key) = crate::sdk::WasmSdk::find_authentication_key(&identity, &private_key_wif)?; + let (_, matching_key) = + crate::sdk::WasmSdk::find_authentication_key(&identity, &private_key_wif)?; let signer = crate::sdk::WasmSdk::create_signer_from_wif(&private_key_wif, sdk.network)?; let public_key = matching_key.clone(); - + // Calculate token ID - let token_id = Identifier::from(calculate_token_id( - contract_id.as_bytes(), - token_position, - )); - + let token_id = Identifier::from(calculate_token_id(contract_id.as_bytes(), token_position)); + // Create the state transition let platform_version = sdk.version(); let state_transition = BatchTransition::new_token_destroy_frozen_funds_transition( @@ -664,18 +667,24 @@ impl WasmSdk { &signer, platform_version, None, // state_transition_creation_options - ).map_err(|e| JsValue::from_str(&format!("Failed to create destroy frozen transition: {}", e)))?; - + ) + .map_err(|e| { + JsValue::from_str(&format!( + "Failed to create destroy frozen transition: {}", + e + )) + })?; + // Broadcast the transition let proof_result = state_transition .broadcast_and_wait::(&sdk, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to broadcast transition: {}", e)))?; - + // Format and return result self.format_token_result(proof_result) } - + /// Set or update the price for direct token purchases. /// /// # Arguments @@ -703,23 +712,25 @@ impl WasmSdk { private_key_wif: String, public_note: Option, ) -> Result { - use dash_sdk::dpp::tokens::token_pricing_schedule::TokenPricingSchedule; use dash_sdk::dpp::fee::Credits; + use dash_sdk::dpp::tokens::token_pricing_schedule::TokenPricingSchedule; use std::collections::BTreeMap; - + let sdk = self.inner_clone(); - + // Parse identifiers - let (contract_id, actor_id, _, _) = self.parse_token_params( - &data_contract_id, - &identity_id, - "0", // Amount not needed for setting price - None, - ).await?; - + let (contract_id, actor_id, _, _) = self + .parse_token_params( + &data_contract_id, + &identity_id, + "0", // Amount not needed for setting price + None, + ) + .await?; + // Fetch and cache the contract self.fetch_and_cache_token_contract(contract_id).await?; - + // Parse pricing schedule let pricing_schedule = if price_data.is_empty() || price_data == "null" { // Empty price_data means remove pricing (make not purchasable) @@ -728,56 +739,65 @@ impl WasmSdk { match price_type.to_lowercase().as_str() { "single" => { // Parse single price - let price_credits: Credits = price_data.parse::() + let price_credits: Credits = price_data + .parse::() .map_err(|e| JsValue::from_str(&format!("Invalid price credits: {}", e)))?; Some(TokenPricingSchedule::SinglePrice(price_credits)) - }, + } "tiered" | "set" => { // Parse tiered pricing map from JSON - let price_map: std::collections::HashMap = serde_json::from_str(&price_data) - .map_err(|e| JsValue::from_str(&format!("Invalid tiered pricing JSON: {}", e)))?; - + let price_map: std::collections::HashMap = + serde_json::from_str(&price_data).map_err(|e| { + JsValue::from_str(&format!("Invalid tiered pricing JSON: {}", e)) + })?; + // Convert to BTreeMap let mut btree_map = BTreeMap::new(); for (amount_str, credits) in price_map { - let amount: TokenAmount = amount_str.parse() - .map_err(|e| JsValue::from_str(&format!("Invalid token amount '{}': {}", amount_str, e)))?; + let amount: TokenAmount = amount_str.parse().map_err(|e| { + JsValue::from_str(&format!( + "Invalid token amount '{}': {}", + amount_str, e + )) + })?; btree_map.insert(amount, credits); } - + if btree_map.is_empty() { return Err(JsValue::from_str("Tiered pricing map cannot be empty")); } - + Some(TokenPricingSchedule::SetPrices(btree_map)) - }, - _ => return Err(JsValue::from_str("Invalid price type. Use 'single' or 'tiered'")) + } + _ => { + return Err(JsValue::from_str( + "Invalid price type. Use 'single' or 'tiered'", + )) + } } }; - + // Get identity to find matching authentication key let identity = dash_sdk::platform::Identity::fetch(&sdk, actor_id) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Identity not found"))?; - + // Find matching authentication key and create signer - let (_, matching_key) = crate::sdk::WasmSdk::find_authentication_key(&identity, &private_key_wif)?; + let (_, matching_key) = + crate::sdk::WasmSdk::find_authentication_key(&identity, &private_key_wif)?; let signer = crate::sdk::WasmSdk::create_signer_from_wif(&private_key_wif, sdk.network)?; let public_key = matching_key.clone(); - + // Calculate token ID - let token_id = Identifier::from(calculate_token_id( - contract_id.as_bytes(), - token_position, - )); - + let token_id = Identifier::from(calculate_token_id(contract_id.as_bytes(), token_position)); + // Get identity contract nonce let identity_contract_nonce = sdk .get_identity_contract_nonce(actor_id, contract_id, true, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch nonce: {}", e)))?; - + // Create the state transition let platform_version = sdk.version(); let state_transition = BatchTransition::new_token_change_direct_purchase_price_transition( @@ -794,14 +814,15 @@ impl WasmSdk { &signer, platform_version, None, // state_transition_creation_options - ).map_err(|e| JsValue::from_str(&format!("Failed to create set price transition: {}", e)))?; - + ) + .map_err(|e| JsValue::from_str(&format!("Failed to create set price transition: {}", e)))?; + // Broadcast the transition let proof_result = state_transition .broadcast_and_wait::(&sdk, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to broadcast transition: {}", e)))?; - + // Format and return result based on the proof result type match proof_result { StateTransitionProofResult::VerifiedTokenPricingSchedule(owner_id, schedule) => { @@ -824,35 +845,39 @@ impl WasmSdk { }) } }) - })).map_err(|e| JsValue::from_str(&format!("Failed to serialize result: {}", e))) - }, - StateTransitionProofResult::VerifiedTokenGroupActionWithTokenPricingSchedule(power, status, schedule) => { - to_value(&serde_json::json!({ - "type": "VerifiedTokenGroupActionWithTokenPricingSchedule", - "groupPower": power, - "status": format!("{:?}", status), - "pricingSchedule": schedule.map(|s| match s { - TokenPricingSchedule::SinglePrice(credits) => serde_json::json!({ - "type": "single", - "price": credits - }), - TokenPricingSchedule::SetPrices(prices) => { - let price_map: std::collections::HashMap = prices - .into_iter() - .map(|(amount, credits)| (amount.to_string(), credits)) - .collect(); - serde_json::json!({ - "type": "tiered", - "prices": price_map - }) - } - }) - })).map_err(|e| JsValue::from_str(&format!("Failed to serialize result: {}", e))) - }, - _ => self.format_token_result(proof_result) + })) + .map_err(|e| JsValue::from_str(&format!("Failed to serialize result: {}", e))) + } + StateTransitionProofResult::VerifiedTokenGroupActionWithTokenPricingSchedule( + power, + status, + schedule, + ) => to_value(&serde_json::json!({ + "type": "VerifiedTokenGroupActionWithTokenPricingSchedule", + "groupPower": power, + "status": format!("{:?}", status), + "pricingSchedule": schedule.map(|s| match s { + TokenPricingSchedule::SinglePrice(credits) => serde_json::json!({ + "type": "single", + "price": credits + }), + TokenPricingSchedule::SetPrices(prices) => { + let price_map: std::collections::HashMap = prices + .into_iter() + .map(|(amount, credits)| (amount.to_string(), credits)) + .collect(); + serde_json::json!({ + "type": "tiered", + "prices": price_map + }) + } + }) + })) + .map_err(|e| JsValue::from_str(&format!("Failed to serialize result: {}", e))), + _ => self.format_token_result(proof_result), } } - + /// Purchase tokens directly at the configured price. /// /// # Arguments @@ -878,89 +903,97 @@ impl WasmSdk { private_key_wif: String, ) -> Result { use dash_sdk::dpp::fee::Credits; - + let sdk = self.inner_clone(); - + // Parse and validate parameters - let (contract_id, purchaser_id, token_amount, _) = self.parse_token_params( - &data_contract_id, - &identity_id, - &amount, - None, - ).await?; - + let (contract_id, purchaser_id, token_amount, _) = self + .parse_token_params(&data_contract_id, &identity_id, &amount, None) + .await?; + // Get total price - either from parameter or fetch from pricing schedule let price_credits: Credits = match total_agreed_price { Some(price_str) => { // Use provided price - price_str.parse::() + price_str + .parse::() .map_err(|e| JsValue::from_str(&format!("Invalid total agreed price: {}", e)))? } None => { // Fetch price from pricing schedule - let token_id = crate::queries::token::calculate_token_id_from_contract(&data_contract_id, token_position) - .map_err(|e| JsValue::from_str(&format!("Failed to calculate token ID: {:?}", e)))?; - + let token_id = crate::queries::token::calculate_token_id_from_contract( + &data_contract_id, + token_position, + ) + .map_err(|e| { + JsValue::from_str(&format!("Failed to calculate token ID: {:?}", e)) + })?; + let token_ids = vec![token_id]; - let prices = crate::queries::token::get_token_direct_purchase_prices(self, token_ids).await - .map_err(|e| JsValue::from_str(&format!("Failed to fetch token price: {:?}", e)))?; - + let prices = + crate::queries::token::get_token_direct_purchase_prices(self, token_ids) + .await + .map_err(|e| { + JsValue::from_str(&format!("Failed to fetch token price: {:?}", e)) + })?; + // Use js_sys to work with JavaScript objects - use js_sys::{Reflect, Array}; - + use js_sys::{Array, Reflect}; + // Get the prices array from the result let prices_prop = Reflect::get(&prices, &JsValue::from_str("prices")) .map_err(|_| JsValue::from_str("Failed to get prices property"))?; - + // Convert to array and get first element let prices_array = Array::from(&prices_prop); if prices_array.length() == 0 { return Err(JsValue::from_str("No prices found for token")); } - + let first_price = prices_array.get(0); - + // Get current price from the price object - let current_price_prop = Reflect::get(&first_price, &JsValue::from_str("currentPrice")) - .map_err(|_| JsValue::from_str("Failed to get currentPrice property"))?; - + let current_price_prop = + Reflect::get(&first_price, &JsValue::from_str("currentPrice")) + .map_err(|_| JsValue::from_str("Failed to get currentPrice property"))?; + // Convert to string and parse - let price_str = current_price_prop.as_string() + let price_str = current_price_prop + .as_string() .ok_or_else(|| JsValue::from_str("Current price is not a string"))?; - - let price_per_token = price_str.parse::() - .map_err(|e| JsValue::from_str(&format!("Invalid current price format: {}", e)))?; - + + let price_per_token = price_str.parse::().map_err(|e| { + JsValue::from_str(&format!("Invalid current price format: {}", e)) + })?; + price_per_token * token_amount } }; - + // Fetch and cache the contract self.fetch_and_cache_token_contract(contract_id).await?; - + // Get identity to find matching authentication key let identity = dash_sdk::platform::Identity::fetch(&sdk, purchaser_id) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Identity not found"))?; - + // Find matching authentication key and create signer - let (_, matching_key) = crate::sdk::WasmSdk::find_authentication_key(&identity, &private_key_wif)?; + let (_, matching_key) = + crate::sdk::WasmSdk::find_authentication_key(&identity, &private_key_wif)?; let signer = crate::sdk::WasmSdk::create_signer_from_wif(&private_key_wif, sdk.network)?; let public_key = matching_key.clone(); - + // Calculate token ID - let token_id = Identifier::from(calculate_token_id( - contract_id.as_bytes(), - token_position, - )); - + let token_id = Identifier::from(calculate_token_id(contract_id.as_bytes(), token_position)); + // Get identity contract nonce let identity_contract_nonce = sdk .get_identity_contract_nonce(purchaser_id, contract_id, true, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch nonce: {}", e)))?; - + // Create the state transition let platform_version = sdk.version(); let state_transition = BatchTransition::new_token_direct_purchase_transition( @@ -976,29 +1009,35 @@ impl WasmSdk { &signer, platform_version, None, // state_transition_creation_options - ).map_err(|e| JsValue::from_str(&format!("Failed to create direct purchase transition: {}", e)))?; - + ) + .map_err(|e| { + JsValue::from_str(&format!( + "Failed to create direct purchase transition: {}", + e + )) + })?; + // Broadcast the transition let proof_result = state_transition .broadcast_and_wait::(&sdk, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to broadcast transition: {}", e)))?; - + // Format and return result self.format_token_result(proof_result) } - + /// Claim tokens from a distribution - /// + /// /// # Arguments - /// + /// /// * `data_contract_id` - ID of the data contract containing the token /// * `token_position` - Position of the token within the contract /// * `distribution_type` - Type of distribution: "perpetual" or "preprogrammed" /// * `identity_id` - Identity ID of the claimant /// * `private_key_wif` - Private key in WIF format /// * `public_note` - Optional public note - /// + /// /// Returns a Promise that resolves to a JsValue containing the state transition result #[wasm_bindgen(js_name = tokenClaim)] pub async fn token_claim( @@ -1010,51 +1049,57 @@ impl WasmSdk { private_key_wif: String, public_note: Option, ) -> Result { - use dash_sdk::dpp::data_contract::associated_token::token_distribution_key::TokenDistributionType; - + use dash_sdk::dpp::data_contract::associated_token::token_distribution_key::TokenDistributionType; + let sdk = self.inner_clone(); - + // Parse identifiers - let (contract_id, identity_identifier, _, _) = self.parse_token_params( - &data_contract_id, - &identity_id, - "0", // Amount not needed for claim - None, - ).await?; - + let (contract_id, identity_identifier, _, _) = self + .parse_token_params( + &data_contract_id, + &identity_id, + "0", // Amount not needed for claim + None, + ) + .await?; + // Fetch and cache the contract self.fetch_and_cache_token_contract(contract_id).await?; - + // Parse distribution type let dist_type = match distribution_type.to_lowercase().as_str() { "perpetual" => TokenDistributionType::Perpetual, - "preprogrammed" | "pre-programmed" | "scheduled" => TokenDistributionType::PreProgrammed, - _ => return Err(JsValue::from_str("Invalid distribution type. Use 'perpetual' or 'preprogrammed'")) + "preprogrammed" | "pre-programmed" | "scheduled" => { + TokenDistributionType::PreProgrammed + } + _ => { + return Err(JsValue::from_str( + "Invalid distribution type. Use 'perpetual' or 'preprogrammed'", + )) + } }; - + // Get identity to find matching authentication key let identity = dash_sdk::platform::Identity::fetch(&sdk, identity_identifier) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Identity not found"))?; - + // Find matching authentication key and create signer - let (_, matching_key) = crate::sdk::WasmSdk::find_authentication_key(&identity, &private_key_wif)?; + let (_, matching_key) = + crate::sdk::WasmSdk::find_authentication_key(&identity, &private_key_wif)?; let signer = crate::sdk::WasmSdk::create_signer_from_wif(&private_key_wif, sdk.network)?; let public_key = matching_key.clone(); - + // Calculate token ID - let token_id = Identifier::from(calculate_token_id( - contract_id.as_bytes(), - token_position, - )); - + let token_id = Identifier::from(calculate_token_id(contract_id.as_bytes(), token_position)); + // Get identity contract nonce let identity_contract_nonce = sdk .get_identity_contract_nonce(identity_identifier, contract_id, true, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch nonce: {}", e)))?; - + // Create the state transition directly as a token claim transition let platform_version = sdk.version(); // Create state transition using BatchTransition's token claim method @@ -1071,18 +1116,19 @@ impl WasmSdk { &signer, platform_version, None, // state_transition_creation_options - ).map_err(|e| JsValue::from_str(&format!("Failed to create claim transition: {}", e)))?; - + ) + .map_err(|e| JsValue::from_str(&format!("Failed to create claim transition: {}", e)))?; + // Broadcast the transition let proof_result = state_transition .broadcast_and_wait::(&sdk, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to broadcast transition: {}", e)))?; - + // Format and return result self.format_token_result(proof_result) } - + /// Update token configuration settings. /// /// # Arguments @@ -1109,24 +1155,26 @@ impl WasmSdk { private_key_wif: String, public_note: Option, ) -> Result { - use dash_sdk::dpp::data_contract::associated_token::token_configuration_item::TokenConfigurationChangeItem; use dash_sdk::dpp::data_contract::associated_token::token_configuration_convention::TokenConfigurationConvention; - use dash_sdk::dpp::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers; + use dash_sdk::dpp::data_contract::associated_token::token_configuration_item::TokenConfigurationChangeItem; use dash_sdk::dpp::data_contract::associated_token::token_perpetual_distribution::TokenPerpetualDistribution; - + use dash_sdk::dpp::data_contract::change_control_rules::authorized_action_takers::AuthorizedActionTakers; + let sdk = self.inner_clone(); - + // Parse identifiers - let (contract_id, owner_id, _, _) = self.parse_token_params( - &data_contract_id, - &identity_id, - "0", // Amount not needed for config update - None, - ).await?; - + let (contract_id, owner_id, _, _) = self + .parse_token_params( + &data_contract_id, + &identity_id, + "0", // Amount not needed for config update + None, + ) + .await?; + // Fetch and cache the contract self.fetch_and_cache_token_contract(contract_id).await?; - + // Parse configuration change item based on type let config_change_item = match config_item_type.as_str() { "conventions" => { @@ -1134,94 +1182,150 @@ impl WasmSdk { let convention: TokenConfigurationConvention = serde_json::from_str(&config_value) .map_err(|e| JsValue::from_str(&format!("Invalid conventions JSON: {}", e)))?; TokenConfigurationChangeItem::Conventions(convention) - }, + } "max_supply" => { if config_value.is_empty() || config_value == "null" { TokenConfigurationChangeItem::MaxSupply(None) } else { - let max_supply: TokenAmount = config_value.parse() + let max_supply: TokenAmount = config_value + .parse() .map_err(|e| JsValue::from_str(&format!("Invalid max supply: {}", e)))?; TokenConfigurationChangeItem::MaxSupply(Some(max_supply)) } - }, + } "perpetual_distribution" => { if config_value.is_empty() || config_value == "null" { TokenConfigurationChangeItem::PerpetualDistribution(None) } else { // Parse JSON for perpetual distribution config - let distribution: TokenPerpetualDistribution = serde_json::from_str(&config_value) - .map_err(|e| JsValue::from_str(&format!("Invalid perpetual distribution JSON: {}", e)))?; + let distribution: TokenPerpetualDistribution = + serde_json::from_str(&config_value).map_err(|e| { + JsValue::from_str(&format!( + "Invalid perpetual distribution JSON: {}", + e + )) + })?; TokenConfigurationChangeItem::PerpetualDistribution(Some(distribution)) } - }, + } "new_tokens_destination_identity" => { if config_value.is_empty() || config_value == "null" { TokenConfigurationChangeItem::NewTokensDestinationIdentity(None) } else { let dest_id = Identifier::from_string(&config_value, Encoding::Base58) - .map_err(|e| JsValue::from_str(&format!("Invalid destination identity ID: {}", e)))?; + .map_err(|e| { + JsValue::from_str(&format!("Invalid destination identity ID: {}", e)) + })?; TokenConfigurationChangeItem::NewTokensDestinationIdentity(Some(dest_id)) } - }, + } "minting_allow_choosing_destination" => { - let allow: bool = config_value.parse() + let allow: bool = config_value + .parse() .map_err(|_| JsValue::from_str("Invalid boolean value"))?; TokenConfigurationChangeItem::MintingAllowChoosingDestination(allow) - }, - "manual_minting" | "manual_burning" | "conventions_control_group" | - "conventions_admin_group" | "max_supply_control_group" | "max_supply_admin_group" | - "perpetual_distribution_control_group" | "perpetual_distribution_admin_group" | - "new_tokens_destination_identity_control_group" | "new_tokens_destination_identity_admin_group" | - "minting_allow_choosing_destination_control_group" | "minting_allow_choosing_destination_admin_group" | - "manual_minting_admin_group" | "manual_burning_admin_group" => { + } + "manual_minting" + | "manual_burning" + | "conventions_control_group" + | "conventions_admin_group" + | "max_supply_control_group" + | "max_supply_admin_group" + | "perpetual_distribution_control_group" + | "perpetual_distribution_admin_group" + | "new_tokens_destination_identity_control_group" + | "new_tokens_destination_identity_admin_group" + | "minting_allow_choosing_destination_control_group" + | "minting_allow_choosing_destination_admin_group" + | "manual_minting_admin_group" + | "manual_burning_admin_group" => { // Parse AuthorizedActionTakers from JSON let action_takers: AuthorizedActionTakers = serde_json::from_str(&config_value) - .map_err(|e| JsValue::from_str(&format!("Invalid authorized action takers JSON: {}", e)))?; - + .map_err(|e| { + JsValue::from_str(&format!("Invalid authorized action takers JSON: {}", e)) + })?; + match config_item_type.as_str() { "manual_minting" => TokenConfigurationChangeItem::ManualMinting(action_takers), "manual_burning" => TokenConfigurationChangeItem::ManualBurning(action_takers), - "conventions_control_group" => TokenConfigurationChangeItem::ConventionsControlGroup(action_takers), - "conventions_admin_group" => TokenConfigurationChangeItem::ConventionsAdminGroup(action_takers), - "max_supply_control_group" => TokenConfigurationChangeItem::MaxSupplyControlGroup(action_takers), - "max_supply_admin_group" => TokenConfigurationChangeItem::MaxSupplyAdminGroup(action_takers), - "perpetual_distribution_control_group" => TokenConfigurationChangeItem::PerpetualDistributionControlGroup(action_takers), - "perpetual_distribution_admin_group" => TokenConfigurationChangeItem::PerpetualDistributionAdminGroup(action_takers), - "new_tokens_destination_identity_control_group" => TokenConfigurationChangeItem::NewTokensDestinationIdentityControlGroup(action_takers), - "new_tokens_destination_identity_admin_group" => TokenConfigurationChangeItem::NewTokensDestinationIdentityAdminGroup(action_takers), - "minting_allow_choosing_destination_control_group" => TokenConfigurationChangeItem::MintingAllowChoosingDestinationControlGroup(action_takers), - "minting_allow_choosing_destination_admin_group" => TokenConfigurationChangeItem::MintingAllowChoosingDestinationAdminGroup(action_takers), - "manual_minting_admin_group" => TokenConfigurationChangeItem::ManualMintingAdminGroup(action_takers), - "manual_burning_admin_group" => TokenConfigurationChangeItem::ManualBurningAdminGroup(action_takers), - _ => unreachable!() + "conventions_control_group" => { + TokenConfigurationChangeItem::ConventionsControlGroup(action_takers) + } + "conventions_admin_group" => { + TokenConfigurationChangeItem::ConventionsAdminGroup(action_takers) + } + "max_supply_control_group" => { + TokenConfigurationChangeItem::MaxSupplyControlGroup(action_takers) + } + "max_supply_admin_group" => { + TokenConfigurationChangeItem::MaxSupplyAdminGroup(action_takers) + } + "perpetual_distribution_control_group" => { + TokenConfigurationChangeItem::PerpetualDistributionControlGroup( + action_takers, + ) + } + "perpetual_distribution_admin_group" => { + TokenConfigurationChangeItem::PerpetualDistributionAdminGroup(action_takers) + } + "new_tokens_destination_identity_control_group" => { + TokenConfigurationChangeItem::NewTokensDestinationIdentityControlGroup( + action_takers, + ) + } + "new_tokens_destination_identity_admin_group" => { + TokenConfigurationChangeItem::NewTokensDestinationIdentityAdminGroup( + action_takers, + ) + } + "minting_allow_choosing_destination_control_group" => { + TokenConfigurationChangeItem::MintingAllowChoosingDestinationControlGroup( + action_takers, + ) + } + "minting_allow_choosing_destination_admin_group" => { + TokenConfigurationChangeItem::MintingAllowChoosingDestinationAdminGroup( + action_takers, + ) + } + "manual_minting_admin_group" => { + TokenConfigurationChangeItem::ManualMintingAdminGroup(action_takers) + } + "manual_burning_admin_group" => { + TokenConfigurationChangeItem::ManualBurningAdminGroup(action_takers) + } + _ => unreachable!(), } - }, - _ => return Err(JsValue::from_str(&format!("Invalid config item type: {}", config_item_type))) + } + _ => { + return Err(JsValue::from_str(&format!( + "Invalid config item type: {}", + config_item_type + ))) + } }; - + // Get identity to find matching authentication key let identity = dash_sdk::platform::Identity::fetch(&sdk, owner_id) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch identity: {}", e)))? .ok_or_else(|| JsValue::from_str("Identity not found"))?; - + // Find matching authentication key and create signer - let (_, matching_key) = crate::sdk::WasmSdk::find_authentication_key(&identity, &private_key_wif)?; + let (_, matching_key) = + crate::sdk::WasmSdk::find_authentication_key(&identity, &private_key_wif)?; let signer = crate::sdk::WasmSdk::create_signer_from_wif(&private_key_wif, sdk.network)?; let public_key = matching_key.clone(); - + // Calculate token ID - let token_id = Identifier::from(calculate_token_id( - contract_id.as_bytes(), - token_position, - )); - + let token_id = Identifier::from(calculate_token_id(contract_id.as_bytes(), token_position)); + // Get identity contract nonce let identity_contract_nonce = sdk .get_identity_contract_nonce(owner_id, contract_id, true, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to fetch nonce: {}", e)))?; - + // Create the state transition let platform_version = sdk.version(); let state_transition = BatchTransition::new_token_config_update_transition( @@ -1238,15 +1342,18 @@ impl WasmSdk { &signer, platform_version, None, // state_transition_creation_options - ).map_err(|e| JsValue::from_str(&format!("Failed to create config update transition: {}", e)))?; - + ) + .map_err(|e| { + JsValue::from_str(&format!("Failed to create config update transition: {}", e)) + })?; + // Broadcast the transition let proof_result = state_transition .broadcast_and_wait::(&sdk, None) .await .map_err(|e| JsValue::from_str(&format!("Failed to broadcast transition: {}", e)))?; - + // Format and return result self.format_token_result(proof_result) } -} \ No newline at end of file +} diff --git a/packages/wasm-sdk/src/wallet/dip14.rs b/packages/wasm-sdk/src/wallet/dip14.rs index c1c8141f11..124619d20e 100644 --- a/packages/wasm-sdk/src/wallet/dip14.rs +++ b/packages/wasm-sdk/src/wallet/dip14.rs @@ -1,18 +1,17 @@ //! DIP14 - Extended HD Key Derivation using 256-bit Unsigned Integers -//! +//! //! This module implements DIP14, which extends BIP32 to support 256-bit derivation indices //! instead of the standard 31-bit limitation. -use dash_sdk::dpp::key_wallet::bip32::{ExtendedPrivKey, ExtendedPubKey}; -use dash_sdk::dpp::dashcore::secp256k1::{self, Secp256k1, SecretKey, PublicKey, Scalar}; +use dash_sdk::dpp::dashcore::hashes::{sha256, Hash}; +use dash_sdk::dpp::dashcore::secp256k1::{self, PublicKey, Scalar, Secp256k1, SecretKey}; use dash_sdk::dpp::dashcore::Network; +use dash_sdk::dpp::key_wallet; +use dash_sdk::dpp::key_wallet::bip32::{ExtendedPrivKey, ExtendedPubKey}; +use hex; use hmac::{Hmac, Mac}; use sha2::Sha512; use std::convert::TryInto; -use dash_sdk::dpp::dashcore::hashes::{sha256, ripemd160, Hash}; -use hex; -use dash_sdk::dpp::dashcore; -use dash_sdk::dpp::key_wallet; type HmacSha512 = Hmac; @@ -53,9 +52,9 @@ pub struct Dip14ExtendedPrivKey { // DIP14 Version bytes const _DIP14_VERSION_MAINNET_PRIVATE: [u8; 4] = [0x04, 0x9e, 0xdd, 0x93]; // dpms -const _DIP14_VERSION_MAINNET_PUBLIC: [u8; 4] = [0x04, 0x9e, 0xdc, 0x93]; // dpmp +const _DIP14_VERSION_MAINNET_PUBLIC: [u8; 4] = [0x04, 0x9e, 0xdc, 0x93]; // dpmp const _DIP14_VERSION_TESTNET_PRIVATE: [u8; 4] = [0x04, 0xa5, 0x90, 0x8e]; // dpts -const _DIP14_VERSION_TESTNET_PUBLIC: [u8; 4] = [0x04, 0xa5, 0x8f, 0x51]; // dptp +const _DIP14_VERSION_TESTNET_PUBLIC: [u8; 4] = [0x04, 0xa5, 0x8f, 0x51]; // dptp impl Dip14ExtendedPrivKey { /// Create from a standard BIP32 ExtendedPrivKey (for master key) @@ -64,7 +63,7 @@ impl Dip14ExtendedPrivKey { // For BIP32 compatibility, store the 32-bit child number in the last 4 bytes let child_u32: u32 = key.child_number.into(); child_number[28..32].copy_from_slice(&child_u32.to_be_bytes()); - + Self { network: key.network, depth: key.depth, @@ -74,18 +73,19 @@ impl Dip14ExtendedPrivKey { private_key: key.private_key, } } - + /// Convert to standard BIP32 ExtendedPrivKey (only valid for indices < 2^32-1) pub fn to_bip32(&self) -> Result { // Check if the child number fits in 32 bits if !self.child_number[0..28].iter().all(|&b| b == 0) { return Err(Dip14Error::InvalidIndex); } - - let child_bytes: [u8; 4] = self.child_number[28..32].try_into() + + let child_bytes: [u8; 4] = self.child_number[28..32] + .try_into() .map_err(|_| Dip14Error::InvalidIndex)?; let child_number = key_wallet::bip32::ChildNumber::from(u32::from_be_bytes(child_bytes)); - + Ok(ExtendedPrivKey { network: self.network, depth: self.depth, @@ -95,15 +95,15 @@ impl Dip14ExtendedPrivKey { private_key: self.private_key, }) } - + /// Derive a child key using DIP14 extended derivation pub fn derive_child(&self, index: &[u8; 32], hardened: bool) -> Result { let secp = Secp256k1::new(); - + // Prepare HMAC input based on hardened flag let mut hmac = HmacSha512::new_from_slice(&self.chain_code) .map_err(|_| Dip14Error::DerivationFailed("Invalid chain code".to_string()))?; - + if hardened { // Hardened: 0x00 || ser256(k_parent) || ser256(i) hmac.update(&[0x00]); @@ -115,60 +115,81 @@ impl Dip14ExtendedPrivKey { hmac.update(&public_key.serialize()); hmac.update(&ser256(index)); } - + let result = hmac.finalize().into_bytes(); let (il_bytes, child_chain_code) = result.split_at(32); - + // Perform scalar addition: child_key = IL + parent_key (mod n) // This is the core of BIP32/DIP14 child key derivation - + // First, try to create a secret key from IL let il_scalar = match SecretKey::from_slice(il_bytes) { Ok(key) => key, - Err(_) => return Err(Dip14Error::DerivationFailed("Invalid IL bytes (IL >= n)".to_string())), + Err(_) => { + return Err(Dip14Error::DerivationFailed( + "Invalid IL bytes (IL >= n)".to_string(), + )) + } }; - + // Add parent key to IL // In secp256k1, we perform scalar addition: child_key = parent_key + IL (mod n) // Convert IL to a Scalar for the tweak operation let il_scalar_bytes = il_scalar.secret_bytes(); - let tweak = Scalar::from_be_bytes(il_scalar_bytes) - .map_err(|_| Dip14Error::DerivationFailed("Failed to convert IL to scalar".to_string()))?; - + let tweak = Scalar::from_be_bytes(il_scalar_bytes).map_err(|_| { + Dip14Error::DerivationFailed("Failed to convert IL to scalar".to_string()) + })?; + // Debug: log parent and IL values before addition - web_sys::console::log_1(&format!("Parent key: {}", hex::encode(&self.private_key.secret_bytes())).into()); + web_sys::console::log_1( + &format!( + "Parent key: {}", + hex::encode(&self.private_key.secret_bytes()) + ) + .into(), + ); web_sys::console::log_1(&format!("IL (tweak): {}", hex::encode(&il_scalar_bytes)).into()); - + // Perform scalar addition: child_key = parent_key + IL (mod n) - let child_key = self.private_key.add_tweak(&tweak) + let child_key = self + .private_key + .add_tweak(&tweak) .map_err(|e| Dip14Error::DerivationFailed(format!("Failed to add tweak: {}", e)))?; - + // Debug: log result after addition - web_sys::console::log_1(&format!("Child key after addition: {}", hex::encode(&child_key.secret_bytes())).into()); - + web_sys::console::log_1( + &format!( + "Child key after addition: {}", + hex::encode(&child_key.secret_bytes()) + ) + .into(), + ); + // Calculate parent fingerprint (first 4 bytes of parent pubkey hash160) let parent_pubkey = PublicKey::from_secret_key(&secp, &self.private_key); // Use sha256 then ripemd160 to create hash160 let sha256_hash = sha256::Hash::hash(&parent_pubkey.serialize()); - let parent_pubkey_hash = dash_sdk::dpp::dashcore::hashes::ripemd160::Hash::hash(&sha256_hash[..]); + let parent_pubkey_hash = + dash_sdk::dpp::dashcore::hashes::ripemd160::Hash::hash(&sha256_hash[..]); let mut parent_fingerprint = [0u8; 4]; parent_fingerprint.copy_from_slice(&parent_pubkey_hash[0..4]); - + Ok(Self { network: self.network, depth: self.depth + 1, parent_fingerprint, child_number: *index, - chain_code: child_chain_code.try_into() - .map_err(|_| Dip14Error::DerivationFailed("Invalid chain code length".to_string()))?, + chain_code: child_chain_code.try_into().map_err(|_| { + Dip14Error::DerivationFailed("Invalid chain code length".to_string()) + })?, private_key: child_key, }) } - + /// Get the extended public key pub fn to_extended_pub_key(&self, secp: &Secp256k1) -> Dip14ExtendedPubKey { let public_key = PublicKey::from_secret_key(secp, &self.private_key); - + Dip14ExtendedPubKey { network: self.network, depth: self.depth, @@ -197,11 +218,12 @@ impl Dip14ExtendedPubKey { if !self.child_number[0..28].iter().all(|&b| b == 0) { return Err(Dip14Error::InvalidIndex); } - - let child_bytes: [u8; 4] = self.child_number[28..32].try_into() + + let child_bytes: [u8; 4] = self.child_number[28..32] + .try_into() .map_err(|_| Dip14Error::InvalidIndex)?; let child_number = key_wallet::bip32::ChildNumber::from(u32::from_be_bytes(child_bytes)); - + Ok(ExtendedPubKey { network: self.network, depth: self.depth, @@ -216,7 +238,7 @@ impl Dip14ExtendedPubKey { #[cfg(test)] mod tests { use super::*; - + #[test] fn test_ser256() { let mut value = [0u8; 32]; @@ -225,17 +247,17 @@ mod tests { assert_eq!(serialized[31], 1); assert_eq!(serialized[0], 0); } - + #[test] fn test_bip32_compatibility() { // Test that indices < 2^32-1 produce compatible results let seed = [0x42u8; 64]; let master = ExtendedPrivKey::new_master(Network::Testnet, &seed).unwrap(); let dip14_master = Dip14ExtendedPrivKey::from_bip32(&master); - + // Should be able to convert back for small indices let bip32_key = dip14_master.to_bip32().unwrap(); assert_eq!(master.private_key, bip32_key.private_key); assert_eq!(master.chain_code, bip32_key.chain_code); } -} \ No newline at end of file +} diff --git a/packages/wasm-sdk/src/wallet/extended_derivation.rs b/packages/wasm-sdk/src/wallet/extended_derivation.rs index f16c29e8ff..6131dac4b8 100644 --- a/packages/wasm-sdk/src/wallet/extended_derivation.rs +++ b/packages/wasm-sdk/src/wallet/extended_derivation.rs @@ -1,113 +1,118 @@ //! Extended key derivation for DIP14/DIP15 support -//! +//! //! Implements 256-bit derivation paths for DashPay contact keys -use wasm_bindgen::prelude::*; -use dash_sdk::dpp::key_wallet::{ExtendedPrivKey, DerivationPath, bip32}; -use dash_sdk::dpp::dashcore::secp256k1::Secp256k1; use crate::wallet::key_derivation::mnemonic_to_seed; +use dash_sdk::dpp::dashcore; +use dash_sdk::dpp::dashcore::secp256k1::Secp256k1; +use dash_sdk::dpp::key_wallet::{bip32, DerivationPath, ExtendedPrivKey}; use std::str::FromStr; +use wasm_bindgen::prelude::*; use web_sys; -use dash_sdk::dpp::dashcore; /// Derive a key from seed phrase with extended path supporting 256-bit indices /// This supports DIP14/DIP15 paths with identity IDs #[wasm_bindgen] pub fn derive_key_from_seed_with_extended_path( - mnemonic: &str, - passphrase: Option, + mnemonic: &str, + passphrase: Option, path: &str, - network: &str + network: &str, ) -> Result { // Debug: Log the path being processed web_sys::console::log_1(&format!("Processing extended path: {}", path).into()); - + // Get seed from mnemonic let seed = mnemonic_to_seed(mnemonic, passphrase)?; - + let net = match network { "mainnet" => dashcore::Network::Dash, "testnet" => dashcore::Network::Testnet, _ => return Err(JsError::new("Invalid network")), }; - + // Create master extended private key from seed let master_key = ExtendedPrivKey::new_master(net, &seed) .map_err(|e| JsError::new(&format!("Failed to create master key: {}", e)))?; - + // Parse the derivation path using dashcore's built-in parser // This already supports 256-bit hex values like 0x775d3854... let derivation_path = DerivationPath::from_str(path) .map_err(|e| JsError::new(&format!("Invalid derivation path: {}", e)))?; - + // Use dashcore's built-in derive_priv method which handles DIP14 let secp = Secp256k1::new(); - let derived_key = master_key.derive_priv(&secp, &derivation_path) + let derived_key = master_key + .derive_priv(&secp, &derivation_path) .map_err(|e| JsError::new(&format!("Failed to derive key: {}", e)))?; - + // Get the extended public key let xpub = bip32::ExtendedPubKey::from_priv(&secp, &derived_key); - + // Get the private key let private_key = dashcore::PrivateKey::new(derived_key.private_key, net); - + // Get public key let public_key = private_key.public_key(&secp); - + // Get address let address = dashcore::Address::p2pkh(&public_key, net); - + // Create result object let obj = js_sys::Object::new(); - - js_sys::Reflect::set( - &obj, - &JsValue::from_str("path"), - &JsValue::from_str(path), - ).map_err(|_| JsError::new("Failed to set path property"))?; - + + js_sys::Reflect::set(&obj, &JsValue::from_str("path"), &JsValue::from_str(path)) + .map_err(|_| JsError::new("Failed to set path property"))?; + js_sys::Reflect::set( &obj, &JsValue::from_str("private_key_wif"), &JsValue::from_str(&private_key.to_wif()), - ).map_err(|_| JsError::new("Failed to set private_key_wif property"))?; - + ) + .map_err(|_| JsError::new("Failed to set private_key_wif property"))?; + js_sys::Reflect::set( &obj, &JsValue::from_str("private_key_hex"), &JsValue::from_str(&hex::encode(private_key.inner.secret_bytes())), - ).map_err(|_| JsError::new("Failed to set private_key_hex property"))?; - + ) + .map_err(|_| JsError::new("Failed to set private_key_hex property"))?; + js_sys::Reflect::set( &obj, &JsValue::from_str("public_key"), &JsValue::from_str(&hex::encode(public_key.to_bytes())), - ).map_err(|_| JsError::new("Failed to set public_key property"))?; - + ) + .map_err(|_| JsError::new("Failed to set public_key property"))?; + js_sys::Reflect::set( &obj, &JsValue::from_str("address"), &JsValue::from_str(&address.to_string()), - ).map_err(|_| JsError::new("Failed to set address property"))?; - + ) + .map_err(|_| JsError::new("Failed to set address property"))?; + js_sys::Reflect::set( &obj, &JsValue::from_str("network"), &JsValue::from_str(network), - ).map_err(|_| JsError::new("Failed to set network property"))?; - + ) + .map_err(|_| JsError::new("Failed to set network property"))?; + js_sys::Reflect::set( &obj, &JsValue::from_str("xprv"), &JsValue::from_str(&derived_key.to_string()), - ).map_err(|_| JsError::new("Failed to set xprv property"))?; - + ) + .map_err(|_| JsError::new("Failed to set xprv property"))?; + js_sys::Reflect::set( &obj, &JsValue::from_str("xpub"), &JsValue::from_str(&xpub.to_string()), - ).map_err(|_| JsError::new("Failed to set xpub property"))?; - + ) + .map_err(|_| JsError::new("Failed to set xpub property"))?; + Ok(obj.into()) } @@ -120,10 +125,10 @@ pub fn derive_dashpay_contact_key( receiver_identity_id: &str, account: u32, address_index: u32, - network: &str + network: &str, ) -> Result { use bs58; - + // Convert base58 identity IDs to hex format if needed let sender_id_formatted = if sender_identity_id.starts_with("0x") { sender_identity_id.to_string() @@ -134,7 +139,7 @@ pub fn derive_dashpay_contact_key( .map_err(|e| JsError::new(&format!("Invalid sender identity ID: {}", e)))?; format!("0x{}", hex::encode(bytes)) }; - + let receiver_id_formatted = if receiver_identity_id.starts_with("0x") { receiver_identity_id.to_string() } else { @@ -144,7 +149,7 @@ pub fn derive_dashpay_contact_key( .map_err(|e| JsError::new(&format!("Invalid receiver identity ID: {}", e)))?; format!("0x{}", hex::encode(bytes)) }; - + // Build the DIP15 path // m / 9' / coin_type' / 15' / account' / sender_id / receiver_id / index let coin_type = match network { @@ -152,66 +157,68 @@ pub fn derive_dashpay_contact_key( "testnet" => 1, _ => return Err(JsError::new("Invalid network")), }; - + let path = format!( "m/9'/{}'/{}'/{}'/{}/{}/{}", coin_type, - 15, // DIP15 feature + 15, // DIP15 feature account, sender_id_formatted, receiver_id_formatted, address_index ); - + web_sys::console::log_1(&format!("DashPay contact path: {}", path).into()); - + // Use the extended derivation function - let result = derive_key_from_seed_with_extended_path( - mnemonic, - passphrase, - &path, - network - )?; - + let result = derive_key_from_seed_with_extended_path(mnemonic, passphrase, &path, network)?; + // Add DIP15-specific metadata - let obj = result.dyn_into::() + let obj = result + .dyn_into::() .map_err(|_| JsError::new("Failed to cast result to object"))?; - + js_sys::Reflect::set( &obj, &JsValue::from_str("dipStandard"), &JsValue::from_str("DIP15"), - ).map_err(|_| JsError::new("Failed to set dipStandard property"))?; - + ) + .map_err(|_| JsError::new("Failed to set dipStandard property"))?; + js_sys::Reflect::set( &obj, &JsValue::from_str("purpose"), &JsValue::from_str("DashPay Contact Payment"), - ).map_err(|_| JsError::new("Failed to set purpose property"))?; - + ) + .map_err(|_| JsError::new("Failed to set purpose property"))?; + js_sys::Reflect::set( &obj, &JsValue::from_str("senderIdentity"), &JsValue::from_str(sender_identity_id), - ).map_err(|_| JsError::new("Failed to set senderIdentity property"))?; - + ) + .map_err(|_| JsError::new("Failed to set senderIdentity property"))?; + js_sys::Reflect::set( &obj, &JsValue::from_str("receiverIdentity"), &JsValue::from_str(receiver_identity_id), - ).map_err(|_| JsError::new("Failed to set receiverIdentity property"))?; - + ) + .map_err(|_| JsError::new("Failed to set receiverIdentity property"))?; + js_sys::Reflect::set( &obj, &JsValue::from_str("account"), &JsValue::from_f64(account as f64), - ).map_err(|_| JsError::new("Failed to set account property"))?; - + ) + .map_err(|_| JsError::new("Failed to set account property"))?; + js_sys::Reflect::set( &obj, &JsValue::from_str("addressIndex"), &JsValue::from_f64(address_index as f64), - ).map_err(|_| JsError::new("Failed to set addressIndex property"))?; - + ) + .map_err(|_| JsError::new("Failed to set addressIndex property"))?; + Ok(obj.into()) -} \ No newline at end of file +} diff --git a/packages/wasm-sdk/src/wallet/key_derivation.rs b/packages/wasm-sdk/src/wallet/key_derivation.rs index eb6afd59c9..904b840315 100644 --- a/packages/wasm-sdk/src/wallet/key_derivation.rs +++ b/packages/wasm-sdk/src/wallet/key_derivation.rs @@ -1,21 +1,18 @@ //! Key derivation functionality for HD wallets -//! +//! //! Implements BIP32, BIP39, and BIP44 standards for hierarchical deterministic key derivation -use wasm_bindgen::prelude::*; -use serde::{Serialize, Deserialize}; -use bip39::{Mnemonic, Language}; -use rand::{RngCore, thread_rng}; -use std::str::FromStr; -use serde_json; +use bip39::{Language, Mnemonic}; use dash_sdk::dpp::dashcore; use dash_sdk::dpp::dashcore::secp256k1::Secp256k1; use dash_sdk::dpp::key_wallet::bip32::{ - ChildNumber, - DerivationPath as BIP32DerivationPath, - ExtendedPrivKey as BIP32ExtendedPrivKey, + ChildNumber, DerivationPath as BIP32DerivationPath, ExtendedPrivKey as BIP32ExtendedPrivKey, ExtendedPubKey as BIP32ExtendedPubKey, }; +use rand::{thread_rng, RngCore}; +use serde::{Deserialize, Serialize}; +use std::str::FromStr; +use wasm_bindgen::prelude::*; /// Dash coin type for BIP44 (mainnet) pub const DASH_COIN_TYPE: u32 = 5; @@ -89,12 +86,9 @@ impl DerivationPath { /// Convert to string representation (e.g., "m/44'/5'/0'/0/0") pub fn to_string(&self) -> String { - format!("m/{}'/{}'/{}'/{}/{}", - self.purpose, - self.coin_type, - self.account, - self.change, - self.index + format!( + "m/{}'/{}'/{}'/{}/{}", + self.purpose, self.coin_type, self.account, self.change, self.index ) } @@ -116,27 +110,34 @@ impl DerivationPath { purpose: parse_hardened(parts[0])?, coin_type: parse_hardened(parts[1])?, account: parse_hardened(parts[2])?, - change: parts[3].parse().map_err(|_| JsError::new("Invalid change index"))?, - index: parts[4].parse().map_err(|_| JsError::new("Invalid address index"))?, + change: parts[3] + .parse() + .map_err(|_| JsError::new("Invalid change index"))?, + index: parts[4] + .parse() + .map_err(|_| JsError::new("Invalid address index"))?, }) } } /// Generate a new mnemonic phrase #[wasm_bindgen] -pub fn generate_mnemonic(word_count: Option, language_code: Option) -> Result { +pub fn generate_mnemonic( + word_count: Option, + language_code: Option, +) -> Result { let words = word_count.unwrap_or(12); - + // Validate word count and calculate entropy bytes let entropy_bytes = match words { - 12 => 16, // 128 bits - 15 => 20, // 160 bits - 18 => 24, // 192 bits - 21 => 28, // 224 bits - 24 => 32, // 256 bits + 12 => 16, // 128 bits + 15 => 20, // 160 bits + 18 => 24, // 192 bits + 21 => 28, // 224 bits + 24 => 32, // 256 bits _ => return Err(JsError::new("Word count must be 12, 15, 18, 21, or 24")), }; - + // Select language based on language code let language = match language_code.as_deref() { Some("en") | None => Language::English, @@ -151,15 +152,15 @@ pub fn generate_mnemonic(word_count: Option, language_code: Option) Some("es") => Language::Spanish, Some(code) => return Err(JsError::new(&format!("Unsupported language code: {}. Supported: en, zh-cn, zh-tw, cs, fr, it, ja, ko, pt, es", code))), }; - + // Generate random entropy let mut entropy = vec![0u8; entropy_bytes]; thread_rng().fill_bytes(&mut entropy); - + // Create mnemonic from entropy let mnemonic = Mnemonic::from_entropy_in(language, &entropy) .map_err(|e| JsError::new(&format!("Failed to generate mnemonic: {}", e)))?; - + Ok(mnemonic.to_string()) } @@ -183,7 +184,7 @@ pub fn validate_mnemonic(mnemonic: &str, language_code: Option) -> bool }; return Mnemonic::parse_in(language, mnemonic).is_ok(); } - + // Otherwise, try to parse in any language Mnemonic::parse_normalized(mnemonic).is_ok() } @@ -194,21 +195,25 @@ pub fn mnemonic_to_seed(mnemonic: &str, passphrase: Option) -> Result, network: &str) -> Result { +pub fn derive_key_from_seed_phrase( + mnemonic: &str, + passphrase: Option, + network: &str, +) -> Result { use crate::wallet::key_generation::KeyPair; - + // Get seed from mnemonic let seed = mnemonic_to_seed(mnemonic, passphrase)?; - + // For now, we'll use the first 32 bytes of the seed as the private key // Note: This is a simplified approach. Proper BIP32/BIP44 would use HD derivation // with the path m/44'/5'/0'/0/0 for Dash mainnet or m/44'/1'/0'/0/0 for testnet @@ -218,17 +223,17 @@ pub fn derive_key_from_seed_phrase(mnemonic: &str, passphrase: Option, n // This shouldn't happen with BIP39, but handle it just in case return Err(JsError::new("Seed too short")); }; - + let net = match network { "mainnet" => dashcore::Network::Dash, "testnet" => dashcore::Network::Testnet, _ => return Err(JsError::new("Invalid network")), }; - + // Create private key from seed bytes let private_key = dashcore::PrivateKey::from_slice(key_bytes, net) .map_err(|e| JsError::new(&format!("Failed to create private key: {}", e)))?; - + // Get public key use dash_sdk::dpp::dashcore::secp256k1::Secp256k1; let secp = Secp256k1::new(); @@ -237,7 +242,7 @@ pub fn derive_key_from_seed_phrase(mnemonic: &str, passphrase: Option, n let public_key_bytes = public_key.inner.serialize(); // Get address let address = dashcore::Address::p2pkh(&public_key, net); - + let key_pair = KeyPair { private_key_wif: private_key.to_wif(), private_key_hex: hex::encode(key_bytes), @@ -245,7 +250,7 @@ pub fn derive_key_from_seed_phrase(mnemonic: &str, passphrase: Option, n address: address.to_string(), network: network.to_string(), }; - + serde_wasm_bindgen::to_value(&key_pair) .map_err(|e| JsError::new(&format!("Failed to serialize key pair: {}", e))) } @@ -253,84 +258,87 @@ pub fn derive_key_from_seed_phrase(mnemonic: &str, passphrase: Option, n /// Derive a key from seed phrase with arbitrary path #[wasm_bindgen] pub fn derive_key_from_seed_with_path( - mnemonic: &str, - passphrase: Option, + mnemonic: &str, + passphrase: Option, path: &str, - network: &str + network: &str, ) -> Result { - use dash_sdk::dpp::key_wallet::{ExtendedPrivKey, DerivationPath}; - + use dash_sdk::dpp::key_wallet::{DerivationPath, ExtendedPrivKey}; + // Get seed from mnemonic let seed = mnemonic_to_seed(mnemonic, passphrase)?; - + let net = match network { "mainnet" => dashcore::Network::Dash, "testnet" => dashcore::Network::Testnet, _ => return Err(JsError::new("Invalid network")), }; - + // Parse derivation path let derivation_path = DerivationPath::from_str(path) .map_err(|e| JsError::new(&format!("Invalid derivation path: {}", e)))?; - + // Create master extended private key from seed let master_key = ExtendedPrivKey::new_master(net, &seed) .map_err(|e| JsError::new(&format!("Failed to create master key: {}", e)))?; - + // Derive the key at the specified path - let derived_key = master_key.derive_priv(&dashcore::secp256k1::Secp256k1::new(), &derivation_path) + let derived_key = master_key + .derive_priv(&dashcore::secp256k1::Secp256k1::new(), &derivation_path) .map_err(|e| JsError::new(&format!("Failed to derive key: {}", e)))?; - + // In v0.40-dev, ExtendedPrivKey might have a different structure // Create a PrivateKey from the derived key let private_key = dashcore::PrivateKey::new(derived_key.private_key, net); - + // Get public key let secp = dash_sdk::dpp::dashcore::secp256k1::Secp256k1::new(); let public_key = private_key.public_key(&secp); - + // Get address let address = dashcore::Address::p2pkh(&public_key, net); - + // Create a JavaScript object directly let obj = js_sys::Object::new(); - - js_sys::Reflect::set( - &obj, - &JsValue::from_str("path"), - &JsValue::from_str(path), - ).map_err(|_| JsError::new("Failed to set path property"))?; - + + js_sys::Reflect::set(&obj, &JsValue::from_str("path"), &JsValue::from_str(path)) + .map_err(|_| JsError::new("Failed to set path property"))?; + js_sys::Reflect::set( &obj, &JsValue::from_str("private_key_wif"), &JsValue::from_str(&private_key.to_wif()), - ).map_err(|_| JsError::new("Failed to set private_key_wif property"))?; - + ) + .map_err(|_| JsError::new("Failed to set private_key_wif property"))?; + js_sys::Reflect::set( &obj, &JsValue::from_str("private_key_hex"), &JsValue::from_str(&hex::encode(private_key.inner.secret_bytes())), - ).map_err(|_| JsError::new("Failed to set private_key_hex property"))?; - + ) + .map_err(|_| JsError::new("Failed to set private_key_hex property"))?; + js_sys::Reflect::set( &obj, &JsValue::from_str("public_key"), &JsValue::from_str(&hex::encode(public_key.to_bytes())), - ).map_err(|_| JsError::new("Failed to set public_key property"))?; - + ) + .map_err(|_| JsError::new("Failed to set public_key property"))?; + js_sys::Reflect::set( &obj, &JsValue::from_str("address"), &JsValue::from_str(&address.to_string()), - ).map_err(|_| JsError::new("Failed to set address property"))?; - + ) + .map_err(|_| JsError::new("Failed to set address property"))?; + js_sys::Reflect::set( &obj, &JsValue::from_str("network"), &JsValue::from_str(network), - ).map_err(|_| JsError::new("Failed to set network property"))?; - + ) + .map_err(|_| JsError::new("Failed to set network property"))?; + Ok(obj.into()) } @@ -384,39 +392,44 @@ pub fn derivation_path_dip9_testnet(feature_type: u32, account: u32, index: u32) pub fn derivation_path_dip13_mainnet(account: u32) -> JsValue { // DIP13 uses m/9'/5'/account' format (DIP13 uses purpose 9, not 13) let path_str = format!("m/{}'/{}'/{}'", DIP13_PURPOSE, DASH_COIN_TYPE, account); - + let obj = js_sys::Object::new(); - + js_sys::Reflect::set( &obj, &JsValue::from_str("path"), &JsValue::from_str(&path_str), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &obj, &JsValue::from_str("purpose"), &JsValue::from_f64(DIP13_PURPOSE as f64), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &obj, &JsValue::from_str("coin_type"), &JsValue::from_f64(DASH_COIN_TYPE as f64), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &obj, &JsValue::from_str("account"), &JsValue::from_f64(account as f64), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &obj, &JsValue::from_str("description"), &JsValue::from_str("DIP13 HD identity key path"), - ).unwrap(); - + ) + .unwrap(); + obj.into() } @@ -425,56 +438,61 @@ pub fn derivation_path_dip13_mainnet(account: u32) -> JsValue { pub fn derivation_path_dip13_testnet(account: u32) -> JsValue { // DIP13 uses m/9'/1'/account' format for testnet let path_str = format!("m/{}'/{}'/{}'", DIP13_PURPOSE, TESTNET_COIN_TYPE, account); - + let obj = js_sys::Object::new(); - + js_sys::Reflect::set( &obj, &JsValue::from_str("path"), &JsValue::from_str(&path_str), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &obj, &JsValue::from_str("purpose"), &JsValue::from_f64(DIP13_PURPOSE as f64), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &obj, &JsValue::from_str("coin_type"), &JsValue::from_f64(TESTNET_COIN_TYPE as f64), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &obj, &JsValue::from_str("account"), &JsValue::from_f64(account as f64), - ).unwrap(); - + ) + .unwrap(); + js_sys::Reflect::set( &obj, &JsValue::from_str("description"), &JsValue::from_str("DIP13 HD identity key path (testnet)"), - ).unwrap(); - + ) + .unwrap(); + obj.into() } /// Get child public key from extended public key #[wasm_bindgen] -pub fn derive_child_public_key( - xpub: &str, - index: u32, - hardened: bool, -) -> Result { +pub fn derive_child_public_key(xpub: &str, index: u32, hardened: bool) -> Result { if hardened { - return Err(JsError::new("Cannot derive hardened child from extended public key")); + return Err(JsError::new( + "Cannot derive hardened child from extended public key", + )); } // Disallow indices in the hardened range for non-hardened derivation if index >= 0x8000_0000 { - return Err(JsError::new("Index is in hardened range; use a value < 2^31")); + return Err(JsError::new( + "Index is in hardened range; use a value < 2^31", + )); } // Parse the extended public key diff --git a/packages/wasm-sdk/src/wallet/key_generation.rs b/packages/wasm-sdk/src/wallet/key_generation.rs index 2bc5613ad0..96cdffd87f 100644 --- a/packages/wasm-sdk/src/wallet/key_generation.rs +++ b/packages/wasm-sdk/src/wallet/key_generation.rs @@ -1,14 +1,13 @@ //! Key generation functionality for wallets -//! +//! //! Provides key generation and address derivation without full HD wallet support -use wasm_bindgen::prelude::*; -use serde::{Serialize, Deserialize}; -use dash_sdk::dpp::dashcore::{Network, PrivateKey, PublicKey, Address}; +use dash_sdk::dpp::dashcore::hashes::{sha256, Hash}; use dash_sdk::dpp::dashcore::secp256k1::{Secp256k1, SecretKey}; -use dash_sdk::dpp::dashcore::hashes::{Hash, sha256}; +use dash_sdk::dpp::dashcore::{Address, Network, PrivateKey, PublicKey}; +use serde::{Deserialize, Serialize}; use std::str::FromStr; -use dash_sdk::dpp::dashcore; +use wasm_bindgen::prelude::*; /// Key pair information #[derive(Debug, Clone, Serialize, Deserialize)] @@ -47,12 +46,16 @@ pub fn generate_key_pair(network: &str) -> Result { let secp = Secp256k1::new(); let secret_key = SecretKey::from_slice(&key_bytes) .map_err(|e| JsError::new(&format!("Invalid secret key: {}", e)))?; - let public_key = dash_sdk::dpp::dashcore::secp256k1::PublicKey::from_secret_key(&secp, &secret_key); + let public_key = + dash_sdk::dpp::dashcore::secp256k1::PublicKey::from_secret_key(&secp, &secret_key); let public_key_bytes = public_key.serialize(); // Get address - let address = Address::p2pkh(&PublicKey::from_slice(&public_key_bytes) - .map_err(|e| JsError::new(&format!("Failed to create public key: {}", e)))?, net); + let address = Address::p2pkh( + &PublicKey::from_slice(&public_key_bytes) + .map_err(|e| JsError::new(&format!("Failed to create public key: {}", e)))?, + net, + ); let key_pair = KeyPair { private_key_wif: private_key.to_wif(), @@ -96,13 +99,16 @@ pub fn key_pair_from_wif(private_key_wif: &str) -> Result { let secp = Secp256k1::new(); let secret_key = SecretKey::from_slice(&private_key.inner.secret_bytes()) .map_err(|e| JsError::new(&format!("Invalid secret key: {}", e)))?; - let public_key = dash_sdk::dpp::dashcore::secp256k1::PublicKey::from_secret_key(&secp, &secret_key); + let public_key = + dash_sdk::dpp::dashcore::secp256k1::PublicKey::from_secret_key(&secp, &secret_key); let public_key_bytes = public_key.serialize(); // Get address - let address = Address::p2pkh(&PublicKey::from_slice(&public_key_bytes) - .map_err(|e| JsError::new(&format!("Failed to create public key: {}", e)))?, - private_key.network); + let address = Address::p2pkh( + &PublicKey::from_slice(&public_key_bytes) + .map_err(|e| JsError::new(&format!("Failed to create public key: {}", e)))?, + private_key.network, + ); let key_pair = KeyPair { private_key_wif: private_key_wif.to_string(), @@ -120,7 +126,9 @@ pub fn key_pair_from_wif(private_key_wif: &str) -> Result { #[wasm_bindgen] pub fn key_pair_from_hex(private_key_hex: &str, network: &str) -> Result { if private_key_hex.len() != 64 { - return Err(JsError::new("Private key hex must be exactly 64 characters")); + return Err(JsError::new( + "Private key hex must be exactly 64 characters", + )); } let net = match network { @@ -129,8 +137,8 @@ pub fn key_pair_from_hex(private_key_hex: &str, network: &str) -> Result return Err(JsError::new("Invalid network. Use 'mainnet' or 'testnet'")), }; - let key_bytes = hex::decode(private_key_hex) - .map_err(|e| JsError::new(&format!("Invalid hex: {}", e)))?; + let key_bytes = + hex::decode(private_key_hex).map_err(|e| JsError::new(&format!("Invalid hex: {}", e)))?; let private_key = PrivateKey::from_slice(&key_bytes, net) .map_err(|e| JsError::new(&format!("Failed to create private key: {}", e)))?; @@ -147,8 +155,8 @@ pub fn pubkey_to_address(pubkey_hex: &str, network: &str) -> Result return Err(JsError::new("Invalid network. Use 'mainnet' or 'testnet'")), }; - let pubkey_bytes = hex::decode(pubkey_hex) - .map_err(|e| JsError::new(&format!("Invalid hex: {}", e)))?; + let pubkey_bytes = + hex::decode(pubkey_hex).map_err(|e| JsError::new(&format!("Invalid hex: {}", e)))?; let public_key = PublicKey::from_slice(&pubkey_bytes) .map_err(|e| JsError::new(&format!("Invalid public key: {}", e)))?; @@ -185,9 +193,10 @@ pub fn sign_message(message: &str, private_key_wif: &str) -> Result { @@ -28,7 +28,7 @@ fn main() { } } } - + // Also test if generate_in exists println!("\nChecking if generate_in method exists..."); // This will fail to compile if generate_in doesn't exist