diff --git a/packages/wasm-sdk/AI_REFERENCE.md b/packages/wasm-sdk/AI_REFERENCE.md index 93392ab5d6..afad0797b1 100644 --- a/packages/wasm-sdk/AI_REFERENCE.md +++ b/packages/wasm-sdk/AI_REFERENCE.md @@ -228,7 +228,7 @@ const result = await sdk.getDataContract("id"); Parameters: - `id` (text, required) - Data Contract ID - - Example: `GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec` + - Example: `HLY575cNazmc5824FxqaEMEBuzFeE4a98GDRNKbyJqCM` - `limit` (number, optional) - Limit - `offset` (number, optional) - Offset diff --git a/packages/wasm-sdk/docs.html b/packages/wasm-sdk/docs.html index d81d76f3b4..1c45c5888e 100644 --- a/packages/wasm-sdk/docs.html +++ b/packages/wasm-sdk/docs.html @@ -1035,15 +1035,23 @@
Parameters:
Example
return await window.wasmFunctions.get_identity_keys(sdk, '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk', 'all');
-
-
Example 2 - Get Specific Keys
-
return await window.wasmFunctions.get_identity_keys(sdk, '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk', 'specific', [0, 1, 2]);
- -
-
-
- -
+
+
+ +
+
Example 2 - Get Specific Keys
+
return await window.wasmFunctions.get_identity_keys(sdk, '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk', 'specific', [0, 1, 2]);
+ +
+
+ +
+
Example 3 - Search Keys by Purpose
+
return await window.wasmFunctions.get_identity_keys(sdk, '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk', 'search', undefined, '{"0": {"0": "current"}, "1": {"0": "current"}}');
+ +
+
+

Get Identities Contract Keys

Get keys for multiple identities related to a specific contract

