From 35f19f37c33b889025e1ad70ce6d76a19d9cc9c6 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Thu, 27 Nov 2025 10:40:20 +0700 Subject: [PATCH 01/12] fix: non proved `get_token_perpetual_distribution_last_claim` --- packages/wasm-sdk/QUERY_REVIEW.md | 18 ++ packages/wasm-sdk/src/context_provider.rs | 6 +- packages/wasm-sdk/src/queries/token.rs | 240 ++++++++-------------- 3 files changed, 105 insertions(+), 159 deletions(-) create mode 100644 packages/wasm-sdk/QUERY_REVIEW.md diff --git a/packages/wasm-sdk/QUERY_REVIEW.md b/packages/wasm-sdk/QUERY_REVIEW.md new file mode 100644 index 00000000000..5268a7f11d0 --- /dev/null +++ b/packages/wasm-sdk/QUERY_REVIEW.md @@ -0,0 +1,18 @@ +# wasm-sdk query review (fetch/fetch_many, proofs, efficiency) + +Context: review of query implementations to verify we use fetch/fetch_many correctly, avoid inefficient patterns, and handle proofs. + +## Findings + +- `src/queries/token.rs:getTokenPerpetualDistributionLastClaim` + - Uses a direct gRPC call with `prove: false` to avoid context/provider issues. Result is unverified and may diverge from the proof-backed variant. Should either default to the proofable path or document that this endpoint is best-effort/non-verified. +- `src/queries/protocol.rs:getProtocolVersionUpgradeVoteStatus` (and proof variant TODO) + - Uses `fetch_votes` without a proof-capable path; proof variant remains unimplemented. Protocol vote status cannot currently be verified. +- `src/queries/epoch.rs:getEvonodesProposedEpochBlocksBy*WithProofInfo` + - Proof-supporting variants are stubbed (return errors). Evonode proposed block counts cannot be fetched with proofs yet. + +## Suggested follow-ups + +1) Add/enable batch identity key queries (or parallelize) and return combined proofs for `getIdentitiesContractKeys*`. +2) Decide whether `getTokenPerpetualDistributionLastClaim` should default to the proof-backed path or be clearly marked as best-effort. +3) Implement proof-capable variants for protocol vote status and evonode proposed block counts once underlying fetch/proof traits land. diff --git a/packages/wasm-sdk/src/context_provider.rs b/packages/wasm-sdk/src/context_provider.rs index aaea30430c8..924caa7e567 100644 --- a/packages/wasm-sdk/src/context_provider.rs +++ b/packages/wasm-sdk/src/context_provider.rs @@ -106,7 +106,8 @@ impl WasmTrustedContext { std::num::NonZeroUsize::new(100).unwrap(), ) .map_err(|e| ContextProviderError::Generic(e.to_string()))? - .with_refetch_if_not_found(false); // Disable refetch since we'll pre-fetch + // Enable refetch so token configs (and other context) are pulled on demand, same as rs-sdk + .with_refetch_if_not_found(true); Ok(Self { inner: std::sync::Arc::new(inner), @@ -120,7 +121,8 @@ impl WasmTrustedContext { std::num::NonZeroUsize::new(100).unwrap(), ) .map_err(|e| ContextProviderError::Generic(e.to_string()))? - .with_refetch_if_not_found(false); // Disable refetch since we'll pre-fetch + // Enable refetch so token configs (and other context) are pulled on demand, same as rs-sdk + .with_refetch_if_not_found(true); Ok(Self { inner: std::sync::Arc::new(inner), diff --git a/packages/wasm-sdk/src/queries/token.rs b/packages/wasm-sdk/src/queries/token.rs index 3c06aac02a2..96ef55f1cfb 100644 --- a/packages/wasm-sdk/src/queries/token.rs +++ b/packages/wasm-sdk/src/queries/token.rs @@ -4,10 +4,12 @@ use crate::queries::ProofMetadataResponseWasm; use crate::sdk::WasmSdk; use dash_sdk::dpp::balances::credits::TokenAmount; use dash_sdk::dpp::tokens::calculate_token_id; +use dash_sdk::dpp::data_contract::associated_token::token_perpetual_distribution::reward_distribution_moment::RewardDistributionMoment; use dash_sdk::dpp::tokens::info::IdentityTokenInfo; use dash_sdk::dpp::tokens::status::TokenStatus; use dash_sdk::dpp::tokens::token_pricing_schedule::TokenPricingSchedule; -use dash_sdk::platform::{FetchMany, Identifier}; +use dash_sdk::platform::query::TokenLastClaimQuery; +use dash_sdk::platform::{Fetch, FetchMany, Identifier}; use js_sys::{BigInt, Map}; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsValue; @@ -480,172 +482,96 @@ impl WasmSdk { let identity_identifier = identifier_from_js(&identity_id, "identity ID")?; let token_identifier = identifier_from_js(&token_id, "token ID")?; - // Use direct gRPC request instead of high-level SDK fetch to avoid proof verification issues - use dapi_grpc::platform::v0::{ - get_token_perpetual_distribution_last_claim_request::{ - 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 - }, - )), + // Create query and fetch via SDK (proofless variant) + let query = TokenLastClaimQuery { + token_id: token_identifier, + identity_id: identity_identifier, }; - // Execute the gRPC request - let response = self - .inner_sdk() - .execute(request, rs_dapi_client::RequestSettings::default()) - .await - .map_err(|e| { - WasmSdkError::generic(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, - ), - ) => { - 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, - ), - ) => { - 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)), - 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)), - 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)), + // Try proof-backed fetch first; fall back to direct gRPC with prove=false if context lacks token config + let claim_result = match RewardDistributionMoment::fetch(self.as_ref(), query.clone()).await + { + Ok(res) => res, + Err(err) => { + // If proof verification failed due to missing token distribution type, retry without proofs + if err + .to_string() + .contains("Token distribution type not found with get_token_distribution") + { + use dapi_grpc::platform::v0::{ + get_token_perpetual_distribution_last_claim_request::{ + GetTokenPerpetualDistributionLastClaimRequestV0, Version, + }, + get_token_perpetual_distribution_last_claim_response::get_token_perpetual_distribution_last_claim_response_v0::last_claim_info::PaidAt, + GetTokenPerpetualDistributionLastClaimRequest, + }; + use rs_dapi_client::DapiRequestExecutor; + + let request = GetTokenPerpetualDistributionLastClaimRequest { + version: Some(Version::V0( + GetTokenPerpetualDistributionLastClaimRequestV0 { + token_id: query.token_id.to_vec(), + identity_id: query.identity_id.to_vec(), + contract_info: None, + prove: false, + }, + )), + }; + + let response = self + .inner_sdk() + .execute(request, rs_dapi_client::RequestSettings::default()) + .await + .map_err(|e| { + WasmSdkError::generic(format!( + "Failed to fetch token perpetual distribution last claim (fallback): {}", + e + )) + })?; + + let claim = 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::last_claim_info::PaidAt::RawBytes( - bytes, + dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::get_token_perpetual_distribution_last_claim_response_v0::Result::LastClaim( + claim, ), - ) => { - // 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 - { - tracing::warn!( - target = "wasm_sdk", timestamp, - "Invalid timestamp in raw bytes (too early)" - ); - 0 // Use 0 for invalid timestamps - } else { - timestamp - }; - - // Validate block height: must be a positive value - let validated_block_height = if block_height == 0 { - tracing::warn!( - target = "wasm_sdk", - "Invalid block height in raw bytes: 0 (genesis block not expected)" - ); - 1 // Use minimum valid block height - } else { - block_height - }; - - Some((validated_timestamp * 1000, validated_block_height)) - } 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 { - tracing::warn!( - target = "wasm_sdk", - "Invalid block height in fallback parsing: 0" - ); - 1 // Use minimum valid block height - } else { - block_height - }; - - Some((0, validated_block_height)) - } else { - tracing::warn!( - target = "wasm_sdk", len = bytes.len(), - "Insufficient raw bytes length (expected 8 or 4)" - ); - Some((0, 0)) - } - } - None => None, // No paid_at info + ) => claim.paid_at, + _ => None, + }, + _ => None, + }; + + match claim { + Some(PaidAt::TimestampMs(ts)) => Some(RewardDistributionMoment::TimeBasedMoment(ts)), + Some(PaidAt::BlockHeight(height)) => { + Some(RewardDistributionMoment::BlockBasedMoment(height)) } + Some(PaidAt::Epoch(epoch)) => { + Some(RewardDistributionMoment::EpochBasedMoment(epoch as u64)) + } + _ => None, } - Some( - dapi_grpc::platform::v0::get_token_perpetual_distribution_last_claim_response::get_token_perpetual_distribution_last_claim_response_v0::Result::Proof( - _, - ), - ) => { - return Err( - WasmSdkError::generic( - "Received proof instead of data - this should not happen with prove: false", - ), - ); - } - None => None, // No claim found + } else { + return Err(err.into()); } } - None => return Err(WasmSdkError::generic("Invalid response version")), }; - Ok(claim_result.map(|(timestamp_ms, block_height)| { - TokenLastClaimWasm::new(timestamp_ms, block_height) - })) + let data = claim_result.map(|moment| { + let (last_claim_timestamp_ms, last_claim_block_height) = match moment { + RewardDistributionMoment::BlockBasedMoment(height) => (0, height), + RewardDistributionMoment::TimeBasedMoment(timestamp) => (timestamp, 0), + RewardDistributionMoment::EpochBasedMoment(epoch) => (0, epoch as u64), + }; + + TokenLastClaimWasm::new(last_claim_timestamp_ms, last_claim_block_height) + }); + + Ok(data) } #[wasm_bindgen(js_name = "getTokenTotalSupply")] From 2140769fc31a6a4f217345171ad94a8ccff649a7 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Sat, 27 Dec 2025 09:26:40 +0700 Subject: [PATCH 02/12] chore: prefetch token disttibution information --- .../src/provider.rs | 28 ++++ packages/wasm-sdk/QUERY_REVIEW.md | 13 +- packages/wasm-sdk/src/context_provider.rs | 9 + packages/wasm-sdk/src/queries/token.rs | 155 +++++++++--------- 4 files changed, 125 insertions(+), 80 deletions(-) diff --git a/packages/rs-sdk-trusted-context-provider/src/provider.rs b/packages/rs-sdk-trusted-context-provider/src/provider.rs index ce80912a389..5b158bd6b04 100644 --- a/packages/rs-sdk-trusted-context-provider/src/provider.rs +++ b/packages/rs-sdk-trusted-context-provider/src/provider.rs @@ -61,6 +61,9 @@ pub struct TrustedHttpContextProvider { /// Known contracts cache - contracts that are pre-loaded and can be served immediately known_contracts: Arc>>>, + /// Known token configurations cache - token configs that are pre-loaded for proof verification + known_token_configurations: Arc>>, + /// Whether to refetch quorums if not found in cache refetch_if_not_found: bool, } @@ -151,6 +154,7 @@ impl TrustedHttpContextProvider { last_previous_quorums: Arc::new(ArcSwap::new(Arc::new(None))), fallback_provider: None, known_contracts: Arc::new(Mutex::new(HashMap::new())), + known_token_configurations: Arc::new(Mutex::new(HashMap::new())), refetch_if_not_found: true, }) } @@ -194,6 +198,23 @@ impl TrustedHttpContextProvider { } } + /// Add a token configuration to the known token configurations cache + pub fn add_known_token_configuration(&self, token_id: Identifier, config: TokenConfiguration) { + let mut known = self.known_token_configurations.lock().unwrap(); + known.insert(token_id, config); + } + + /// Add multiple token configurations to the known token configurations cache + pub fn add_known_token_configurations( + &self, + configs: Vec<(Identifier, TokenConfiguration)>, + ) { + let mut known = self.known_token_configurations.lock().unwrap(); + for (token_id, config) in configs { + known.insert(token_id, config); + } + } + /// Update the quorum caches by fetching current and previous quorums pub async fn update_quorum_caches(&self) -> Result<(), TrustedContextProviderError> { // Fetch current quorums @@ -669,6 +690,13 @@ impl ContextProvider for TrustedHttpContextProvider { &self, token_id: &Identifier, ) -> Result, ContextProviderError> { + // First check known token configurations cache + let known = self.known_token_configurations.lock().unwrap(); + if let Some(config) = known.get(token_id) { + return Ok(Some(config.clone())); + } + drop(known); + // Delegate to fallback provider if available if let Some(ref provider) = self.fallback_provider { provider.get_token_configuration(token_id) diff --git a/packages/wasm-sdk/QUERY_REVIEW.md b/packages/wasm-sdk/QUERY_REVIEW.md index 5268a7f11d0..44690b97a3e 100644 --- a/packages/wasm-sdk/QUERY_REVIEW.md +++ b/packages/wasm-sdk/QUERY_REVIEW.md @@ -4,15 +4,14 @@ Context: review of query implementations to verify we use fetch/fetch_many corre ## Findings -- `src/queries/token.rs:getTokenPerpetualDistributionLastClaim` - - Uses a direct gRPC call with `prove: false` to avoid context/provider issues. Result is unverified and may diverge from the proof-backed variant. Should either default to the proofable path or document that this endpoint is best-effort/non-verified. -- `src/queries/protocol.rs:getProtocolVersionUpgradeVoteStatus` (and proof variant TODO) +- ✅ `src/queries/token.rs:getTokenPerpetualDistributionLastClaim` + - **FIXED**: Now prefetches token configuration before making the proof-verified query. The token configuration is fetched via `TokenContractInfo` and `DataContract` queries, extracted, and cached in the trusted context provider before calling `RewardDistributionMoment::fetch()`. +- `src/queries/protocol.rs:getProtocolVersionUpgradeVoteStatus` (and proof variant TODO) - Uses `fetch_votes` without a proof-capable path; proof variant remains unimplemented. Protocol vote status cannot currently be verified. -- `src/queries/epoch.rs:getEvonodesProposedEpochBlocksBy*WithProofInfo` +- `src/queries/epoch.rs:getEvonodesProposedEpochBlocksBy*WithProofInfo` - Proof-supporting variants are stubbed (return errors). Evonode proposed block counts cannot be fetched with proofs yet. ## Suggested follow-ups -1) Add/enable batch identity key queries (or parallelize) and return combined proofs for `getIdentitiesContractKeys*`. -2) Decide whether `getTokenPerpetualDistributionLastClaim` should default to the proof-backed path or be clearly marked as best-effort. -3) Implement proof-capable variants for protocol vote status and evonode proposed block counts once underlying fetch/proof traits land. +1) Add/enable batch identity key queries (or parallelize) and return combined proofs for `getIdentitiesContractKeys*`. +2) Implement proof-capable variants for protocol vote status and evonode proposed block counts once underlying fetch/proof traits land. diff --git a/packages/wasm-sdk/src/context_provider.rs b/packages/wasm-sdk/src/context_provider.rs index 924caa7e567..10863528bcd 100644 --- a/packages/wasm-sdk/src/context_provider.rs +++ b/packages/wasm-sdk/src/context_provider.rs @@ -140,4 +140,13 @@ impl WasmTrustedContext { pub fn add_known_contract(&self, contract: DataContract) { self.inner.add_known_contract(contract); } + + /// Add a token configuration to the known token configurations cache + pub fn add_known_token_configuration( + &self, + token_id: Identifier, + config: TokenConfiguration, + ) { + self.inner.add_known_token_configuration(token_id, config); + } } diff --git a/packages/wasm-sdk/src/queries/token.rs b/packages/wasm-sdk/src/queries/token.rs index 96ef55f1cfb..be345dfebf1 100644 --- a/packages/wasm-sdk/src/queries/token.rs +++ b/packages/wasm-sdk/src/queries/token.rs @@ -482,84 +482,17 @@ impl WasmSdk { let identity_identifier = identifier_from_js(&identity_id, "identity ID")?; let token_identifier = identifier_from_js(&token_id, "token ID")?; - // Create query and fetch via SDK (proofless variant) + // Prefetch token configuration and add to context provider cache + // This is required for proof verification to work + self.prefetch_token_configuration(token_identifier).await?; + + // Create query and fetch via SDK with proof verification let query = TokenLastClaimQuery { token_id: token_identifier, identity_id: identity_identifier, }; - // Try proof-backed fetch first; fall back to direct gRPC with prove=false if context lacks token config - let claim_result = match RewardDistributionMoment::fetch(self.as_ref(), query.clone()).await - { - Ok(res) => res, - Err(err) => { - // If proof verification failed due to missing token distribution type, retry without proofs - if err - .to_string() - .contains("Token distribution type not found with get_token_distribution") - { - use dapi_grpc::platform::v0::{ - get_token_perpetual_distribution_last_claim_request::{ - GetTokenPerpetualDistributionLastClaimRequestV0, Version, - }, - get_token_perpetual_distribution_last_claim_response::get_token_perpetual_distribution_last_claim_response_v0::last_claim_info::PaidAt, - GetTokenPerpetualDistributionLastClaimRequest, - }; - use rs_dapi_client::DapiRequestExecutor; - - let request = GetTokenPerpetualDistributionLastClaimRequest { - version: Some(Version::V0( - GetTokenPerpetualDistributionLastClaimRequestV0 { - token_id: query.token_id.to_vec(), - identity_id: query.identity_id.to_vec(), - contract_info: None, - prove: false, - }, - )), - }; - - let response = self - .inner_sdk() - .execute(request, rs_dapi_client::RequestSettings::default()) - .await - .map_err(|e| { - WasmSdkError::generic(format!( - "Failed to fetch token perpetual distribution last claim (fallback): {}", - e - )) - })?; - - let claim = 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, - ), - ) => claim.paid_at, - _ => None, - }, - _ => None, - }; - - match claim { - Some(PaidAt::TimestampMs(ts)) => Some(RewardDistributionMoment::TimeBasedMoment(ts)), - Some(PaidAt::BlockHeight(height)) => { - Some(RewardDistributionMoment::BlockBasedMoment(height)) - } - Some(PaidAt::Epoch(epoch)) => { - Some(RewardDistributionMoment::EpochBasedMoment(epoch as u64)) - } - _ => None, - } - } else { - return Err(err.into()); - } - } - }; + let claim_result = RewardDistributionMoment::fetch(self.as_ref(), query).await?; let data = claim_result.map(|moment| { let (last_claim_timestamp_ms, last_claim_block_height) = match moment { @@ -922,6 +855,10 @@ impl WasmSdk { let identity_identifier = identifier_from_js(&identity_id, "identity ID")?; let token_identifier = identifier_from_js(&token_id, "token ID")?; + // Prefetch token configuration and add to context provider cache + // This is required for proof verification to work + self.prefetch_token_configuration(token_identifier).await?; + // Create query let query = TokenLastClaimQuery { token_id: token_identifier, @@ -964,3 +901,75 @@ impl WasmSdk { )) } } + +// Internal helper methods for token queries +impl WasmSdk { + /// Prefetch token configuration and add it to the context provider cache. + /// This is required for proof verification of token-related queries. + pub(crate) async fn prefetch_token_configuration( + &self, + token_id: Identifier, + ) -> Result<(), WasmSdkError> { + use crate::sdk::{MAINNET_TRUSTED_CONTEXT, TESTNET_TRUSTED_CONTEXT}; + use dash_sdk::dpp::data_contract::accessors::v1::DataContractV1Getters; + use dash_sdk::dpp::dashcore::Network; + use dash_sdk::dpp::tokens::contract_info::v0::TokenContractInfoV0Accessors; + use dash_sdk::dpp::tokens::contract_info::TokenContractInfo; + use dash_sdk::platform::DataContract; + + // Step 1: Fetch TokenContractInfo to get contract_id and position + let token_contract_info = TokenContractInfo::fetch(self.as_ref(), token_id) + .await? + .ok_or_else(|| { + WasmSdkError::generic(format!( + "Token contract info not found for token ID: {}", + token_id + )) + })?; + + let contract_id = token_contract_info.contract_id(); + let token_position = token_contract_info.token_contract_position(); + + // Step 2: Fetch the DataContract + let data_contract = DataContract::fetch(self.as_ref(), contract_id) + .await? + .ok_or_else(|| { + WasmSdkError::generic(format!( + "Data contract not found for contract ID: {}", + contract_id + )) + })?; + + // Step 3: Extract the TokenConfiguration from the contract + let token_configuration = data_contract + .expected_token_configuration(token_position) + .map_err(|e| { + WasmSdkError::generic(format!( + "Failed to get token configuration at position {}: {}", + token_position, e + )) + })? + .clone(); + + // Step 4: Add the token configuration to the trusted context cache + let network = self.network(); + match network { + Network::Dash => { + if let Some(ref ctx) = *MAINNET_TRUSTED_CONTEXT.lock().unwrap() { + ctx.add_known_token_configuration(token_id, token_configuration); + } + } + Network::Testnet => { + if let Some(ref ctx) = *TESTNET_TRUSTED_CONTEXT.lock().unwrap() { + ctx.add_known_token_configuration(token_id, token_configuration); + } + } + _ => { + // For other networks, we can't cache the token configuration + // The proof verification may still work if the context provider has the info + } + } + + Ok(()) + } +} From 3f70f715a199ca933a61711cb426b2c49793e1af Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Sat, 27 Dec 2025 18:15:43 +0700 Subject: [PATCH 03/12] feat(sdk): add proof support for evonode proposed blocks and protocol upgrade state queries --- .../tests/functional/epoch.spec.mjs | 16 ++++ .../tests/functional/protocol.spec.mjs | 16 ++++ packages/wasm-sdk/QUERY_REVIEW.md | 17 ---- packages/wasm-sdk/src/queries/epoch.rs | 93 ++++++++++++++++--- packages/wasm-sdk/src/queries/protocol.rs | 71 ++++++++++++-- .../tests/functional/epochs-blocks.spec.mjs | 20 ++++ .../tests/functional/protocol.spec.mjs | 17 ++++ 7 files changed, 211 insertions(+), 39 deletions(-) delete mode 100644 packages/wasm-sdk/QUERY_REVIEW.md diff --git a/packages/js-evo-sdk/tests/functional/epoch.spec.mjs b/packages/js-evo-sdk/tests/functional/epoch.spec.mjs index f4d6ab0e6c0..c91db04719b 100644 --- a/packages/js-evo-sdk/tests/functional/epoch.spec.mjs +++ b/packages/js-evo-sdk/tests/functional/epoch.spec.mjs @@ -24,4 +24,20 @@ describe('Epoch', function epochSuite() { const res = await sdk.epoch.evonodesProposedBlocksByRange({ epoch: TEST_IDS.epoch, limit: 5 }); expect(res).to.exist(); }); + + it('evonodesProposedBlocksByIdsWithProof() returns results with proof', async () => { + const res = await sdk.epoch.evonodesProposedBlocksByIdsWithProof(TEST_IDS.epoch, [TEST_IDS.proTxHash]); + expect(res).to.exist(); + expect(res.data).to.be.instanceOf(Map); + expect(res.proof).to.exist(); + expect(res.metadata).to.exist(); + }); + + it('evonodesProposedBlocksByRangeWithProof() returns results with proof', async () => { + const res = await sdk.epoch.evonodesProposedBlocksByRangeWithProof({ epoch: TEST_IDS.epoch, limit: 5 }); + expect(res).to.exist(); + expect(res.data).to.be.instanceOf(Map); + expect(res.proof).to.exist(); + expect(res.metadata).to.exist(); + }); }); diff --git a/packages/js-evo-sdk/tests/functional/protocol.spec.mjs b/packages/js-evo-sdk/tests/functional/protocol.spec.mjs index 3db4f508342..c89cf6466d9 100644 --- a/packages/js-evo-sdk/tests/functional/protocol.spec.mjs +++ b/packages/js-evo-sdk/tests/functional/protocol.spec.mjs @@ -15,8 +15,24 @@ describe('Protocol', function protocolSuite() { expect(res).to.exist(); }); + it('versionUpgradeStateWithProof() returns state with proof', async () => { + const res = await sdk.protocol.versionUpgradeStateWithProof(); + expect(res).to.exist(); + expect(res.data).to.exist(); + expect(res.proof).to.exist(); + expect(res.metadata).to.exist(); + }); + it('versionUpgradeVoteStatus() returns vote window status', async () => { const res = await sdk.protocol.versionUpgradeVoteStatus(TEST_IDS.proTxHash, 1); expect(res).to.exist(); }); + + it('versionUpgradeVoteStatusWithProof() returns vote status with proof', async () => { + const res = await sdk.protocol.versionUpgradeVoteStatusWithProof(TEST_IDS.proTxHash, 50); + expect(res).to.exist(); + expect(res.data).to.be.instanceOf(Map); + expect(res.proof).to.exist(); + expect(res.metadata).to.exist(); + }); }); diff --git a/packages/wasm-sdk/QUERY_REVIEW.md b/packages/wasm-sdk/QUERY_REVIEW.md deleted file mode 100644 index 44690b97a3e..00000000000 --- a/packages/wasm-sdk/QUERY_REVIEW.md +++ /dev/null @@ -1,17 +0,0 @@ -# wasm-sdk query review (fetch/fetch_many, proofs, efficiency) - -Context: review of query implementations to verify we use fetch/fetch_many correctly, avoid inefficient patterns, and handle proofs. - -## Findings - -- ✅ `src/queries/token.rs:getTokenPerpetualDistributionLastClaim` - - **FIXED**: Now prefetches token configuration before making the proof-verified query. The token configuration is fetched via `TokenContractInfo` and `DataContract` queries, extracted, and cached in the trusted context provider before calling `RewardDistributionMoment::fetch()`. -- `src/queries/protocol.rs:getProtocolVersionUpgradeVoteStatus` (and proof variant TODO) - - Uses `fetch_votes` without a proof-capable path; proof variant remains unimplemented. Protocol vote status cannot currently be verified. -- `src/queries/epoch.rs:getEvonodesProposedEpochBlocksBy*WithProofInfo` - - Proof-supporting variants are stubbed (return errors). Evonode proposed block counts cannot be fetched with proofs yet. - -## Suggested follow-ups - -1) Add/enable batch identity key queries (or parallelize) and return combined proofs for `getIdentitiesContractKeys*`. -2) Implement proof-capable variants for protocol vote status and evonode proposed block counts once underlying fetch/proof traits land. diff --git a/packages/wasm-sdk/src/queries/epoch.rs b/packages/wasm-sdk/src/queries/epoch.rs index 5ab5ca36b58..b62a48f4f93 100644 --- a/packages/wasm-sdk/src/queries/epoch.rs +++ b/packages/wasm-sdk/src/queries/epoch.rs @@ -521,31 +521,94 @@ impl WasmSdk { )) } - #[wasm_bindgen(js_name = "getEvonodesProposedEpochBlocksByIdsWithProofInfo")] + #[wasm_bindgen( + js_name = "getEvonodesProposedEpochBlocksByIdsWithProofInfo", + unchecked_return_type = "ProofMetadataResponseTyped>" + )] pub async fn get_evonodes_proposed_epoch_blocks_by_ids_with_proof_info( &self, epoch: u16, #[wasm_bindgen(js_name = "proTxHashes")] pro_tx_hashes: Vec, - ) -> Result { - // TODO: Implement once SDK Query trait is implemented for ProposerBlockCountById - // Currently not supported due to query format issues - let _ = (self, epoch, pro_tx_hashes); // Parameters will be used when implemented - Err(WasmSdkError::generic( - "get_evonodes_proposed_epoch_blocks_by_ids_with_proof_info is not yet implemented", + ) -> Result { + use drive_proof_verifier::types::ProposerBlockCountById; + + // Parse the ProTxHash strings + let parsed_hashes: Vec = pro_tx_hashes + .into_iter() + .map(|hash_str| { + ProTxHash::from_str(&hash_str).map_err(|e| { + WasmSdkError::invalid_argument(format!( + "Invalid ProTxHash '{}': {}", + hash_str, e + )) + }) + }) + .collect::, WasmSdkError>>()?; + + // Use FetchMany with proof to get block counts for specific IDs + let (counts, metadata, proof) = + ProposerBlockCountById::fetch_many_with_metadata_and_proof( + self.as_ref(), + (epoch, parsed_hashes), + None, + ) + .await?; + + let map = Map::new(); + for (identifier, count) in counts.0 { + let key = JsValue::from(IdentifierWasm::from(identifier)); + map.set(&key, &JsValue::from(BigInt::from(count))); + } + + Ok(ProofMetadataResponseWasm::from_parts( + JsValue::from(map), + metadata.into(), + proof.into(), )) } - #[wasm_bindgen(js_name = "getEvonodesProposedEpochBlocksByRangeWithProofInfo")] + #[wasm_bindgen( + js_name = "getEvonodesProposedEpochBlocksByRangeWithProofInfo", + unchecked_return_type = "ProofMetadataResponseTyped>" + )] pub async fn get_evonodes_proposed_epoch_blocks_by_range_with_proof_info( &self, query: EvonodeProposedBlocksRangeQueryJs, - ) -> Result { - // TODO: Implement once SDK Query trait is implemented for ProposerBlockCountByRange - // Currently not supported due to query format issues - let parsed = parse_evonode_range_query(query)?; - let _ = (self, parsed); // Parameters will be used when implemented - Err(WasmSdkError::generic( - "get_evonodes_proposed_epoch_blocks_by_range_with_proof_info is not yet implemented", + ) -> Result { + use drive_proof_verifier::types::ProposerBlockCountByRange; + + let EvonodeProposedBlocksRangeQueryParsed { + epoch, + limit, + start_info, + order_ascending: _, + } = parse_evonode_range_query(query)?; + + // Create a LimitQuery for the range request + let limit_query = LimitQuery { + query: Some(epoch), + limit, + start_info, + }; + + let (counts, metadata, proof) = + ProposerBlockCountByRange::fetch_many_with_metadata_and_proof( + self.as_ref(), + limit_query, + None, + ) + .await?; + + let map = Map::new(); + for (identifier, count) in counts.0 { + let key = JsValue::from(IdentifierWasm::from(identifier)); + map.set(&key, &JsValue::from(BigInt::from(count))); + } + + Ok(ProofMetadataResponseWasm::from_parts( + JsValue::from(map), + metadata.into(), + proof.into(), )) } } diff --git a/packages/wasm-sdk/src/queries/protocol.rs b/packages/wasm-sdk/src/queries/protocol.rs index 7fc9d00a8a2..67611502a13 100644 --- a/packages/wasm-sdk/src/queries/protocol.rs +++ b/packages/wasm-sdk/src/queries/protocol.rs @@ -244,19 +244,76 @@ impl WasmSdk { )) } - #[wasm_bindgen(js_name = "getProtocolVersionUpgradeVoteStatusWithProofInfo")] + #[wasm_bindgen( + js_name = "getProtocolVersionUpgradeVoteStatusWithProofInfo", + unchecked_return_type = "ProofMetadataResponseTyped>" + )] pub async fn get_protocol_version_upgrade_vote_status_with_proof_info( &self, #[wasm_bindgen(js_name = "startProTxHash")] #[wasm_bindgen(unchecked_param_type = "string | Uint8Array")] start_pro_tx_hash: JsValue, count: u32, - ) -> Result { - // 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 _ = (self, start_pro_tx_hash, count); // Parameters will be used when implemented - Err(WasmSdkError::generic( - "get_protocol_version_upgrade_vote_status_with_proof_info is not yet implemented", + ) -> Result { + use dash_sdk::dpp::dashcore::ProTxHash; + use dash_sdk::platform::{FetchMany, LimitQuery}; + use drive_proof_verifier::types::MasternodeProtocolVote; + use std::str::FromStr; + + // Parse the ProTxHash + let start_hash: Option = if let Some(s) = start_pro_tx_hash.as_string() { + if s.is_empty() { + None + } else { + Some(ProTxHash::from_str(&s).map_err(|e| { + WasmSdkError::invalid_argument(format!("Invalid ProTxHash: {}", e)) + })?) + } + } else { + let bytes = js_sys::Uint8Array::new(&start_pro_tx_hash).to_vec(); + if bytes.is_empty() { + None + } else { + if bytes.len() != 32 { + return Err(WasmSdkError::invalid_argument( + "ProTxHash must be 32 bytes or an empty value", + )); + } + let mut arr = [0u8; 32]; + arr.copy_from_slice(&bytes); + let raw = sha256d::Hash::from_byte_array(arr); + Some(ProTxHash::from_raw_hash(raw)) + } + }; + + // Create a LimitQuery with the start hash and count + let query = LimitQuery { + query: start_hash, + limit: Some(count), + start_info: None, + }; + + let (votes_result, metadata, proof) = + MasternodeProtocolVote::fetch_many_with_metadata_and_proof(self.as_ref(), query, None) + .await?; + + // Convert to our response format + let votes_map = Map::new(); + for (pro_tx_hash, vote_opt) in votes_result { + if let Some(vote) = vote_opt { + let key = JsValue::from_str(&pro_tx_hash.to_string()); + let value = JsValue::from(ProtocolVersionUpgradeVoteStatusWasm::new( + pro_tx_hash.to_string(), + vote.voted_version, + )); + votes_map.set(&key, &value); + } + } + + Ok(ProofMetadataResponseWasm::from_parts( + JsValue::from(votes_map), + metadata.into(), + proof.into(), )) } } diff --git a/packages/wasm-sdk/tests/functional/epochs-blocks.spec.mjs b/packages/wasm-sdk/tests/functional/epochs-blocks.spec.mjs index cb3765c414e..c0551d7ae6f 100644 --- a/packages/wasm-sdk/tests/functional/epochs-blocks.spec.mjs +++ b/packages/wasm-sdk/tests/functional/epochs-blocks.spec.mjs @@ -45,4 +45,24 @@ describe('Epochs and evonode blocks', function describeEpochs() { limit: 50, }); }); + + it('queries evonode proposed blocks by ids with proof', async () => { + const EVONODE_ID = '143dcd6a6b7684fde01e88a10e5d65de9a29244c5ecd586d14a342657025f113'; + const res = await client.getEvonodesProposedEpochBlocksByIdsWithProofInfo(8635, [EVONODE_ID]); + expect(res).to.be.ok(); + expect(res.data).to.be.instanceOf(Map); + expect(res.proof).to.be.ok(); + expect(res.metadata).to.be.ok(); + }); + + it('queries evonode proposed blocks by range with proof', async () => { + const res = await client.getEvonodesProposedEpochBlocksByRangeWithProofInfo({ + epoch: 8635, + limit: 50, + }); + expect(res).to.be.ok(); + expect(res.data).to.be.instanceOf(Map); + expect(res.proof).to.be.ok(); + expect(res.metadata).to.be.ok(); + }); }); diff --git a/packages/wasm-sdk/tests/functional/protocol.spec.mjs b/packages/wasm-sdk/tests/functional/protocol.spec.mjs index ba9e3b35f4c..5f093058a66 100644 --- a/packages/wasm-sdk/tests/functional/protocol.spec.mjs +++ b/packages/wasm-sdk/tests/functional/protocol.spec.mjs @@ -22,9 +22,26 @@ describe('Protocol versions', function describeProtocolVersions() { expect(state).to.be.ok(); }); + it('fetches protocol upgrade state with proof', async () => { + const res = await client.getProtocolVersionUpgradeStateWithProofInfo(); + expect(res).to.be.ok(); + expect(res.data).to.be.ok(); + expect(res.proof).to.be.ok(); + expect(res.metadata).to.be.ok(); + }); + it('lists protocol upgrade vote statuses', async () => { const START_PROTX = '143dcd6a6b7684fde01e88a10e5d65de9a29244c5ecd586d14a342657025f113'; const res = await client.getProtocolVersionUpgradeVoteStatus(START_PROTX, 50); expect(res).to.be.instanceOf(Map); }); + + it('lists protocol upgrade vote statuses with proof', async () => { + const START_PROTX = '143dcd6a6b7684fde01e88a10e5d65de9a29244c5ecd586d14a342657025f113'; + const res = await client.getProtocolVersionUpgradeVoteStatusWithProofInfo(START_PROTX, 50); + expect(res).to.be.ok(); + expect(res.data).to.be.instanceOf(Map); + expect(res.proof).to.be.ok(); + expect(res.metadata).to.be.ok(); + }); }); From 208ccb0267629910c14cc919198a71aa071f6fe1 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Sat, 27 Dec 2025 18:32:28 +0700 Subject: [PATCH 04/12] refactor(sdk): simplify function signatures and improve code readability in context provider and queries --- packages/wasm-sdk/src/context_provider.rs | 6 +----- packages/wasm-sdk/src/queries/epoch.rs | 13 ++++++------- packages/wasm-sdk/src/queries/token.rs | 2 +- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/packages/wasm-sdk/src/context_provider.rs b/packages/wasm-sdk/src/context_provider.rs index ac756e7397a..932b5b6b18d 100644 --- a/packages/wasm-sdk/src/context_provider.rs +++ b/packages/wasm-sdk/src/context_provider.rs @@ -166,11 +166,7 @@ impl WasmTrustedContext { } /// Add a token configuration to the known token configurations cache - pub fn add_known_token_configuration( - &self, - token_id: Identifier, - config: TokenConfiguration, - ) { + pub fn add_known_token_configuration(&self, token_id: Identifier, config: TokenConfiguration) { self.inner.add_known_token_configuration(token_id, config); } } diff --git a/packages/wasm-sdk/src/queries/epoch.rs b/packages/wasm-sdk/src/queries/epoch.rs index d0489239644..37e81436588 100644 --- a/packages/wasm-sdk/src/queries/epoch.rs +++ b/packages/wasm-sdk/src/queries/epoch.rs @@ -542,13 +542,12 @@ impl WasmSdk { .collect::, WasmSdkError>>()?; // Use FetchMany with proof to get block counts for specific IDs - let (counts, metadata, proof) = - ProposerBlockCountById::fetch_many_with_metadata_and_proof( - self.as_ref(), - (epoch, parsed_hashes), - None, - ) - .await?; + let (counts, metadata, proof) = ProposerBlockCountById::fetch_many_with_metadata_and_proof( + self.as_ref(), + (epoch, parsed_hashes), + None, + ) + .await?; let map = Map::new(); for (identifier, count) in counts.0 { diff --git a/packages/wasm-sdk/src/queries/token.rs b/packages/wasm-sdk/src/queries/token.rs index 194e82f8938..c75c61f48ab 100644 --- a/packages/wasm-sdk/src/queries/token.rs +++ b/packages/wasm-sdk/src/queries/token.rs @@ -920,8 +920,8 @@ impl WasmSdk { token_id: Identifier, ) -> Result<(), WasmSdkError> { use crate::sdk::{MAINNET_TRUSTED_CONTEXT, TESTNET_TRUSTED_CONTEXT}; - use dash_sdk::dpp::data_contract::accessors::v1::DataContractV1Getters; use dash_sdk::dpp::dashcore::Network; + use dash_sdk::dpp::data_contract::accessors::v1::DataContractV1Getters; use dash_sdk::dpp::tokens::contract_info::v0::TokenContractInfoV0Accessors; use dash_sdk::dpp::tokens::contract_info::TokenContractInfo; use dash_sdk::platform::DataContract; From 49035bca36400b001f9b2d589a8af2c58a8bf455 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Sat, 27 Dec 2025 20:14:26 +0700 Subject: [PATCH 05/12] fix: compilation errors --- .github/grpc-queries-cache.json | 14 +++++++++++++- packages/wasm-sdk/src/queries/epoch.rs | 16 ++++++++-------- packages/wasm-sdk/src/queries/protocol.rs | 8 ++++---- 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/.github/grpc-queries-cache.json b/.github/grpc-queries-cache.json index ed3e6d2954a..0339081f3e5 100644 --- a/.github/grpc-queries-cache.json +++ b/.github/grpc-queries-cache.json @@ -141,7 +141,19 @@ }, "getConsensusParams": { "status": "not_implemented" + }, + "getAddressInfo": { + "status": "implemented" + }, + "getAddressesBranchState": { + "status": "not_implemented" + }, + "getAddressesInfos": { + "status": "implemented" + }, + "getAddressesTrunkState": { + "status": "not_implemented" } }, - "last_updated": "2025-07-14T03:27:11.465612" + "last_updated": "2025-12-27T20:03:57.356709" } \ No newline at end of file diff --git a/packages/wasm-sdk/src/queries/epoch.rs b/packages/wasm-sdk/src/queries/epoch.rs index 37e81436588..0f93dd7c143 100644 --- a/packages/wasm-sdk/src/queries/epoch.rs +++ b/packages/wasm-sdk/src/queries/epoch.rs @@ -555,10 +555,10 @@ impl WasmSdk { map.set(&key, &JsValue::from(BigInt::from(count))); } - Ok(ProofMetadataResponseWasm::from_parts( - JsValue::from(map), - metadata.into(), - proof.into(), + Ok(ProofMetadataResponseWasm::from_sdk_parts( + map, + metadata, + proof, )) } @@ -600,10 +600,10 @@ impl WasmSdk { map.set(&key, &JsValue::from(BigInt::from(count))); } - Ok(ProofMetadataResponseWasm::from_parts( - JsValue::from(map), - metadata.into(), - proof.into(), + Ok(ProofMetadataResponseWasm::from_sdk_parts( + map, + metadata, + proof, )) } } diff --git a/packages/wasm-sdk/src/queries/protocol.rs b/packages/wasm-sdk/src/queries/protocol.rs index f89548e7b74..6e1c6639d61 100644 --- a/packages/wasm-sdk/src/queries/protocol.rs +++ b/packages/wasm-sdk/src/queries/protocol.rs @@ -317,10 +317,10 @@ impl WasmSdk { } } - Ok(ProofMetadataResponseWasm::from_parts( - JsValue::from(votes_map), - metadata.into(), - proof.into(), + Ok(ProofMetadataResponseWasm::from_sdk_parts( + votes_map, + metadata, + proof, )) } } From 634ebcc9d91cfbf8c62859109c626afb2a45addc Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Sat, 27 Dec 2025 20:38:36 +0700 Subject: [PATCH 06/12] refactor: linting and formatiing --- packages/js-evo-sdk/tests/functional/epoch.spec.mjs | 8 ++++++-- packages/wasm-sdk/src/queries/epoch.rs | 8 ++------ packages/wasm-sdk/src/queries/protocol.rs | 4 +--- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/js-evo-sdk/tests/functional/epoch.spec.mjs b/packages/js-evo-sdk/tests/functional/epoch.spec.mjs index c91db04719b..47c6365a663 100644 --- a/packages/js-evo-sdk/tests/functional/epoch.spec.mjs +++ b/packages/js-evo-sdk/tests/functional/epoch.spec.mjs @@ -26,7 +26,8 @@ describe('Epoch', function epochSuite() { }); it('evonodesProposedBlocksByIdsWithProof() returns results with proof', async () => { - const res = await sdk.epoch.evonodesProposedBlocksByIdsWithProof(TEST_IDS.epoch, [TEST_IDS.proTxHash]); + const { epoch, proTxHash } = TEST_IDS; + const res = await sdk.epoch.evonodesProposedBlocksByIdsWithProof(epoch, [proTxHash]); expect(res).to.exist(); expect(res.data).to.be.instanceOf(Map); expect(res.proof).to.exist(); @@ -34,7 +35,10 @@ describe('Epoch', function epochSuite() { }); it('evonodesProposedBlocksByRangeWithProof() returns results with proof', async () => { - const res = await sdk.epoch.evonodesProposedBlocksByRangeWithProof({ epoch: TEST_IDS.epoch, limit: 5 }); + const res = await sdk.epoch.evonodesProposedBlocksByRangeWithProof({ + epoch: TEST_IDS.epoch, + limit: 5, + }); expect(res).to.exist(); expect(res.data).to.be.instanceOf(Map); expect(res.proof).to.exist(); diff --git a/packages/wasm-sdk/src/queries/epoch.rs b/packages/wasm-sdk/src/queries/epoch.rs index 0f93dd7c143..66a5f55a390 100644 --- a/packages/wasm-sdk/src/queries/epoch.rs +++ b/packages/wasm-sdk/src/queries/epoch.rs @@ -556,9 +556,7 @@ impl WasmSdk { } Ok(ProofMetadataResponseWasm::from_sdk_parts( - map, - metadata, - proof, + map, metadata, proof, )) } @@ -601,9 +599,7 @@ impl WasmSdk { } Ok(ProofMetadataResponseWasm::from_sdk_parts( - map, - metadata, - proof, + map, metadata, proof, )) } } diff --git a/packages/wasm-sdk/src/queries/protocol.rs b/packages/wasm-sdk/src/queries/protocol.rs index 6e1c6639d61..57b149e6dc2 100644 --- a/packages/wasm-sdk/src/queries/protocol.rs +++ b/packages/wasm-sdk/src/queries/protocol.rs @@ -318,9 +318,7 @@ impl WasmSdk { } Ok(ProofMetadataResponseWasm::from_sdk_parts( - votes_map, - metadata, - proof, + votes_map, metadata, proof, )) } } From cd8aadb71881aeb43e66bf9a4c9add862c9c57d4 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Sun, 28 Dec 2025 11:42:50 +0700 Subject: [PATCH 07/12] revert: unnecessary changes --- .github/grpc-queries-cache.json | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/.github/grpc-queries-cache.json b/.github/grpc-queries-cache.json index 0339081f3e5..ed3e6d2954a 100644 --- a/.github/grpc-queries-cache.json +++ b/.github/grpc-queries-cache.json @@ -141,19 +141,7 @@ }, "getConsensusParams": { "status": "not_implemented" - }, - "getAddressInfo": { - "status": "implemented" - }, - "getAddressesBranchState": { - "status": "not_implemented" - }, - "getAddressesInfos": { - "status": "implemented" - }, - "getAddressesTrunkState": { - "status": "not_implemented" } }, - "last_updated": "2025-12-27T20:03:57.356709" + "last_updated": "2025-07-14T03:27:11.465612" } \ No newline at end of file From fd0a9bf9290285b3055b0a0b5026c10c523d570c Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Sun, 28 Dec 2025 13:42:23 +0700 Subject: [PATCH 08/12] chore: some fixes --- packages/wasm-dpp2/TODO.md | 52 ++++++++++++++++++++ packages/wasm-sdk/src/context_provider.rs | 6 +-- packages/wasm-sdk/src/queries/epoch.rs | 14 ------ packages/wasm-sdk/src/queries/token.rs | 59 ++++++++--------------- 4 files changed, 75 insertions(+), 56 deletions(-) create mode 100644 packages/wasm-dpp2/TODO.md diff --git a/packages/wasm-dpp2/TODO.md b/packages/wasm-dpp2/TODO.md new file mode 100644 index 00000000000..8f142e23ecc --- /dev/null +++ b/packages/wasm-dpp2/TODO.md @@ -0,0 +1,52 @@ +# wasm-dpp2 TODO + +## Type Wrappers for Flexible Input + +### ProTxHash Wrapper + +Create a wrapper around `ProTxHash` from dashcore with flexible input types. + +`ProTxHash` is a newtype wrapper around `sha256d::Hash`: + +```rust +pub struct ProTxHash(sha256d::Hash); +``` + +```typescript +type ProTxHashLike = ProTxHash | Uint8Array | string; +``` + +**Requirements:** + +- Create `ProTxHashWasm` wrapper in wasm-dpp2 +- Accept hex string, raw bytes (Uint8Array), or ProTxHash object +- Centralize parsing logic (currently duplicated in wasm-sdk methods) + +**Affected areas in wasm-sdk:** + +- `getProtocolVersionUpgradeVoteStatus` (startProTxHash parameter) +- `getProtocolVersionUpgradeVoteStatusWithProofInfo` (startProTxHash parameter) +- `getEvonodesProposedEpochBlocksByIds` (ids parameter) +- `getEvonodesProposedEpochBlocksByIdsWithProofInfo` (proTxHashes parameter) +- `EvonodeProposedBlocksRangeQuery.startAfter` field + +### Network Wrapper + +Create a wrapper around `Network` from dashcore with flexible input types: + +```typescript +type NetworkLike = Network | string; +``` + +**Requirements:** + +- Create `NetworkWasm` wrapper in wasm-dpp2 +- Accept string ("mainnet", "testnet", "devnet", "regtest") or Network object +- Centralize parsing logic +- Use everywhere we pass network parameter + +**Affected areas:** + +- SDK builder methods +- Wallet key derivation +- Address generation/validation diff --git a/packages/wasm-sdk/src/context_provider.rs b/packages/wasm-sdk/src/context_provider.rs index 932b5b6b18d..5ad99a1377f 100644 --- a/packages/wasm-sdk/src/context_provider.rs +++ b/packages/wasm-sdk/src/context_provider.rs @@ -106,8 +106,7 @@ impl WasmTrustedContext { std::num::NonZeroUsize::new(100).unwrap(), ) .map_err(|e| ContextProviderError::Generic(e.to_string()))? - // Enable refetch so token configs (and other context) are pulled on demand, same as rs-sdk - .with_refetch_if_not_found(true); + .with_refetch_if_not_found(false); Ok(Self { inner: std::sync::Arc::new(inner), @@ -121,8 +120,7 @@ impl WasmTrustedContext { std::num::NonZeroUsize::new(100).unwrap(), ) .map_err(|e| ContextProviderError::Generic(e.to_string()))? - // Enable refetch so token configs (and other context) are pulled on demand, same as rs-sdk - .with_refetch_if_not_found(true); + .with_refetch_if_not_found(false); Ok(Self { inner: std::sync::Arc::new(inner), diff --git a/packages/wasm-sdk/src/queries/epoch.rs b/packages/wasm-sdk/src/queries/epoch.rs index 66a5f55a390..e808423fec6 100644 --- a/packages/wasm-sdk/src/queries/epoch.rs +++ b/packages/wasm-sdk/src/queries/epoch.rs @@ -164,12 +164,6 @@ export interface EvonodeProposedBlocksRangeQuery { * @default undefined */ startAfter?: string; - - /** - * Sort order for results. - * @default undefined (server default) - */ - orderAscending?: boolean; } "#; @@ -187,15 +181,12 @@ struct EvonodeProposedBlocksRangeQueryInput { limit: Option, #[serde(default)] start_after: Option, - #[serde(default)] - order_ascending: Option, } struct EvonodeProposedBlocksRangeQueryParsed { epoch: u16, limit: Option, start_info: Option, - order_ascending: Option, } fn parse_evonode_range_query( @@ -223,7 +214,6 @@ fn parse_evonode_range_query( epoch: input.epoch, limit: input.limit, start_info, - order_ascending: input.order_ascending, }) } @@ -374,11 +364,8 @@ impl WasmSdk { epoch, limit, start_info, - order_ascending, } = parse_evonode_range_query(query)?; - let _ = order_ascending; - let counts_result = ProposerBlockCounts::fetch_proposed_blocks_by_range( self.as_ref(), Some(epoch), @@ -574,7 +561,6 @@ impl WasmSdk { epoch, limit, start_info, - order_ascending: _, } = parse_evonode_range_query(query)?; // Create a LimitQuery for the range request diff --git a/packages/wasm-sdk/src/queries/token.rs b/packages/wasm-sdk/src/queries/token.rs index c75c61f48ab..f96de4f01e9 100644 --- a/packages/wasm-sdk/src/queries/token.rs +++ b/packages/wasm-sdk/src/queries/token.rs @@ -72,6 +72,18 @@ impl TokenLastClaimWasm { } } +impl From for TokenLastClaimWasm { + fn from(moment: RewardDistributionMoment) -> Self { + let (last_claim_timestamp_ms, last_claim_block_height) = match moment { + RewardDistributionMoment::BlockBasedMoment(height) => (0, height), + RewardDistributionMoment::TimeBasedMoment(timestamp) => (timestamp, 0), + RewardDistributionMoment::EpochBasedMoment(epoch) => (0, epoch as u64), + }; + + Self::new(last_claim_timestamp_ms, last_claim_block_height) + } +} + #[wasm_bindgen(js_class = TokenLastClaim)] impl TokenLastClaimWasm { #[wasm_bindgen(getter = "lastClaimTimestampMs")] @@ -503,17 +515,7 @@ impl WasmSdk { let claim_result = RewardDistributionMoment::fetch(self.as_ref(), query).await?; - let data = claim_result.map(|moment| { - let (last_claim_timestamp_ms, last_claim_block_height) = match moment { - RewardDistributionMoment::BlockBasedMoment(height) => (0, height), - RewardDistributionMoment::TimeBasedMoment(timestamp) => (timestamp, 0), - RewardDistributionMoment::EpochBasedMoment(epoch) => (0, epoch as u64), - }; - - TokenLastClaimWasm::new(last_claim_timestamp_ms, last_claim_block_height) - }); - - Ok(data) + Ok(claim_result.map(TokenLastClaimWasm::from)) } #[wasm_bindgen(js_name = "getTokenTotalSupply")] @@ -879,31 +881,10 @@ impl WasmSdk { RewardDistributionMoment::fetch_with_metadata_and_proof(self.as_ref(), query, None) .await?; - 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, - // 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 - }; - - Some(TokenLastClaimWasm::new( - last_claim_timestamp_ms, - last_claim_block_height, - )) - } else { - None - }; - - let data = data.map(JsValue::from).unwrap_or(JsValue::UNDEFINED); + let data = claim_result + .map(TokenLastClaimWasm::from) + .map(JsValue::from) + .unwrap_or(JsValue::UNDEFINED); Ok(ProofMetadataResponseWasm::from_sdk_parts( data, metadata, proof, @@ -974,8 +955,10 @@ impl WasmSdk { } } _ => { - // For other networks, we can't cache the token configuration - // The proof verification may still work if the context provider has the info + return Err(WasmSdkError::generic(format!( + "Token configuration caching not implemented for network {:?}", + network + ))); } } From a19372aa4a479ec20816a08e167877180fc2f1ef Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Sun, 28 Dec 2025 13:44:30 +0700 Subject: [PATCH 09/12] docs: bring back comment --- packages/wasm-sdk/src/context_provider.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/wasm-sdk/src/context_provider.rs b/packages/wasm-sdk/src/context_provider.rs index 5ad99a1377f..093c45ad838 100644 --- a/packages/wasm-sdk/src/context_provider.rs +++ b/packages/wasm-sdk/src/context_provider.rs @@ -106,7 +106,7 @@ impl WasmTrustedContext { std::num::NonZeroUsize::new(100).unwrap(), ) .map_err(|e| ContextProviderError::Generic(e.to_string()))? - .with_refetch_if_not_found(false); + .with_refetch_if_not_found(false); // Disable refetch since we'll pre-fetch Ok(Self { inner: std::sync::Arc::new(inner), @@ -120,7 +120,7 @@ impl WasmTrustedContext { std::num::NonZeroUsize::new(100).unwrap(), ) .map_err(|e| ContextProviderError::Generic(e.to_string()))? - .with_refetch_if_not_found(false); + .with_refetch_if_not_found(false); // Disable refetch since we'll pre-fetch Ok(Self { inner: std::sync::Arc::new(inner), From f06dbf3c9551445ab946c9b271ed97a9aebfc1d8 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Sun, 28 Dec 2025 15:06:50 +0700 Subject: [PATCH 10/12] fix: function not returning error if context is not present --- packages/wasm-sdk/src/queries/token.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/wasm-sdk/src/queries/token.rs b/packages/wasm-sdk/src/queries/token.rs index be345dfebf1..b4aee5466ed 100644 --- a/packages/wasm-sdk/src/queries/token.rs +++ b/packages/wasm-sdk/src/queries/token.rs @@ -955,14 +955,24 @@ impl WasmSdk { let network = self.network(); match network { Network::Dash => { - if let Some(ref ctx) = *MAINNET_TRUSTED_CONTEXT.lock().unwrap() { - ctx.add_known_token_configuration(token_id, token_configuration); - } + let guard = MAINNET_TRUSTED_CONTEXT.lock().unwrap(); + let ctx = guard.as_ref().ok_or_else(|| { + WasmSdkError::generic(format!( + "Mainnet trusted context not initialized for token {}", + token_id + )) + })?; + ctx.add_known_token_configuration(token_id, token_configuration); } Network::Testnet => { - if let Some(ref ctx) = *TESTNET_TRUSTED_CONTEXT.lock().unwrap() { - ctx.add_known_token_configuration(token_id, token_configuration); - } + let guard = TESTNET_TRUSTED_CONTEXT.lock().unwrap(); + let ctx = guard.as_ref().ok_or_else(|| { + WasmSdkError::generic(format!( + "Testnet trusted context not initialized for token {}", + token_id + )) + })?; + ctx.add_known_token_configuration(token_id, token_configuration); } _ => { // For other networks, we can't cache the token configuration From c0697bfc770107c91f573cfc7d0e769689a89486 Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Sun, 28 Dec 2025 18:40:31 +0700 Subject: [PATCH 11/12] fix: invalid token distribution response --- packages/wasm-sdk/src/queries/token.rs | 76 ++++++++++++++------------ 1 file changed, 41 insertions(+), 35 deletions(-) diff --git a/packages/wasm-sdk/src/queries/token.rs b/packages/wasm-sdk/src/queries/token.rs index ef6e210c8a9..4bf322f9d0c 100644 --- a/packages/wasm-sdk/src/queries/token.rs +++ b/packages/wasm-sdk/src/queries/token.rs @@ -55,48 +55,54 @@ impl TokenPriceInfoWasm { } } -#[wasm_bindgen(js_name = "TokenLastClaim")] -#[derive(Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct TokenLastClaimWasm { - last_claim_timestamp_ms: u64, - last_claim_block_height: u64, -} - -impl TokenLastClaimWasm { - fn new(last_claim_timestamp_ms: u64, last_claim_block_height: u64) -> Self { - Self { - last_claim_timestamp_ms, - last_claim_block_height, +#[wasm_bindgen(js_name = "RewardDistributionMoment")] +pub struct RewardDistributionMomentWasm(RewardDistributionMoment); + +#[wasm_bindgen(js_class = RewardDistributionMoment)] +impl RewardDistributionMomentWasm { + /// Returns the type: "block", "time", or "epoch" + #[wasm_bindgen(getter = "type")] + pub fn moment_type(&self) -> String { + match &self.0 { + RewardDistributionMoment::BlockBasedMoment(_) => "block".to_string(), + RewardDistributionMoment::TimeBasedMoment(_) => "time".to_string(), + RewardDistributionMoment::EpochBasedMoment(_) => "epoch".to_string(), } } -} -impl From for TokenLastClaimWasm { - fn from(moment: RewardDistributionMoment) -> Self { - let (last_claim_timestamp_ms, last_claim_block_height) = match moment { - RewardDistributionMoment::BlockBasedMoment(height) => (0, height), - RewardDistributionMoment::TimeBasedMoment(timestamp) => (timestamp, 0), - RewardDistributionMoment::EpochBasedMoment(epoch) => (0, epoch as u64), - }; + /// Returns the block height (only valid when type is "block") + #[wasm_bindgen(getter = "blockHeight")] + pub fn block_height(&self) -> Option { + match &self.0 { + RewardDistributionMoment::BlockBasedMoment(height) => Some(*height), + _ => None, + } + } - Self::new(last_claim_timestamp_ms, last_claim_block_height) + /// Returns the timestamp in ms (only valid when type is "time") + #[wasm_bindgen(getter = "timestampMs")] + pub fn timestamp_ms(&self) -> Option { + match &self.0 { + RewardDistributionMoment::TimeBasedMoment(ts) => Some(*ts), + _ => None, + } } -} -#[wasm_bindgen(js_class = TokenLastClaim)] -impl TokenLastClaimWasm { - #[wasm_bindgen(getter = "lastClaimTimestampMs")] - pub fn last_claim_timestamp_ms(&self) -> u64 { - self.last_claim_timestamp_ms + /// Returns the epoch index (only valid when type is "epoch") + #[wasm_bindgen(getter = "epochIndex")] + pub fn epoch_index(&self) -> Option { + match &self.0 { + RewardDistributionMoment::EpochBasedMoment(epoch) => Some(*epoch), + _ => None, + } } +} - #[wasm_bindgen(getter = "lastClaimBlockHeight")] - pub fn last_claim_block_height(&self) -> u64 { - self.last_claim_block_height +impl From for RewardDistributionMomentWasm { + fn from(moment: RewardDistributionMoment) -> Self { + Self(moment) } } -impl_wasm_serde_conversions!(TokenLastClaimWasm); #[wasm_bindgen(js_name = "TokenTotalSupply")] #[derive(Clone, Serialize, Deserialize)] @@ -498,7 +504,7 @@ impl WasmSdk { #[wasm_bindgen(js_name = "tokenId")] #[wasm_bindgen(unchecked_param_type = "Identifier | Uint8Array | string")] token_id: JsValue, - ) -> Result, WasmSdkError> { + ) -> Result, WasmSdkError> { // Parse IDs let identity_identifier = identifier_from_js(&identity_id, "identity ID")?; let token_identifier = identifier_from_js(&token_id, "token ID")?; @@ -515,7 +521,7 @@ impl WasmSdk { let claim_result = RewardDistributionMoment::fetch(self.as_ref(), query).await?; - Ok(claim_result.map(TokenLastClaimWasm::from)) + Ok(claim_result.map(RewardDistributionMomentWasm::from)) } #[wasm_bindgen(js_name = "getTokenTotalSupply")] @@ -882,7 +888,7 @@ impl WasmSdk { .await?; let data = claim_result - .map(TokenLastClaimWasm::from) + .map(RewardDistributionMomentWasm::from) .map(JsValue::from) .unwrap_or(JsValue::UNDEFINED); From 45ab344f04160de4e6b86d349f7c5f6b258a85fd Mon Sep 17 00:00:00 2001 From: Ivan Shumkov Date: Sun, 28 Dec 2025 18:53:16 +0700 Subject: [PATCH 12/12] fix: evo typings --- packages/js-evo-sdk/src/tokens/facade.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js-evo-sdk/src/tokens/facade.ts b/packages/js-evo-sdk/src/tokens/facade.ts index ee2d3289e36..67d79ce37f1 100644 --- a/packages/js-evo-sdk/src/tokens/facade.ts +++ b/packages/js-evo-sdk/src/tokens/facade.ts @@ -100,12 +100,12 @@ export class TokensFacade { return w.getTokenContractInfoWithProofInfo(contractId); } - async perpetualDistributionLastClaim(identityId: wasm.IdentifierLike, tokenId: wasm.IdentifierLike): Promise { + async perpetualDistributionLastClaim(identityId: wasm.IdentifierLike, tokenId: wasm.IdentifierLike): Promise { const w = await this.sdk.getWasmSdkConnected(); return w.getTokenPerpetualDistributionLastClaim(identityId, tokenId); } - async perpetualDistributionLastClaimWithProof(identityId: wasm.IdentifierLike, tokenId: wasm.IdentifierLike): Promise> { + async perpetualDistributionLastClaimWithProof(identityId: wasm.IdentifierLike, tokenId: wasm.IdentifierLike): Promise> { const w = await this.sdk.getWasmSdkConnected(); return w.getTokenPerpetualDistributionLastClaimWithProofInfo(identityId, tokenId); }