@@ -1349,7 +1357,7 @@
Parameters:
Data Contract ID text (required) -
Example: GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec +
Example: HLY575cNazmc5824FxqaEMEBuzFeE4a98GDRNKbyJqCM
Limit @@ -1366,8 +1374,8 @@
Parameters:
Example
-
return await window.wasmFunctions.get_data_contract_history(sdk, 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', 10, 0);
- 🚧 Work in Progress +
return await window.wasmFunctions.get_data_contract_history(sdk, 'HLY575cNazmc5824FxqaEMEBuzFeE4a98GDRNKbyJqCM', 10, 0);
+
@@ -2023,8 +2031,8 @@
Parameters:
Example
-
return await window.wasmFunctions.get_token_perpetual_distribution_last_claim(sdk, '5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk', 'EETVvWgohFDKtbB3ejEzBcDRMNYkc9TtgXY6y8hzP3Ta');
- 🚧 Work in Progress +
return await window.wasmFunctions.get_token_perpetual_distribution_last_claim(sdk, '5RG84o6KsTaZudDqS8ytbaRB8QP4YYQ2uwzb6Hj8cfjX', 'HEv1AYWQfwCffXQgmuzmzyzUo9untRTmVr67n4e4PSWa');
+
diff --git a/packages/wasm-sdk/fixed_definitions.json b/packages/wasm-sdk/fixed_definitions.json index b286f55748..95507b0d31 100644 --- a/packages/wasm-sdk/fixed_definitions.json +++ b/packages/wasm-sdk/fixed_definitions.json @@ -285,7 +285,7 @@ "type": "text", "label": "Data Contract ID", "required": true, - "placeholder": "GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec" + "placeholder": "HLY575cNazmc5824FxqaEMEBuzFeE4a98GDRNKbyJqCM" }, { "name": "limit", diff --git a/packages/wasm-sdk/generate_docs.py b/packages/wasm-sdk/generate_docs.py index 343e7f2976..18ec5ae5b7 100755 --- a/packages/wasm-sdk/generate_docs.py +++ b/packages/wasm-sdk/generate_docs.py @@ -130,6 +130,7 @@ def generate_example_code(query_key, inputs): 'specialized_balance_id': 'AzaU7zqCT7X1kxh8yWxkT9PxAgNqWDu4Gz13emwcRyAT', 'contract_id': 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', 'data_contract_id': 'GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec', + 'data_contract_history_id': 'HLY575cNazmc5824FxqaEMEBuzFeE4a98GDRNKbyJqCM', 'token_contract_id': 'ALybvzfcCwMs7sinDwmtumw17NneuW7RgFtFHgjKmF3A', 'group_contract_id': '49PJEnNx7ReCitzkLdkDNr4s6RScGsnNexcdSZJ1ph5N', 'public_key_hash_unique': 'b7e904ce25ed97594e72f7af0e66f298031c1754', @@ -144,8 +145,8 @@ def generate_example_code(query_key, inputs): # Map input names to test values param_mapping = { - 'id': f"'{test_data['data_contract_id']}'" if 'getDataContract' in query_key else f"'{test_data['identity_id']}'", - 'identityId': f"'{test_data['specialized_balance_id']}'" if 'getPrefundedSpecializedBalance' in query_key else f"'{test_data['identity_id']}'", + 'id': f"'{test_data['data_contract_history_id']}'" if 'getDataContractHistory' in query_key else f"'{test_data['data_contract_id']}'" if 'getDataContract' in query_key else f"'{test_data['identity_id']}'", + 'identityId': f"'{test_data['specialized_balance_id']}'" if 'getPrefundedSpecializedBalance' in query_key else "'5RG84o6KsTaZudDqS8ytbaRB8QP4YYQ2uwzb6Hj8cfjX'" if 'getTokenPerpetualDistributionLastClaim' in query_key else f"'{test_data['identity_id']}'", 'ids': f"['{test_data['data_contract_id']}', '{test_data['token_contract_id']}']" if 'getDataContracts' in query_key else f"['{test_data['pro_tx_hash']}']" if 'Evonodes' in query_key else f"['{test_data['identity_id']}']", 'identitiesIds': f"['{test_data['identity_id']}']", 'identityIds': f"['{test_data['identity_id']}']", @@ -153,7 +154,7 @@ def generate_example_code(query_key, inputs): 'dataContractId': "'EETVvWgohFDKtbB3ejEzBcDRMNYkc9TtgXY6y8hzP3Ta'" if 'getTokenContractInfo' in query_key else f"'{test_data['data_contract_id']}'", 'publicKeyHash': f"'{test_data['public_key_hash_unique']}'" if 'ByPublicKeyHash' in query_key and 'NonUnique' not in query_key else f"'{test_data['public_key_hash_non_unique']}'", 'startProTxHash': f"'{test_data['pro_tx_hash']}'", - 'tokenId': "'EETVvWgohFDKtbB3ejEzBcDRMNYkc9TtgXY6y8hzP3Ta'" if 'getTokenPerpetualDistributionLastClaim' in query_key else f"'{test_data['token_id']}'", + 'tokenId': "'HEv1AYWQfwCffXQgmuzmzyzUo9untRTmVr67n4e4PSWa'" if 'getTokenPerpetualDistributionLastClaim' in query_key else f"'{test_data['token_id']}'", 'tokenIds': f"['{test_data['token_id']}', 'H7FRpZJqZK933r9CzZMsCuf1BM34NT5P2wSJyjDkprqy']" if 'getTokenStatuses' in query_key else "['H7FRpZJqZK933r9CzZMsCuf1BM34NT5P2wSJyjDkprqy']" if 'getTokenDirectPurchasePrices' in query_key else f"['{test_data['token_id']}']", 'documentType': f"'{test_data['document_type']}'", 'documentId': f"'{test_data['document_id']}'", @@ -206,6 +207,10 @@ def generate_example_code(query_key, inputs): elif query_key == 'getGroupActions': # getGroupActions expects: sdk, contractId, groupContractPosition, status, startAtInfo (object or null), count params = [f"'{test_data['group_contract_id']}'", "0", "'ACTIVE'", "null", "100"] + elif query_key == 'getDataContractHistory': + # getDataContractHistory expects: sdk, id, limit, offset, startAtMs + # Use the specific contract ID for getDataContractHistory examples + params = ["'HLY575cNazmc5824FxqaEMEBuzFeE4a98GDRNKbyJqCM'", "10", "0"] else: # Generate parameters normally params = [] @@ -399,17 +404,30 @@ def generate_operation_entry(operation_key, operation, type_prefix): html_content += '

This is an internal query used to wait for and retrieve the result of a previously submitted state transition. It requires a valid state transition hash from a prior operation.

' else: html_content += f' ' - if operation_key in ['getPathElements', 'getDataContractHistory', 'getContestedResourceVotersForIdentity', 'getTokenPerpetualDistributionLastClaim']: + if operation_key in ['getPathElements', 'getContestedResourceVotersForIdentity']: html_content += ' 🚧 Work in Progress' # Add special examples and info if operation_key == 'getIdentityKeys': - html_content += '''\n
-
Example 2 - Get Specific Keys
-
return await window.wasmFunctions.get_identity_keys(sdk, \'5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk\', \'specific\', [0, 1, 2]);
- -
-
''' + html_content += f''' +
+
+ +
+
Example 2 - Get Specific Keys
+
return await window.wasmFunctions.get_identity_keys(sdk, \'5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk\', \'specific\', [0, 1, 2]);
+ +
+
+ +
+
Example 3 - Search Keys by Purpose
+
return await window.wasmFunctions.get_identity_keys(sdk, \'5DbLwAxGBzUzo81VewMUwn4b5P4bpv9FNFybi25XB5Bk\', \'search\', undefined, \'{{"0": {{"0": "current"}}, "1": {{"0": "current"}}}}\');
+ +
+
+ ''' + return html_content elif operation_key == 'getPathElements': html_content += generate_path_elements_info() diff --git a/packages/wasm-sdk/src/context_provider.rs b/packages/wasm-sdk/src/context_provider.rs index ca2e383b47..be6dac6108 100644 --- a/packages/wasm-sdk/src/context_provider.rs +++ b/packages/wasm-sdk/src/context_provider.rs @@ -45,9 +45,21 @@ impl ContextProvider for WasmContext { fn get_token_configuration( &self, - _token_id: &Identifier, + token_id: &Identifier, ) -> Result, ContextProviderError> { - // Return None for now - this means the token config will be fetched from the network + // 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/queries/data_contract.rs b/packages/wasm-sdk/src/queries/data_contract.rs index 5904269d06..5bf023abed 100644 --- a/packages/wasm-sdk/src/queries/data_contract.rs +++ b/packages/wasm-sdk/src/queries/data_contract.rs @@ -59,7 +59,7 @@ pub async fn data_contract_fetch_with_proof_info( #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "camelCase")] struct DataContractHistoryResponse { - versions: BTreeMap, + versions: BTreeMap, } #[wasm_bindgen] @@ -94,7 +94,7 @@ pub async fn get_data_contract_history( if let Some(history) = history_result { for (revision, contract) in history { - versions.insert(revision, contract.to_json(platform_version)?); + versions.insert(revision.to_string(), contract.to_json(platform_version)?); } } @@ -183,7 +183,7 @@ pub async fn get_data_contract_history_with_proof_info( if let Some(history) = history_result { for (revision, contract) in history { - versions.insert(revision, contract.to_json(platform_version)?); + versions.insert(revision.to_string(), contract.to_json(platform_version)?); } } diff --git a/packages/wasm-sdk/src/queries/identity.rs b/packages/wasm-sdk/src/queries/identity.rs index fd6ca28c14..58761e4dd6 100644 --- a/packages/wasm-sdk/src/queries/identity.rs +++ b/packages/wasm-sdk/src/queries/identity.rs @@ -157,9 +157,9 @@ pub async fn get_identity_keys( let request = GetIdentityKeysRequest { version: Some(Version::V0(GetIdentityKeysRequestV0 { identity_id: id.to_vec(), - prove: true, - limit: limit.map(|l| l.into()), - offset: offset.map(|o| o.into()), + prove: false, + 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, @@ -254,9 +254,9 @@ pub async fn get_identity_keys( let request = GetIdentityKeysRequest { version: Some(Version::V0(GetIdentityKeysRequestV0 { identity_id: id.to_vec(), - prove: true, - limit: limit.map(|l| l.into()), - offset: offset.map(|o| o.into()), + prove: false, + 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, diff --git a/packages/wasm-sdk/src/queries/token.rs b/packages/wasm-sdk/src/queries/token.rs index bc893957f5..d89ed15eae 100644 --- a/packages/wasm-sdk/src/queries/token.rs +++ b/packages/wasm-sdk/src/queries/token.rs @@ -541,41 +541,123 @@ pub async fn get_token_perpetual_distribution_last_claim( dash_sdk::dpp::platform_value::string_encoding::Encoding::Base58, )?; - // Create query - let query = TokenLastClaimQuery { - token_id: token_identifier, - identity_id: identity_identifier, + // 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 + } + }; + 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 + })), }; - // Fetch last claim info - let claim_result = RewardDistributionMoment::fetch(sdk.as_ref(), query) + // Execute the gRPC request + 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)))?; - 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, - // we'll return the moment value and type - let (last_claim_timestamp_ms, last_claim_block_height) = match moment { - dash_sdk::dpp::data_contract::associated_token::token_perpetual_distribution::reward_distribution_moment::RewardDistributionMoment::BlockBasedMoment(height) => { - (0, height) // No timestamp available for block-based - }, - dash_sdk::dpp::data_contract::associated_token::token_perpetual_distribution::reward_distribution_moment::RewardDistributionMoment::TimeBasedMoment(timestamp) => { - (timestamp, 0) // No block height available for time-based - }, - dash_sdk::dpp::data_contract::associated_token::token_perpetual_distribution::reward_distribution_moment::RewardDistributionMoment::EpochBasedMoment(epoch) => { - (0, epoch as u64) // Convert epoch to u64, no timestamp available - }, - }; - + // 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)) => { + match v0.result { + Some(dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::get_token_perpetual_distribution_last_claim_response_v0::Result::LastClaim(claim)) => { + // Convert gRPC response to RewardDistributionMoment equivalent + match claim.paid_at { + Some(dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::get_token_perpetual_distribution_last_claim_response_v0::last_claim_info::PaidAt::TimestampMs(timestamp)) => { + Some((timestamp, 0)) // (timestamp_ms, block_height) + }, + Some(dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::get_token_perpetual_distribution_last_claim_response_v0::last_claim_info::PaidAt::BlockHeight(height)) => { + Some((0, height)) // (timestamp_ms, block_height) + }, + Some(dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::get_token_perpetual_distribution_last_claim_response_v0::last_claim_info::PaidAt::Epoch(epoch)) => { + Some((0, epoch as u64)) // (timestamp_ms, block_height) + }, + Some(dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::get_token_perpetual_distribution_last_claim_response_v0::last_claim_info::PaidAt::RawBytes(bytes)) => { + // Raw bytes format specification (confirmed via server trace logs): + // - Total length: 8 bytes (big-endian encoding) + // - Bytes 0-3: Timestamp as u32 (seconds since Unix epoch, 0 = no timestamp recorded) + // - Bytes 4-7: Block height as u32 (Dash blockchain block number) + // + // Validation ranges: + // - Timestamp: 0 (unset) or >= 1609459200 (Jan 1, 2021 00:00:00 UTC, before Dash Platform mainnet) + // - Block height: 0 (invalid) or >= 1 (valid blockchain height) + 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()); + 0 // Use 0 for invalid timestamps + } 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()); + 1 // Use minimum valid block height + } 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()-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()); + 1 // Use minimum valid block height + } 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()); + Some((0, 0)) + } + }, + None => { + None // No paid_at info + } + } + }, + Some(dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::get_token_perpetual_distribution_last_claim_response_v0::Result::Proof(_)) => { + return Err(JsError::new("Received proof instead of data - this should not happen with prove: false")) + }, + None => None, // No claim found + } + }, + None => { + return Err(JsError::new("Invalid response version")) + } + }; + + if let Some((timestamp_ms, block_height)) = claim_result { let response = LastClaimResponse { - last_claim_timestamp_ms, - last_claim_block_height, + 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) + 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) diff --git a/packages/wasm-sdk/src/sdk.rs b/packages/wasm-sdk/src/sdk.rs index 5b326a02a1..a25ce468f5 100644 --- a/packages/wasm-sdk/src/sdk.rs +++ b/packages/wasm-sdk/src/sdk.rs @@ -55,6 +55,11 @@ impl WasmSdk { 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 diff --git a/packages/wasm-sdk/update_inputs.py b/packages/wasm-sdk/update_inputs.py index 6f8d115760..e6894a31c0 100644 --- a/packages/wasm-sdk/update_inputs.py +++ b/packages/wasm-sdk/update_inputs.py @@ -64,7 +64,7 @@ {"name": "id", "type": "text", "label": "Data Contract ID", "required": True, "placeholder": "GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec"} ], "getDataContractHistory": [ - {"name": "id", "type": "text", "label": "Data Contract ID", "required": True, "placeholder": "GWRSAVFMjXx8HpQFaNJMqBV7MBgMK4br5UESsB4S31Ec"}, + {"name": "id", "type": "text", "label": "Data Contract ID", "required": True, "placeholder": "HLY575cNazmc5824FxqaEMEBuzFeE4a98GDRNKbyJqCM"}, {"name": "limit", "type": "number", "label": "Limit", "required": False}, {"name": "offset", "type": "number", "label": "Offset", "required": False} ],