From c5325c96026e0fb7bbb46f7cb85adf0be3e87a8e Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 13 Mar 2026 00:55:16 +0700 Subject: [PATCH 01/15] feat(wasm-dpp2,js-sdk): add shielded pool WASM bindings and JS SDK methods Add WASM bindings for all 5 shielded state transition types in wasm-dpp2: - ShieldTransitionWasm - ShieldedTransferTransitionWasm - UnshieldTransitionWasm - ShieldFromAssetLockTransitionWasm - ShieldedWithdrawalTransitionWasm Each wrapper exposes type-specific getters (anchor, proof, binding signature, actions, etc.), serialization (toBytes/fromBytes/toJSON/toObject), and toStateTransition conversion following wasm-dpp2 patterns. Also adds computePlatformSighash utility for constructing the platform sighash used by Orchard binding signatures. Replaces all shielded todo!() panics in wasm-dpp2's StateTransitionWasm with proper None returns or error messages for identityContractNonce, identityNonce, setOwnerId, setIdentityContractNonce, and setIdentityNonce. Updates wasm-dpp to: - Register shielded types (15-19) in StateTransitionTypeWasm enum - Handle shielded transitions in createFromBuffer factory via serde Adds JS SDK shielded methods (platform.shielded.*): - shield, shieldedTransfer, unshield, shieldFromAssetLock, shieldedWithdrawal All accept pre-serialized transition bytes and broadcast with skipValidation. Co-Authored-By: Claude Opus 4.6 --- .../src/SDK/Client/Platform/Platform.ts | 26 +++ .../Platform/methods/shielded/shield.ts | 38 +++++ .../methods/shielded/shieldFromAssetLock.ts | 39 +++++ .../methods/shielded/shieldedTransfer.ts | 41 +++++ .../methods/shielded/shieldedWithdrawal.ts | 41 +++++ .../Platform/methods/shielded/unshield.ts | 41 +++++ .../state_transition/transition_types.rs | 13 +- .../state_transition_factory.rs | 20 ++- packages/wasm-dpp2/src/lib.rs | 5 + packages/wasm-dpp2/src/shielded/mod.rs | 37 +++++ .../shield_from_asset_lock_transition.rs | 139 ++++++++++++++++ .../src/shielded/shield_transition.rs | 156 ++++++++++++++++++ .../shielded/shielded_transfer_transition.rs | 119 +++++++++++++ .../shielded_withdrawal_transition.rs | 146 ++++++++++++++++ .../src/shielded/unshield_transition.rs | 128 ++++++++++++++ .../base/state_transition.rs | 16 +- 16 files changed, 993 insertions(+), 12 deletions(-) create mode 100644 packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shield.ts create mode 100644 packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldFromAssetLock.ts create mode 100644 packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldedTransfer.ts create mode 100644 packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldedWithdrawal.ts create mode 100644 packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/unshield.ts create mode 100644 packages/wasm-dpp2/src/shielded/mod.rs create mode 100644 packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs create mode 100644 packages/wasm-dpp2/src/shielded/shield_transition.rs create mode 100644 packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs create mode 100644 packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs create mode 100644 packages/wasm-dpp2/src/shielded/unshield_transition.rs diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts index 873fe62c53d..0539c8365f8 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts @@ -34,6 +34,12 @@ import resolveNameByRecord from './methods/names/resolveByRecord'; import searchName from './methods/names/search'; import broadcastStateTransition from './broadcastStateTransition'; +import shieldMethod from './methods/shielded/shield'; +import shieldedTransferMethod from './methods/shielded/shieldedTransfer'; +import unshieldMethod from './methods/shielded/unshield'; +import shieldFromAssetLockMethod from './methods/shielded/shieldFromAssetLock'; +import shieldedWithdrawalMethod from './methods/shielded/shieldedWithdrawal'; + import logger, { ConfigurableLogger } from '../../../logger'; import Fetcher from './Fetcher'; import NonceManager from './NonceManager/NonceManager'; @@ -98,6 +104,14 @@ interface DataContracts { history: Function, } +interface Shielded { + shield: Function, + shieldedTransfer: Function, + unshield: Function, + shieldFromAssetLock: Function, + shieldedWithdrawal: Function, +} + /** * Class for Dash Platform * @@ -117,6 +131,11 @@ export class Platform { public documents: Records; + /** + * Shielded pool operations (shield, transfer, unshield) + */ + public shielded: Shielded; + /** * @param {Function} get - get identities from the platform * @param {Function} register - register identities on the platform @@ -180,6 +199,13 @@ export class Platform { resolveByRecord: resolveNameByRecord.bind(this), search: searchName.bind(this), }; + this.shielded = { + shield: shieldMethod.bind(this), + shieldedTransfer: shieldedTransferMethod.bind(this), + unshield: unshieldMethod.bind(this), + shieldFromAssetLock: shieldFromAssetLockMethod.bind(this), + shieldedWithdrawal: shieldedWithdrawalMethod.bind(this), + }; this.identities = { register: registerIdentity.bind(this), get: getIdentity.bind(this), diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shield.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shield.ts new file mode 100644 index 00000000000..5391cb205e8 --- /dev/null +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shield.ts @@ -0,0 +1,38 @@ +import broadcastStateTransition from '../../broadcastStateTransition'; +import { Platform } from '../../Platform'; +import { IStateTransitionResult } from '../../IStateTransitionResult'; + +/** + * Broadcast a shield transition (transparent platform addresses -> shielded pool). + * + * The Orchard bundle, platform address witnesses, and full state transition + * must be built externally (e.g., via rs-sdk's `shield_funds()` or a native + * wallet library) and serialized to platform binary format. + * + * @param serializedTransition - Platform-serialized ShieldTransition bytes + * @returns Broadcast result + */ +export async function shield( + this: Platform, + serializedTransition: Uint8Array, +): Promise { + this.logger.debug('[Shielded#shield] Broadcasting shield transition'); + await this.initialize(); + + const { dpp } = this; + + const transition = dpp.stateTransition.createFromBuffer( + serializedTransition, + {}, + ); + + const result = await broadcastStateTransition(this, await transition, { + skipValidation: true, + }); + + this.logger.silly('[Shielded#shield] Broadcasted ShieldTransition'); + + return result; +} + +export default shield; diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldFromAssetLock.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldFromAssetLock.ts new file mode 100644 index 00000000000..95e4b1cd1a8 --- /dev/null +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldFromAssetLock.ts @@ -0,0 +1,39 @@ +import broadcastStateTransition from '../../broadcastStateTransition'; +import { Platform } from '../../Platform'; +import { IStateTransitionResult } from '../../IStateTransitionResult'; + +/** + * Broadcast a shield-from-asset-lock transition (core asset lock -> shielded pool). + * + * The Orchard bundle, asset lock proof, ECDSA signature, and full state + * transition must be built externally (e.g., via rs-sdk's + * `shield_from_asset_lock()` or a native wallet library) and serialized + * to platform binary format. + * + * @param serializedTransition - Platform-serialized ShieldFromAssetLockTransition bytes + * @returns Broadcast result + */ +export async function shieldFromAssetLock( + this: Platform, + serializedTransition: Uint8Array, +): Promise { + this.logger.debug('[Shielded#shieldFromAssetLock] Broadcasting shield from asset lock'); + await this.initialize(); + + const { dpp } = this; + + const transition = dpp.stateTransition.createFromBuffer( + serializedTransition, + {}, + ); + + const result = await broadcastStateTransition(this, await transition, { + skipValidation: true, + }); + + this.logger.silly('[Shielded#shieldFromAssetLock] Broadcasted ShieldFromAssetLockTransition'); + + return result; +} + +export default shieldFromAssetLock; diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldedTransfer.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldedTransfer.ts new file mode 100644 index 00000000000..81f68a4aaff --- /dev/null +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldedTransfer.ts @@ -0,0 +1,41 @@ +import broadcastStateTransition from '../../broadcastStateTransition'; +import { Platform } from '../../Platform'; +import { IStateTransitionResult } from '../../IStateTransitionResult'; + +/** + * Broadcast a shielded transfer (shielded-to-shielded) transition. + * + * The Orchard bundle and full state transition must be built externally + * (e.g., via rs-sdk's `transfer_shielded()` or a native wallet library) + * and serialized to platform binary format before passing here. + * + * Authentication is provided entirely by Orchard spend authorization + * signatures within the bundle — no identity signing needed. + * + * @param serializedTransition - Platform-serialized ShieldedTransferTransition bytes + * @returns Broadcast result + */ +export async function shieldedTransfer( + this: Platform, + serializedTransition: Uint8Array, +): Promise { + this.logger.debug('[Shielded#shieldedTransfer] Broadcasting shielded transfer'); + await this.initialize(); + + const { dpp } = this; + + const transition = dpp.stateTransition.createFromBuffer( + serializedTransition, + {}, + ); + + const result = await broadcastStateTransition(this, await transition, { + skipValidation: true, + }); + + this.logger.silly('[Shielded#shieldedTransfer] Broadcasted ShieldedTransferTransition'); + + return result; +} + +export default shieldedTransfer; diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldedWithdrawal.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldedWithdrawal.ts new file mode 100644 index 00000000000..fb6e47b63bf --- /dev/null +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldedWithdrawal.ts @@ -0,0 +1,41 @@ +import broadcastStateTransition from '../../broadcastStateTransition'; +import { Platform } from '../../Platform'; +import { IStateTransitionResult } from '../../IStateTransitionResult'; + +/** + * Broadcast a shielded withdrawal transition (shielded pool -> core L1 address). + * + * The Orchard bundle and full state transition must be built externally + * (e.g., via rs-sdk's `withdraw_shielded()` or a native wallet library) + * and serialized to platform binary format. + * + * The platform sighash binds `outputScript || amount` to prevent + * an attacker from substituting a different L1 destination or amount. + * + * @param serializedTransition - Platform-serialized ShieldedWithdrawalTransition bytes + * @returns Broadcast result + */ +export async function shieldedWithdrawal( + this: Platform, + serializedTransition: Uint8Array, +): Promise { + this.logger.debug('[Shielded#shieldedWithdrawal] Broadcasting shielded withdrawal'); + await this.initialize(); + + const { dpp } = this; + + const transition = dpp.stateTransition.createFromBuffer( + serializedTransition, + {}, + ); + + const result = await broadcastStateTransition(this, await transition, { + skipValidation: true, + }); + + this.logger.silly('[Shielded#shieldedWithdrawal] Broadcasted ShieldedWithdrawalTransition'); + + return result; +} + +export default shieldedWithdrawal; diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/unshield.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/unshield.ts new file mode 100644 index 00000000000..95eb4636601 --- /dev/null +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/unshield.ts @@ -0,0 +1,41 @@ +import broadcastStateTransition from '../../broadcastStateTransition'; +import { Platform } from '../../Platform'; +import { IStateTransitionResult } from '../../IStateTransitionResult'; + +/** + * Broadcast an unshield transition (shielded pool -> platform address). + * + * The Orchard bundle and full state transition must be built externally + * (e.g., via rs-sdk's `unshield_funds()` or a native wallet library) + * and serialized to platform binary format. + * + * The platform sighash binds `outputAddress || amount` to prevent + * an attacker from substituting a different destination or amount. + * + * @param serializedTransition - Platform-serialized UnshieldTransition bytes + * @returns Broadcast result + */ +export async function unshield( + this: Platform, + serializedTransition: Uint8Array, +): Promise { + this.logger.debug('[Shielded#unshield] Broadcasting unshield transition'); + await this.initialize(); + + const { dpp } = this; + + const transition = dpp.stateTransition.createFromBuffer( + serializedTransition, + {}, + ); + + const result = await broadcastStateTransition(this, await transition, { + skipValidation: true, + }); + + this.logger.silly('[Shielded#unshield] Broadcasted UnshieldTransition'); + + return result; +} + +export default unshield; diff --git a/packages/wasm-dpp/src/identity/state_transition/transition_types.rs b/packages/wasm-dpp/src/identity/state_transition/transition_types.rs index 29d2274dffe..0e30aacee89 100644 --- a/packages/wasm-dpp/src/identity/state_transition/transition_types.rs +++ b/packages/wasm-dpp/src/identity/state_transition/transition_types.rs @@ -19,6 +19,11 @@ pub enum StateTransitionTypeWasm { AddressFundsTransfer = 12, AddressFundingFromAssetLock = 13, AddressCreditWithdrawal = 14, + Shield = 15, + ShieldedTransfer = 16, + Unshield = 17, + ShieldFromAssetLock = 18, + ShieldedWithdrawal = 19, } impl From for StateTransitionTypeWasm { @@ -55,7 +60,13 @@ impl From for StateTransitionTypeWasm { StateTransitionType::AddressCreditWithdrawal => { StateTransitionTypeWasm::AddressCreditWithdrawal } - _ => todo!("shielded state transition types not yet implemented in wasm"), + StateTransitionType::Shield => StateTransitionTypeWasm::Shield, + StateTransitionType::ShieldedTransfer => StateTransitionTypeWasm::ShieldedTransfer, + StateTransitionType::Unshield => StateTransitionTypeWasm::Unshield, + StateTransitionType::ShieldFromAssetLock => { + StateTransitionTypeWasm::ShieldFromAssetLock + } + StateTransitionType::ShieldedWithdrawal => StateTransitionTypeWasm::ShieldedWithdrawal, } } } diff --git a/packages/wasm-dpp/src/state_transition/state_transition_factory.rs b/packages/wasm-dpp/src/state_transition/state_transition_factory.rs index 2b929d17a15..689b3d3501d 100644 --- a/packages/wasm-dpp/src/state_transition/state_transition_factory.rs +++ b/packages/wasm-dpp/src/state_transition/state_transition_factory.rs @@ -79,12 +79,20 @@ impl StateTransitionFactoryWasm { StateTransition::AddressCreditWithdrawal(st) => { serde_wasm_bindgen::to_value(&st).map_err(|e| JsValue::from(e.to_string())) } - StateTransition::Shield(_) - | StateTransition::ShieldedTransfer(_) - | StateTransition::Unshield(_) - | StateTransition::ShieldFromAssetLock(_) - | StateTransition::ShieldedWithdrawal(_) => { - todo!("shielded transitions not yet implemented in state_transition_factory") + StateTransition::Shield(st) => { + serde_wasm_bindgen::to_value(&st).map_err(|e| JsValue::from(e.to_string())) + } + StateTransition::ShieldedTransfer(st) => { + serde_wasm_bindgen::to_value(&st).map_err(|e| JsValue::from(e.to_string())) + } + StateTransition::Unshield(st) => { + serde_wasm_bindgen::to_value(&st).map_err(|e| JsValue::from(e.to_string())) + } + StateTransition::ShieldFromAssetLock(st) => { + serde_wasm_bindgen::to_value(&st).map_err(|e| JsValue::from(e.to_string())) + } + StateTransition::ShieldedWithdrawal(st) => { + serde_wasm_bindgen::to_value(&st).map_err(|e| JsValue::from(e.to_string())) } }, Err(dpp::ProtocolError::StateTransitionError(e)) => match e { diff --git a/packages/wasm-dpp2/src/lib.rs b/packages/wasm-dpp2/src/lib.rs index 8ff7050bff0..ad00d08c620 100644 --- a/packages/wasm-dpp2/src/lib.rs +++ b/packages/wasm-dpp2/src/lib.rs @@ -24,6 +24,7 @@ pub mod mock_bls; pub mod platform_address; pub mod public_key; pub mod serialization; +pub mod shielded; pub mod state_transitions; pub mod tokens; pub mod utils; @@ -66,6 +67,10 @@ pub use platform_address::{ PlatformAddressWasm, default_fee_strategy, fee_strategy_from_steps, fee_strategy_from_steps_or_default, outputs_to_btree_map, outputs_to_optional_btree_map, }; +pub use shielded::{ + ShieldFromAssetLockTransitionWasm, ShieldTransitionWasm, ShieldedTransferTransitionWasm, + ShieldedWithdrawalTransitionWasm, UnshieldTransitionWasm, +}; pub use state_transitions::base::{GroupStateTransitionInfoWasm, StateTransitionWasm}; pub use state_transitions::proof_result::{StateTransitionProofResultTypeJs, convert_proof_result}; pub use tokens::*; diff --git a/packages/wasm-dpp2/src/shielded/mod.rs b/packages/wasm-dpp2/src/shielded/mod.rs new file mode 100644 index 00000000000..96bf9b92ad4 --- /dev/null +++ b/packages/wasm-dpp2/src/shielded/mod.rs @@ -0,0 +1,37 @@ +pub mod shield_from_asset_lock_transition; +pub mod shield_transition; +pub mod shielded_transfer_transition; +pub mod shielded_withdrawal_transition; +pub mod unshield_transition; + +pub use shield_from_asset_lock_transition::ShieldFromAssetLockTransitionWasm; +pub use shield_transition::ShieldTransitionWasm; +pub use shielded_transfer_transition::ShieldedTransferTransitionWasm; +pub use shielded_withdrawal_transition::ShieldedWithdrawalTransitionWasm; +pub use unshield_transition::UnshieldTransitionWasm; + +use crate::error::WasmDppResult; +use wasm_bindgen::prelude::wasm_bindgen; + +/// Compute the platform sighash from an Orchard bundle commitment and extra data. +/// +/// `sighash = SHA-256("DashPlatformSighash" || bundleCommitment || extraData)` +/// +/// - For shield and shielded_transfer transitions, `extraData` should be empty. +/// - For unshield transitions, `extraData` = `outputAddress || amount (LE u64)`. +/// - For shielded withdrawal transitions, `extraData` = `outputScript || amount (LE u64)`. +#[wasm_bindgen(js_name = computePlatformSighash)] +pub fn compute_platform_sighash_wasm( + bundle_commitment: &[u8], + extra_data: &[u8], +) -> WasmDppResult> { + if bundle_commitment.len() != 32 { + return Err(crate::error::WasmDppError::invalid_argument(&format!( + "bundleCommitment must be exactly 32 bytes, got {}", + bundle_commitment.len() + ))); + } + let commitment: &[u8; 32] = bundle_commitment.try_into().expect("checked length above"); + let result = dpp::shielded::compute_platform_sighash(commitment, extra_data); + Ok(result.to_vec()) +} diff --git a/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs b/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs new file mode 100644 index 00000000000..b168907b19e --- /dev/null +++ b/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs @@ -0,0 +1,139 @@ +use crate::error::{WasmDppError, WasmDppResult}; +use crate::identifier::IdentifierWasm; +use crate::impl_wasm_type_info; +use dpp::serialization::{PlatformDeserializable, PlatformSerializable}; +use dpp::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; +use dpp::state_transition::{StateTransition, StateTransitionLike}; +use wasm_bindgen::prelude::*; + +#[derive(Clone)] +#[wasm_bindgen(js_name = ShieldFromAssetLockTransition)] +pub struct ShieldFromAssetLockTransitionWasm(ShieldFromAssetLockTransition); + +impl From for ShieldFromAssetLockTransitionWasm { + fn from(v: ShieldFromAssetLockTransition) -> Self { + ShieldFromAssetLockTransitionWasm(v) + } +} + +impl From for ShieldFromAssetLockTransition { + fn from(v: ShieldFromAssetLockTransitionWasm) -> Self { + v.0 + } +} + +#[wasm_bindgen(js_class = ShieldFromAssetLockTransition)] +impl ShieldFromAssetLockTransitionWasm { + #[wasm_bindgen(js_name = getType)] + pub fn get_type(&self) -> u8 { + self.0.state_transition_type() as u8 + } + + /// Returns the asset lock proof as a JS value. + #[wasm_bindgen(js_name = getAssetLockProof)] + pub fn get_asset_lock_proof(&self) -> WasmDppResult { + let proof = match &self.0 { + ShieldFromAssetLockTransition::V0(v0) => &v0.asset_lock_proof, + }; + serde_wasm_bindgen::to_value(proof).map_err(|e| WasmDppError::serialization(e.to_string())) + } + + /// Returns the serialized Orchard actions as a JS array. + #[wasm_bindgen(js_name = getActions)] + pub fn get_actions(&self) -> WasmDppResult { + let inner = match &self.0 { + ShieldFromAssetLockTransition::V0(v0) => &v0.actions, + }; + serde_wasm_bindgen::to_value(inner).map_err(|e| WasmDppError::serialization(e.to_string())) + } + + /// Returns the net value balance. + #[wasm_bindgen(js_name = getValueBalance)] + pub fn get_value_balance(&self) -> u64 { + match &self.0 { + ShieldFromAssetLockTransition::V0(v0) => v0.value_balance, + } + } + + /// Returns the anchor (32-byte Merkle root). + #[wasm_bindgen(js_name = getAnchor)] + pub fn get_anchor(&self) -> Vec { + match &self.0 { + ShieldFromAssetLockTransition::V0(v0) => v0.anchor.to_vec(), + } + } + + /// Returns the Halo2 proof bytes. + #[wasm_bindgen(js_name = getProof)] + pub fn get_proof(&self) -> Vec { + match &self.0 { + ShieldFromAssetLockTransition::V0(v0) => v0.proof.clone(), + } + } + + /// Returns the RedPallas binding signature (64 bytes). + #[wasm_bindgen(js_name = getBindingSignature)] + pub fn get_binding_signature(&self) -> Vec { + match &self.0 { + ShieldFromAssetLockTransition::V0(v0) => v0.binding_signature.to_vec(), + } + } + + /// Returns the ECDSA signature. + #[wasm_bindgen(js_name = getSignature)] + pub fn get_signature(&self) -> Vec { + match &self.0 { + ShieldFromAssetLockTransition::V0(v0) => v0.signature.to_vec(), + } + } + + #[wasm_bindgen(js_name = getModifiedDataIds)] + pub fn modified_data_ids(&self) -> Vec { + self.0 + .modified_data_ids() + .into_iter() + .map(IdentifierWasm::from) + .collect() + } + + #[wasm_bindgen(js_name = toObject)] + pub fn to_object(&self) -> WasmDppResult { + serde_wasm_bindgen::to_value(&self.0) + .map_err(|e| WasmDppError::serialization(e.to_string())) + } + + #[wasm_bindgen(js_name = toBytes)] + pub fn to_bytes(&self) -> WasmDppResult> { + Ok(PlatformSerializable::serialize_to_bytes( + &StateTransition::ShieldFromAssetLock(self.0.clone()), + )?) + } + + #[wasm_bindgen(js_name = fromBytes)] + pub fn from_bytes(bytes: Vec) -> WasmDppResult { + let st = StateTransition::deserialize_from_bytes(&bytes)?; + match st { + StateTransition::ShieldFromAssetLock(inner) => Ok(inner.into()), + _ => Err(WasmDppError::invalid_argument( + "Invalid state transition type: expected ShieldFromAssetLock", + )), + } + } + + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> WasmDppResult { + let json = serde_json::to_value(&self.0) + .map_err(|e| WasmDppError::serialization(e.to_string()))?; + serde_wasm_bindgen::to_value(&json).map_err(|e| WasmDppError::serialization(e.to_string())) + } + + #[wasm_bindgen(js_name = toStateTransition)] + pub fn to_state_transition(&self) -> crate::state_transitions::base::StateTransitionWasm { + StateTransition::ShieldFromAssetLock(self.0.clone()).into() + } +} + +impl_wasm_type_info!( + ShieldFromAssetLockTransitionWasm, + ShieldFromAssetLockTransition +); diff --git a/packages/wasm-dpp2/src/shielded/shield_transition.rs b/packages/wasm-dpp2/src/shielded/shield_transition.rs new file mode 100644 index 00000000000..d8ea36d4483 --- /dev/null +++ b/packages/wasm-dpp2/src/shielded/shield_transition.rs @@ -0,0 +1,156 @@ +use crate::error::{WasmDppError, WasmDppResult}; +use crate::identifier::IdentifierWasm; +use crate::impl_wasm_type_info; +use dpp::serialization::{PlatformDeserializable, PlatformSerializable}; +use dpp::state_transition::shield_transition::ShieldTransition; +use dpp::state_transition::{StateTransition, StateTransitionLike}; +use wasm_bindgen::prelude::*; + +#[derive(Clone)] +#[wasm_bindgen(js_name = ShieldTransition)] +pub struct ShieldTransitionWasm(ShieldTransition); + +impl From for ShieldTransitionWasm { + fn from(v: ShieldTransition) -> Self { + ShieldTransitionWasm(v) + } +} + +impl From for ShieldTransition { + fn from(v: ShieldTransitionWasm) -> Self { + v.0 + } +} + +#[wasm_bindgen(js_class = ShieldTransition)] +impl ShieldTransitionWasm { + #[wasm_bindgen(js_name = getType)] + pub fn get_type(&self) -> u8 { + self.0.state_transition_type() as u8 + } + + /// Returns the inputs map as a JS object. + #[wasm_bindgen(js_name = getInputs)] + pub fn get_inputs(&self) -> WasmDppResult { + let inner = match &self.0 { + ShieldTransition::V0(v0) => &v0.inputs, + }; + serde_wasm_bindgen::to_value(inner).map_err(|e| WasmDppError::serialization(e.to_string())) + } + + /// Returns the serialized Orchard actions as a JS array. + #[wasm_bindgen(js_name = getActions)] + pub fn get_actions(&self) -> WasmDppResult { + let inner = match &self.0 { + ShieldTransition::V0(v0) => &v0.actions, + }; + serde_wasm_bindgen::to_value(inner).map_err(|e| WasmDppError::serialization(e.to_string())) + } + + /// Returns the shield amount (credits entering the pool). + #[wasm_bindgen(js_name = getAmount)] + pub fn get_amount(&self) -> u64 { + match &self.0 { + ShieldTransition::V0(v0) => v0.amount, + } + } + + /// Returns the anchor (32-byte Merkle root). + #[wasm_bindgen(js_name = getAnchor)] + pub fn get_anchor(&self) -> Vec { + match &self.0 { + ShieldTransition::V0(v0) => v0.anchor.to_vec(), + } + } + + /// Returns the Halo2 proof bytes. + #[wasm_bindgen(js_name = getProof)] + pub fn get_proof(&self) -> Vec { + match &self.0 { + ShieldTransition::V0(v0) => v0.proof.clone(), + } + } + + /// Returns the RedPallas binding signature (64 bytes). + #[wasm_bindgen(js_name = getBindingSignature)] + pub fn get_binding_signature(&self) -> Vec { + match &self.0 { + ShieldTransition::V0(v0) => v0.binding_signature.to_vec(), + } + } + + /// Returns the fee strategy as a JS value. + #[wasm_bindgen(js_name = getFeeStrategy)] + pub fn get_fee_strategy(&self) -> WasmDppResult { + let strategy = match &self.0 { + ShieldTransition::V0(v0) => &v0.fee_strategy, + }; + serde_wasm_bindgen::to_value(strategy) + .map_err(|e| WasmDppError::serialization(e.to_string())) + } + + /// Returns the user fee increase multiplier. + #[wasm_bindgen(js_name = getUserFeeIncrease)] + pub fn get_user_fee_increase(&self) -> u16 { + match &self.0 { + ShieldTransition::V0(v0) => v0.user_fee_increase, + } + } + + /// Returns the input witnesses as a JS value. + #[wasm_bindgen(js_name = getInputWitnesses)] + pub fn get_input_witnesses(&self) -> WasmDppResult { + let witnesses = match &self.0 { + ShieldTransition::V0(v0) => &v0.input_witnesses, + }; + serde_wasm_bindgen::to_value(witnesses) + .map_err(|e| WasmDppError::serialization(e.to_string())) + } + + #[wasm_bindgen(js_name = getModifiedDataIds)] + pub fn modified_data_ids(&self) -> Vec { + self.0 + .modified_data_ids() + .into_iter() + .map(IdentifierWasm::from) + .collect() + } + + #[wasm_bindgen(js_name = toObject)] + pub fn to_object(&self) -> WasmDppResult { + serde_wasm_bindgen::to_value(&self.0) + .map_err(|e| WasmDppError::serialization(e.to_string())) + } + + #[wasm_bindgen(js_name = toBytes)] + pub fn to_bytes(&self) -> WasmDppResult> { + Ok(PlatformSerializable::serialize_to_bytes( + &StateTransition::Shield(self.0.clone()), + )?) + } + + #[wasm_bindgen(js_name = fromBytes)] + pub fn from_bytes(bytes: Vec) -> WasmDppResult { + let st = StateTransition::deserialize_from_bytes(&bytes)?; + match st { + StateTransition::Shield(inner) => Ok(inner.into()), + _ => Err(WasmDppError::invalid_argument( + "Invalid state transition type: expected Shield", + )), + } + } + + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> WasmDppResult { + let json = serde_json::to_value(&self.0) + .map_err(|e| WasmDppError::serialization(e.to_string()))?; + serde_wasm_bindgen::to_value(&json).map_err(|e| WasmDppError::serialization(e.to_string())) + } + + #[wasm_bindgen(js_name = toStateTransition)] + pub fn to_state_transition(&self) -> crate::state_transitions::base::StateTransitionWasm { + StateTransition::Shield(self.0.clone()).into() + } +} + +impl_wasm_type_info!(ShieldTransitionWasm, ShieldTransition); diff --git a/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs b/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs new file mode 100644 index 00000000000..a1263a8603f --- /dev/null +++ b/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs @@ -0,0 +1,119 @@ +use crate::error::{WasmDppError, WasmDppResult}; +use crate::identifier::IdentifierWasm; +use crate::impl_wasm_type_info; +use dpp::serialization::{PlatformDeserializable, PlatformSerializable}; +use dpp::state_transition::shielded_transfer_transition::ShieldedTransferTransition; +use dpp::state_transition::{StateTransition, StateTransitionLike}; +use wasm_bindgen::prelude::*; + +#[derive(Clone)] +#[wasm_bindgen(js_name = ShieldedTransferTransition)] +pub struct ShieldedTransferTransitionWasm(ShieldedTransferTransition); + +impl From for ShieldedTransferTransitionWasm { + fn from(v: ShieldedTransferTransition) -> Self { + ShieldedTransferTransitionWasm(v) + } +} + +impl From for ShieldedTransferTransition { + fn from(v: ShieldedTransferTransitionWasm) -> Self { + v.0 + } +} + +#[wasm_bindgen(js_class = ShieldedTransferTransition)] +impl ShieldedTransferTransitionWasm { + #[wasm_bindgen(js_name = getType)] + pub fn get_type(&self) -> u8 { + self.0.state_transition_type() as u8 + } + + /// Returns the serialized Orchard actions as a JS array. + #[wasm_bindgen(js_name = getActions)] + pub fn get_actions(&self) -> WasmDppResult { + let inner = match &self.0 { + ShieldedTransferTransition::V0(v0) => &v0.actions, + }; + serde_wasm_bindgen::to_value(inner).map_err(|e| WasmDppError::serialization(e.to_string())) + } + + /// Returns the value balance (fee amount leaving the pool). + #[wasm_bindgen(js_name = getValueBalance)] + pub fn get_value_balance(&self) -> u64 { + match &self.0 { + ShieldedTransferTransition::V0(v0) => v0.value_balance, + } + } + + /// Returns the anchor (32-byte Merkle root). + #[wasm_bindgen(js_name = getAnchor)] + pub fn get_anchor(&self) -> Vec { + match &self.0 { + ShieldedTransferTransition::V0(v0) => v0.anchor.to_vec(), + } + } + + /// Returns the Halo2 proof bytes. + #[wasm_bindgen(js_name = getProof)] + pub fn get_proof(&self) -> Vec { + match &self.0 { + ShieldedTransferTransition::V0(v0) => v0.proof.clone(), + } + } + + /// Returns the RedPallas binding signature (64 bytes). + #[wasm_bindgen(js_name = getBindingSignature)] + pub fn get_binding_signature(&self) -> Vec { + match &self.0 { + ShieldedTransferTransition::V0(v0) => v0.binding_signature.to_vec(), + } + } + + #[wasm_bindgen(js_name = getModifiedDataIds)] + pub fn modified_data_ids(&self) -> Vec { + self.0 + .modified_data_ids() + .into_iter() + .map(IdentifierWasm::from) + .collect() + } + + #[wasm_bindgen(js_name = toObject)] + pub fn to_object(&self) -> WasmDppResult { + serde_wasm_bindgen::to_value(&self.0) + .map_err(|e| WasmDppError::serialization(e.to_string())) + } + + #[wasm_bindgen(js_name = toBytes)] + pub fn to_bytes(&self) -> WasmDppResult> { + Ok(PlatformSerializable::serialize_to_bytes( + &StateTransition::ShieldedTransfer(self.0.clone()), + )?) + } + + #[wasm_bindgen(js_name = fromBytes)] + pub fn from_bytes(bytes: Vec) -> WasmDppResult { + let st = StateTransition::deserialize_from_bytes(&bytes)?; + match st { + StateTransition::ShieldedTransfer(inner) => Ok(inner.into()), + _ => Err(WasmDppError::invalid_argument( + "Invalid state transition type: expected ShieldedTransfer", + )), + } + } + + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> WasmDppResult { + let json = serde_json::to_value(&self.0) + .map_err(|e| WasmDppError::serialization(e.to_string()))?; + serde_wasm_bindgen::to_value(&json).map_err(|e| WasmDppError::serialization(e.to_string())) + } + + #[wasm_bindgen(js_name = toStateTransition)] + pub fn to_state_transition(&self) -> crate::state_transitions::base::StateTransitionWasm { + StateTransition::ShieldedTransfer(self.0.clone()).into() + } +} + +impl_wasm_type_info!(ShieldedTransferTransitionWasm, ShieldedTransferTransition); diff --git a/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs b/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs new file mode 100644 index 00000000000..d41678c3ef3 --- /dev/null +++ b/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs @@ -0,0 +1,146 @@ +use crate::error::{WasmDppError, WasmDppResult}; +use crate::identifier::IdentifierWasm; +use crate::impl_wasm_type_info; +use dpp::serialization::{PlatformDeserializable, PlatformSerializable}; +use dpp::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; +use dpp::state_transition::{StateTransition, StateTransitionLike}; +use wasm_bindgen::prelude::*; + +#[derive(Clone)] +#[wasm_bindgen(js_name = ShieldedWithdrawalTransition)] +pub struct ShieldedWithdrawalTransitionWasm(ShieldedWithdrawalTransition); + +impl From for ShieldedWithdrawalTransitionWasm { + fn from(v: ShieldedWithdrawalTransition) -> Self { + ShieldedWithdrawalTransitionWasm(v) + } +} + +impl From for ShieldedWithdrawalTransition { + fn from(v: ShieldedWithdrawalTransitionWasm) -> Self { + v.0 + } +} + +#[wasm_bindgen(js_class = ShieldedWithdrawalTransition)] +impl ShieldedWithdrawalTransitionWasm { + #[wasm_bindgen(js_name = getType)] + pub fn get_type(&self) -> u8 { + self.0.state_transition_type() as u8 + } + + /// Returns the serialized Orchard actions as a JS array. + #[wasm_bindgen(js_name = getActions)] + pub fn get_actions(&self) -> WasmDppResult { + let inner = match &self.0 { + ShieldedWithdrawalTransition::V0(v0) => &v0.actions, + }; + serde_wasm_bindgen::to_value(inner).map_err(|e| WasmDppError::serialization(e.to_string())) + } + + /// Returns the unshielding amount. + #[wasm_bindgen(js_name = getUnshieldingAmount)] + pub fn get_unshielding_amount(&self) -> u64 { + match &self.0 { + ShieldedWithdrawalTransition::V0(v0) => v0.unshielding_amount, + } + } + + /// Returns the anchor (32-byte Merkle root). + #[wasm_bindgen(js_name = getAnchor)] + pub fn get_anchor(&self) -> Vec { + match &self.0 { + ShieldedWithdrawalTransition::V0(v0) => v0.anchor.to_vec(), + } + } + + /// Returns the Halo2 proof bytes. + #[wasm_bindgen(js_name = getProof)] + pub fn get_proof(&self) -> Vec { + match &self.0 { + ShieldedWithdrawalTransition::V0(v0) => v0.proof.clone(), + } + } + + /// Returns the RedPallas binding signature (64 bytes). + #[wasm_bindgen(js_name = getBindingSignature)] + pub fn get_binding_signature(&self) -> Vec { + match &self.0 { + ShieldedWithdrawalTransition::V0(v0) => v0.binding_signature.to_vec(), + } + } + + /// Returns the core fee per byte. + #[wasm_bindgen(js_name = getCoreFeePerByte)] + pub fn get_core_fee_per_byte(&self) -> u32 { + match &self.0 { + ShieldedWithdrawalTransition::V0(v0) => v0.core_fee_per_byte, + } + } + + /// Returns the pooling strategy as a u8. + #[wasm_bindgen(js_name = getPooling)] + pub fn get_pooling(&self) -> u8 { + match &self.0 { + ShieldedWithdrawalTransition::V0(v0) => v0.pooling as u8, + } + } + + /// Returns the output script (core address). + #[wasm_bindgen(js_name = getOutputScript)] + pub fn get_output_script(&self) -> Vec { + match &self.0 { + ShieldedWithdrawalTransition::V0(v0) => v0.output_script.as_bytes().to_vec(), + } + } + + #[wasm_bindgen(js_name = getModifiedDataIds)] + pub fn modified_data_ids(&self) -> Vec { + self.0 + .modified_data_ids() + .into_iter() + .map(IdentifierWasm::from) + .collect() + } + + #[wasm_bindgen(js_name = toObject)] + pub fn to_object(&self) -> WasmDppResult { + serde_wasm_bindgen::to_value(&self.0) + .map_err(|e| WasmDppError::serialization(e.to_string())) + } + + #[wasm_bindgen(js_name = toBytes)] + pub fn to_bytes(&self) -> WasmDppResult> { + Ok(PlatformSerializable::serialize_to_bytes( + &StateTransition::ShieldedWithdrawal(self.0.clone()), + )?) + } + + #[wasm_bindgen(js_name = fromBytes)] + pub fn from_bytes(bytes: Vec) -> WasmDppResult { + let st = StateTransition::deserialize_from_bytes(&bytes)?; + match st { + StateTransition::ShieldedWithdrawal(inner) => Ok(inner.into()), + _ => Err(WasmDppError::invalid_argument( + "Invalid state transition type: expected ShieldedWithdrawal", + )), + } + } + + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> WasmDppResult { + let json = serde_json::to_value(&self.0) + .map_err(|e| WasmDppError::serialization(e.to_string()))?; + serde_wasm_bindgen::to_value(&json).map_err(|e| WasmDppError::serialization(e.to_string())) + } + + #[wasm_bindgen(js_name = toStateTransition)] + pub fn to_state_transition(&self) -> crate::state_transitions::base::StateTransitionWasm { + StateTransition::ShieldedWithdrawal(self.0.clone()).into() + } +} + +impl_wasm_type_info!( + ShieldedWithdrawalTransitionWasm, + ShieldedWithdrawalTransition +); diff --git a/packages/wasm-dpp2/src/shielded/unshield_transition.rs b/packages/wasm-dpp2/src/shielded/unshield_transition.rs new file mode 100644 index 00000000000..bae28992d30 --- /dev/null +++ b/packages/wasm-dpp2/src/shielded/unshield_transition.rs @@ -0,0 +1,128 @@ +use crate::error::{WasmDppError, WasmDppResult}; +use crate::identifier::IdentifierWasm; +use crate::impl_wasm_type_info; +use dpp::serialization::{PlatformDeserializable, PlatformSerializable}; +use dpp::state_transition::unshield_transition::UnshieldTransition; +use dpp::state_transition::{StateTransition, StateTransitionLike}; +use wasm_bindgen::prelude::*; + +#[derive(Clone)] +#[wasm_bindgen(js_name = UnshieldTransition)] +pub struct UnshieldTransitionWasm(UnshieldTransition); + +impl From for UnshieldTransitionWasm { + fn from(v: UnshieldTransition) -> Self { + UnshieldTransitionWasm(v) + } +} + +impl From for UnshieldTransition { + fn from(v: UnshieldTransitionWasm) -> Self { + v.0 + } +} + +#[wasm_bindgen(js_class = UnshieldTransition)] +impl UnshieldTransitionWasm { + #[wasm_bindgen(js_name = getType)] + pub fn get_type(&self) -> u8 { + self.0.state_transition_type() as u8 + } + + /// Returns the output address as a JS value (serialized PlatformAddress). + #[wasm_bindgen(js_name = getOutputAddress)] + pub fn get_output_address(&self) -> WasmDppResult { + let addr = match &self.0 { + UnshieldTransition::V0(v0) => &v0.output_address, + }; + serde_wasm_bindgen::to_value(addr).map_err(|e| WasmDppError::serialization(e.to_string())) + } + + /// Returns the serialized Orchard actions as a JS array. + #[wasm_bindgen(js_name = getActions)] + pub fn get_actions(&self) -> WasmDppResult { + let inner = match &self.0 { + UnshieldTransition::V0(v0) => &v0.actions, + }; + serde_wasm_bindgen::to_value(inner).map_err(|e| WasmDppError::serialization(e.to_string())) + } + + /// Returns the unshielding amount. + #[wasm_bindgen(js_name = getUnshieldingAmount)] + pub fn get_unshielding_amount(&self) -> u64 { + match &self.0 { + UnshieldTransition::V0(v0) => v0.unshielding_amount, + } + } + + /// Returns the anchor (32-byte Merkle root). + #[wasm_bindgen(js_name = getAnchor)] + pub fn get_anchor(&self) -> Vec { + match &self.0 { + UnshieldTransition::V0(v0) => v0.anchor.to_vec(), + } + } + + /// Returns the Halo2 proof bytes. + #[wasm_bindgen(js_name = getProof)] + pub fn get_proof(&self) -> Vec { + match &self.0 { + UnshieldTransition::V0(v0) => v0.proof.clone(), + } + } + + /// Returns the RedPallas binding signature (64 bytes). + #[wasm_bindgen(js_name = getBindingSignature)] + pub fn get_binding_signature(&self) -> Vec { + match &self.0 { + UnshieldTransition::V0(v0) => v0.binding_signature.to_vec(), + } + } + + #[wasm_bindgen(js_name = getModifiedDataIds)] + pub fn modified_data_ids(&self) -> Vec { + self.0 + .modified_data_ids() + .into_iter() + .map(IdentifierWasm::from) + .collect() + } + + #[wasm_bindgen(js_name = toObject)] + pub fn to_object(&self) -> WasmDppResult { + serde_wasm_bindgen::to_value(&self.0) + .map_err(|e| WasmDppError::serialization(e.to_string())) + } + + #[wasm_bindgen(js_name = toBytes)] + pub fn to_bytes(&self) -> WasmDppResult> { + Ok(PlatformSerializable::serialize_to_bytes( + &StateTransition::Unshield(self.0.clone()), + )?) + } + + #[wasm_bindgen(js_name = fromBytes)] + pub fn from_bytes(bytes: Vec) -> WasmDppResult { + let st = StateTransition::deserialize_from_bytes(&bytes)?; + match st { + StateTransition::Unshield(inner) => Ok(inner.into()), + _ => Err(WasmDppError::invalid_argument( + "Invalid state transition type: expected Unshield", + )), + } + } + + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> WasmDppResult { + let json = serde_json::to_value(&self.0) + .map_err(|e| WasmDppError::serialization(e.to_string()))?; + serde_wasm_bindgen::to_value(&json).map_err(|e| WasmDppError::serialization(e.to_string())) + } + + #[wasm_bindgen(js_name = toStateTransition)] + pub fn to_state_transition(&self) -> crate::state_transitions::base::StateTransitionWasm { + StateTransition::Unshield(self.0.clone()).into() + } +} + +impl_wasm_type_info!(UnshieldTransitionWasm, UnshieldTransition); diff --git a/packages/wasm-dpp2/src/state_transitions/base/state_transition.rs b/packages/wasm-dpp2/src/state_transitions/base/state_transition.rs index 5c7625f979c..d2dbe0a927e 100644 --- a/packages/wasm-dpp2/src/state_transitions/base/state_transition.rs +++ b/packages/wasm-dpp2/src/state_transitions/base/state_transition.rs @@ -401,7 +401,7 @@ impl StateTransitionWasm { | ShieldedTransfer(_) | Unshield(_) | ShieldFromAssetLock(_) - | ShieldedWithdrawal(_) => todo!("shielded transitions not yet implemented"), + | ShieldedWithdrawal(_) => None, } } @@ -428,7 +428,7 @@ impl StateTransitionWasm { | ShieldedTransfer(_) | Unshield(_) | ShieldFromAssetLock(_) - | ShieldedWithdrawal(_) => todo!("shielded transitions not yet implemented"), + | ShieldedWithdrawal(_) => None, } } @@ -571,7 +571,9 @@ impl StateTransitionWasm { | Unshield(_) | ShieldFromAssetLock(_) | ShieldedWithdrawal(_) => { - todo!("shielded transitions not yet implemented") + return Err(WasmDppError::invalid_argument( + "Cannot set owner for shielded transition", + )); } }; @@ -646,7 +648,9 @@ impl StateTransitionWasm { | Unshield(_) | ShieldFromAssetLock(_) | ShieldedWithdrawal(_) => { - todo!("shielded transitions not yet implemented") + return Err(WasmDppError::invalid_argument( + "Cannot set identity contract nonce for shielded transition", + )); } }; @@ -741,7 +745,9 @@ impl StateTransitionWasm { | Unshield(_) | ShieldFromAssetLock(_) | ShieldedWithdrawal(_) => { - todo!("shielded transitions not yet implemented") + return Err(WasmDppError::invalid_argument( + "Cannot set identity nonce for shielded transition", + )); } }; From 65c02a666c61285216fa2dcf027ea178032293f0 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 13 Mar 2026 01:50:27 +0700 Subject: [PATCH 02/15] revert: remove js-dash-sdk and wasm-dpp changes from shielded PR js-dash-sdk uses wasm-dpp (not wasm-dpp2), so shielded bindings there belong in a separate effort. This PR focuses on wasm-sdk + wasm-dpp2 only. Co-Authored-By: Claude Opus 4.6 --- .../src/SDK/Client/Platform/Platform.ts | 26 ------------ .../Platform/methods/shielded/shield.ts | 38 ----------------- .../methods/shielded/shieldFromAssetLock.ts | 39 ------------------ .../methods/shielded/shieldedTransfer.ts | 41 ------------------- .../methods/shielded/shieldedWithdrawal.ts | 41 ------------------- .../Platform/methods/shielded/unshield.ts | 41 ------------------- .../state_transition/transition_types.rs | 13 +----- .../state_transition_factory.rs | 20 +++------ 8 files changed, 7 insertions(+), 252 deletions(-) delete mode 100644 packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shield.ts delete mode 100644 packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldFromAssetLock.ts delete mode 100644 packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldedTransfer.ts delete mode 100644 packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldedWithdrawal.ts delete mode 100644 packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/unshield.ts diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts index 0539c8365f8..873fe62c53d 100644 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts +++ b/packages/js-dash-sdk/src/SDK/Client/Platform/Platform.ts @@ -34,12 +34,6 @@ import resolveNameByRecord from './methods/names/resolveByRecord'; import searchName from './methods/names/search'; import broadcastStateTransition from './broadcastStateTransition'; -import shieldMethod from './methods/shielded/shield'; -import shieldedTransferMethod from './methods/shielded/shieldedTransfer'; -import unshieldMethod from './methods/shielded/unshield'; -import shieldFromAssetLockMethod from './methods/shielded/shieldFromAssetLock'; -import shieldedWithdrawalMethod from './methods/shielded/shieldedWithdrawal'; - import logger, { ConfigurableLogger } from '../../../logger'; import Fetcher from './Fetcher'; import NonceManager from './NonceManager/NonceManager'; @@ -104,14 +98,6 @@ interface DataContracts { history: Function, } -interface Shielded { - shield: Function, - shieldedTransfer: Function, - unshield: Function, - shieldFromAssetLock: Function, - shieldedWithdrawal: Function, -} - /** * Class for Dash Platform * @@ -131,11 +117,6 @@ export class Platform { public documents: Records; - /** - * Shielded pool operations (shield, transfer, unshield) - */ - public shielded: Shielded; - /** * @param {Function} get - get identities from the platform * @param {Function} register - register identities on the platform @@ -199,13 +180,6 @@ export class Platform { resolveByRecord: resolveNameByRecord.bind(this), search: searchName.bind(this), }; - this.shielded = { - shield: shieldMethod.bind(this), - shieldedTransfer: shieldedTransferMethod.bind(this), - unshield: unshieldMethod.bind(this), - shieldFromAssetLock: shieldFromAssetLockMethod.bind(this), - shieldedWithdrawal: shieldedWithdrawalMethod.bind(this), - }; this.identities = { register: registerIdentity.bind(this), get: getIdentity.bind(this), diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shield.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shield.ts deleted file mode 100644 index 5391cb205e8..00000000000 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shield.ts +++ /dev/null @@ -1,38 +0,0 @@ -import broadcastStateTransition from '../../broadcastStateTransition'; -import { Platform } from '../../Platform'; -import { IStateTransitionResult } from '../../IStateTransitionResult'; - -/** - * Broadcast a shield transition (transparent platform addresses -> shielded pool). - * - * The Orchard bundle, platform address witnesses, and full state transition - * must be built externally (e.g., via rs-sdk's `shield_funds()` or a native - * wallet library) and serialized to platform binary format. - * - * @param serializedTransition - Platform-serialized ShieldTransition bytes - * @returns Broadcast result - */ -export async function shield( - this: Platform, - serializedTransition: Uint8Array, -): Promise { - this.logger.debug('[Shielded#shield] Broadcasting shield transition'); - await this.initialize(); - - const { dpp } = this; - - const transition = dpp.stateTransition.createFromBuffer( - serializedTransition, - {}, - ); - - const result = await broadcastStateTransition(this, await transition, { - skipValidation: true, - }); - - this.logger.silly('[Shielded#shield] Broadcasted ShieldTransition'); - - return result; -} - -export default shield; diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldFromAssetLock.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldFromAssetLock.ts deleted file mode 100644 index 95e4b1cd1a8..00000000000 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldFromAssetLock.ts +++ /dev/null @@ -1,39 +0,0 @@ -import broadcastStateTransition from '../../broadcastStateTransition'; -import { Platform } from '../../Platform'; -import { IStateTransitionResult } from '../../IStateTransitionResult'; - -/** - * Broadcast a shield-from-asset-lock transition (core asset lock -> shielded pool). - * - * The Orchard bundle, asset lock proof, ECDSA signature, and full state - * transition must be built externally (e.g., via rs-sdk's - * `shield_from_asset_lock()` or a native wallet library) and serialized - * to platform binary format. - * - * @param serializedTransition - Platform-serialized ShieldFromAssetLockTransition bytes - * @returns Broadcast result - */ -export async function shieldFromAssetLock( - this: Platform, - serializedTransition: Uint8Array, -): Promise { - this.logger.debug('[Shielded#shieldFromAssetLock] Broadcasting shield from asset lock'); - await this.initialize(); - - const { dpp } = this; - - const transition = dpp.stateTransition.createFromBuffer( - serializedTransition, - {}, - ); - - const result = await broadcastStateTransition(this, await transition, { - skipValidation: true, - }); - - this.logger.silly('[Shielded#shieldFromAssetLock] Broadcasted ShieldFromAssetLockTransition'); - - return result; -} - -export default shieldFromAssetLock; diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldedTransfer.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldedTransfer.ts deleted file mode 100644 index 81f68a4aaff..00000000000 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldedTransfer.ts +++ /dev/null @@ -1,41 +0,0 @@ -import broadcastStateTransition from '../../broadcastStateTransition'; -import { Platform } from '../../Platform'; -import { IStateTransitionResult } from '../../IStateTransitionResult'; - -/** - * Broadcast a shielded transfer (shielded-to-shielded) transition. - * - * The Orchard bundle and full state transition must be built externally - * (e.g., via rs-sdk's `transfer_shielded()` or a native wallet library) - * and serialized to platform binary format before passing here. - * - * Authentication is provided entirely by Orchard spend authorization - * signatures within the bundle — no identity signing needed. - * - * @param serializedTransition - Platform-serialized ShieldedTransferTransition bytes - * @returns Broadcast result - */ -export async function shieldedTransfer( - this: Platform, - serializedTransition: Uint8Array, -): Promise { - this.logger.debug('[Shielded#shieldedTransfer] Broadcasting shielded transfer'); - await this.initialize(); - - const { dpp } = this; - - const transition = dpp.stateTransition.createFromBuffer( - serializedTransition, - {}, - ); - - const result = await broadcastStateTransition(this, await transition, { - skipValidation: true, - }); - - this.logger.silly('[Shielded#shieldedTransfer] Broadcasted ShieldedTransferTransition'); - - return result; -} - -export default shieldedTransfer; diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldedWithdrawal.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldedWithdrawal.ts deleted file mode 100644 index fb6e47b63bf..00000000000 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/shieldedWithdrawal.ts +++ /dev/null @@ -1,41 +0,0 @@ -import broadcastStateTransition from '../../broadcastStateTransition'; -import { Platform } from '../../Platform'; -import { IStateTransitionResult } from '../../IStateTransitionResult'; - -/** - * Broadcast a shielded withdrawal transition (shielded pool -> core L1 address). - * - * The Orchard bundle and full state transition must be built externally - * (e.g., via rs-sdk's `withdraw_shielded()` or a native wallet library) - * and serialized to platform binary format. - * - * The platform sighash binds `outputScript || amount` to prevent - * an attacker from substituting a different L1 destination or amount. - * - * @param serializedTransition - Platform-serialized ShieldedWithdrawalTransition bytes - * @returns Broadcast result - */ -export async function shieldedWithdrawal( - this: Platform, - serializedTransition: Uint8Array, -): Promise { - this.logger.debug('[Shielded#shieldedWithdrawal] Broadcasting shielded withdrawal'); - await this.initialize(); - - const { dpp } = this; - - const transition = dpp.stateTransition.createFromBuffer( - serializedTransition, - {}, - ); - - const result = await broadcastStateTransition(this, await transition, { - skipValidation: true, - }); - - this.logger.silly('[Shielded#shieldedWithdrawal] Broadcasted ShieldedWithdrawalTransition'); - - return result; -} - -export default shieldedWithdrawal; diff --git a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/unshield.ts b/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/unshield.ts deleted file mode 100644 index 95eb4636601..00000000000 --- a/packages/js-dash-sdk/src/SDK/Client/Platform/methods/shielded/unshield.ts +++ /dev/null @@ -1,41 +0,0 @@ -import broadcastStateTransition from '../../broadcastStateTransition'; -import { Platform } from '../../Platform'; -import { IStateTransitionResult } from '../../IStateTransitionResult'; - -/** - * Broadcast an unshield transition (shielded pool -> platform address). - * - * The Orchard bundle and full state transition must be built externally - * (e.g., via rs-sdk's `unshield_funds()` or a native wallet library) - * and serialized to platform binary format. - * - * The platform sighash binds `outputAddress || amount` to prevent - * an attacker from substituting a different destination or amount. - * - * @param serializedTransition - Platform-serialized UnshieldTransition bytes - * @returns Broadcast result - */ -export async function unshield( - this: Platform, - serializedTransition: Uint8Array, -): Promise { - this.logger.debug('[Shielded#unshield] Broadcasting unshield transition'); - await this.initialize(); - - const { dpp } = this; - - const transition = dpp.stateTransition.createFromBuffer( - serializedTransition, - {}, - ); - - const result = await broadcastStateTransition(this, await transition, { - skipValidation: true, - }); - - this.logger.silly('[Shielded#unshield] Broadcasted UnshieldTransition'); - - return result; -} - -export default unshield; diff --git a/packages/wasm-dpp/src/identity/state_transition/transition_types.rs b/packages/wasm-dpp/src/identity/state_transition/transition_types.rs index 0e30aacee89..29d2274dffe 100644 --- a/packages/wasm-dpp/src/identity/state_transition/transition_types.rs +++ b/packages/wasm-dpp/src/identity/state_transition/transition_types.rs @@ -19,11 +19,6 @@ pub enum StateTransitionTypeWasm { AddressFundsTransfer = 12, AddressFundingFromAssetLock = 13, AddressCreditWithdrawal = 14, - Shield = 15, - ShieldedTransfer = 16, - Unshield = 17, - ShieldFromAssetLock = 18, - ShieldedWithdrawal = 19, } impl From for StateTransitionTypeWasm { @@ -60,13 +55,7 @@ impl From for StateTransitionTypeWasm { StateTransitionType::AddressCreditWithdrawal => { StateTransitionTypeWasm::AddressCreditWithdrawal } - StateTransitionType::Shield => StateTransitionTypeWasm::Shield, - StateTransitionType::ShieldedTransfer => StateTransitionTypeWasm::ShieldedTransfer, - StateTransitionType::Unshield => StateTransitionTypeWasm::Unshield, - StateTransitionType::ShieldFromAssetLock => { - StateTransitionTypeWasm::ShieldFromAssetLock - } - StateTransitionType::ShieldedWithdrawal => StateTransitionTypeWasm::ShieldedWithdrawal, + _ => todo!("shielded state transition types not yet implemented in wasm"), } } } diff --git a/packages/wasm-dpp/src/state_transition/state_transition_factory.rs b/packages/wasm-dpp/src/state_transition/state_transition_factory.rs index 689b3d3501d..2b929d17a15 100644 --- a/packages/wasm-dpp/src/state_transition/state_transition_factory.rs +++ b/packages/wasm-dpp/src/state_transition/state_transition_factory.rs @@ -79,20 +79,12 @@ impl StateTransitionFactoryWasm { StateTransition::AddressCreditWithdrawal(st) => { serde_wasm_bindgen::to_value(&st).map_err(|e| JsValue::from(e.to_string())) } - StateTransition::Shield(st) => { - serde_wasm_bindgen::to_value(&st).map_err(|e| JsValue::from(e.to_string())) - } - StateTransition::ShieldedTransfer(st) => { - serde_wasm_bindgen::to_value(&st).map_err(|e| JsValue::from(e.to_string())) - } - StateTransition::Unshield(st) => { - serde_wasm_bindgen::to_value(&st).map_err(|e| JsValue::from(e.to_string())) - } - StateTransition::ShieldFromAssetLock(st) => { - serde_wasm_bindgen::to_value(&st).map_err(|e| JsValue::from(e.to_string())) - } - StateTransition::ShieldedWithdrawal(st) => { - serde_wasm_bindgen::to_value(&st).map_err(|e| JsValue::from(e.to_string())) + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented in state_transition_factory") } }, Err(dpp::ProtocolError::StateTransitionError(e)) => match e { From 8b71f681eb07125fd43ce76c1e2586c9c380a65e Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 13 Mar 2026 01:53:19 +0700 Subject: [PATCH 03/15] feat(wasm-sdk): add shielded pool query methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds 10 query methods to WasmSdk for the shielded pool: - getShieldedPoolState / WithProofInfo — total shielded balance - getShieldedEncryptedNotes / WithProofInfo — paginated encrypted notes - getShieldedAnchors / WithProofInfo — valid spend anchors - getMostRecentShieldedAnchor / WithProofInfo — latest anchor - getShieldedNullifiers / WithProofInfo — nullifier spent status Also adds ShieldedEncryptedNoteWasm and ShieldedNullifierStatusWasm wrapper types with TypeScript interface declarations. Co-Authored-By: Claude Opus 4.6 --- packages/wasm-sdk/src/lib.rs | 1 + packages/wasm-sdk/src/queries/mod.rs | 1 + packages/wasm-sdk/src/queries/shielded.rs | 351 ++++++++++++++++++++++ 3 files changed, 353 insertions(+) create mode 100644 packages/wasm-sdk/src/queries/shielded.rs diff --git a/packages/wasm-sdk/src/lib.rs b/packages/wasm-sdk/src/lib.rs index 14a662bb7c3..592f7e01523 100644 --- a/packages/wasm-sdk/src/lib.rs +++ b/packages/wasm-sdk/src/lib.rs @@ -15,6 +15,7 @@ pub mod wallet; pub use dpns::*; pub use error::{WasmSdkError, WasmSdkErrorKind}; pub use queries::{ + shielded::{ShieldedEncryptedNoteWasm, ShieldedNullifierStatusWasm}, PlatformAddressInfoWasm, ProofInfoWasm, ProofMetadataResponseWasm, ResponseMetadataWasm, }; pub use state_transitions::identity as state_transition_identity; diff --git a/packages/wasm-sdk/src/queries/mod.rs b/packages/wasm-sdk/src/queries/mod.rs index 14e9eef5dc9..8554dd2dac5 100644 --- a/packages/wasm-sdk/src/queries/mod.rs +++ b/packages/wasm-sdk/src/queries/mod.rs @@ -5,6 +5,7 @@ pub mod epoch; pub mod group; pub mod identity; pub mod protocol; +pub mod shielded; pub mod system; pub mod token; pub(crate) mod utils; diff --git a/packages/wasm-sdk/src/queries/shielded.rs b/packages/wasm-sdk/src/queries/shielded.rs new file mode 100644 index 00000000000..0759b5ab788 --- /dev/null +++ b/packages/wasm-sdk/src/queries/shielded.rs @@ -0,0 +1,351 @@ +use crate::error::WasmSdkError; +use crate::impl_wasm_serde_conversions; +use crate::queries::ProofMetadataResponseWasm; +use crate::sdk::WasmSdk; +use js_sys::{Array, BigInt, Uint8Array}; +use serde::{Deserialize, Serialize}; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsValue; +use wasm_dpp2::serialization::bytes_b64; + +// ── TypeScript interface declarations ────────────────────────────────── + +#[wasm_bindgen(typescript_custom_section)] +const TS_SHIELDED: &'static str = r#" +export interface IShieldedEncryptedNote { + readonly cmx: Uint8Array; + readonly nullifier: Uint8Array; + readonly encryptedNote: Uint8Array; +} + +export interface IShieldedNullifierStatus { + readonly nullifier: Uint8Array; + readonly isSpent: boolean; +} +"#; + +// ── Wrapper types ────────────────────────────────────────────────────── + +#[wasm_bindgen(js_name = "ShieldedEncryptedNote")] +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ShieldedEncryptedNoteWasm { + #[serde(with = "bytes_b64")] + cmx: Vec, + #[serde(with = "bytes_b64")] + nullifier: Vec, + #[serde(with = "bytes_b64")] + encrypted_note: Vec, +} + +#[wasm_bindgen(js_class = ShieldedEncryptedNote)] +impl ShieldedEncryptedNoteWasm { + #[wasm_bindgen(getter)] + pub fn cmx(&self) -> Uint8Array { + Uint8Array::from(self.cmx.as_slice()) + } + + #[wasm_bindgen(getter)] + pub fn nullifier(&self) -> Uint8Array { + Uint8Array::from(self.nullifier.as_slice()) + } + + #[wasm_bindgen(getter = encryptedNote)] + pub fn encrypted_note(&self) -> Uint8Array { + Uint8Array::from(self.encrypted_note.as_slice()) + } +} +impl_wasm_serde_conversions!(ShieldedEncryptedNoteWasm, ShieldedEncryptedNote); + +#[wasm_bindgen(js_name = "ShieldedNullifierStatus")] +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ShieldedNullifierStatusWasm { + #[serde(with = "bytes_b64")] + nullifier: Vec, + is_spent: bool, +} + +#[wasm_bindgen(js_class = ShieldedNullifierStatus)] +impl ShieldedNullifierStatusWasm { + #[wasm_bindgen(getter)] + pub fn nullifier(&self) -> Uint8Array { + Uint8Array::from(self.nullifier.as_slice()) + } + + #[wasm_bindgen(getter = isSpent)] + pub fn is_spent(&self) -> bool { + self.is_spent + } +} +impl_wasm_serde_conversions!(ShieldedNullifierStatusWasm, ShieldedNullifierStatus); + +// ── Query methods ────────────────────────────────────────────────────── + +#[wasm_bindgen] +impl WasmSdk { + /// Returns the total shielded pool balance as a BigInt. + #[wasm_bindgen(js_name = "getShieldedPoolState")] + pub async fn get_shielded_pool_state(&self) -> Result { + use dash_sdk::platform::Fetch; + use drive_proof_verifier::types::{NoParamQuery, ShieldedPoolState}; + + let result = ShieldedPoolState::fetch(self.as_ref(), NoParamQuery {}).await?; + let balance = result.map(|s| s.0).unwrap_or(0); + Ok(BigInt::from(balance)) + } + + /// Fetches encrypted notes from the shielded pool, paginated. + #[wasm_bindgen( + js_name = "getShieldedEncryptedNotes", + unchecked_return_type = "Array" + )] + pub async fn get_shielded_encrypted_notes( + &self, + #[wasm_bindgen(js_name = "startIndex")] start_index: u64, + count: u32, + ) -> Result { + use dash_sdk::platform::Fetch; + use drive_proof_verifier::types::{ShieldedEncryptedNotes, ShieldedEncryptedNotesQuery}; + + let query = ShieldedEncryptedNotesQuery { start_index, count }; + let result = ShieldedEncryptedNotes::fetch(self.as_ref(), query).await?; + + let array = Array::new(); + if let Some(notes) = result { + for note in notes.0 { + array.push(&JsValue::from(ShieldedEncryptedNoteWasm { + cmx: note.cmx, + nullifier: note.nullifier, + encrypted_note: note.encrypted_note, + })); + } + } + Ok(array) + } + + /// Returns all valid anchors for building Orchard spend proofs. + #[wasm_bindgen( + js_name = "getShieldedAnchors", + unchecked_return_type = "Array" + )] + pub async fn get_shielded_anchors(&self) -> Result { + use dash_sdk::platform::Fetch; + use drive_proof_verifier::types::{NoParamQuery, ShieldedAnchors}; + + let result = ShieldedAnchors::fetch(self.as_ref(), NoParamQuery {}).await?; + + let array = Array::new(); + if let Some(anchors) = result { + for anchor in anchors.0 { + array.push(&Uint8Array::from(anchor.as_slice()).into()); + } + } + Ok(array) + } + + /// Returns the most recent shielded anchor (32 bytes), or undefined if none exists. + #[wasm_bindgen(js_name = "getMostRecentShieldedAnchor")] + pub async fn get_most_recent_shielded_anchor(&self) -> Result { + use dash_sdk::platform::Fetch; + use drive_proof_verifier::types::{MostRecentShieldedAnchor, NoParamQuery}; + + let result = MostRecentShieldedAnchor::fetch(self.as_ref(), NoParamQuery {}).await?; + match result { + Some(anchor) => Ok(Uint8Array::from(anchor.0.as_slice()).into()), + None => Ok(JsValue::UNDEFINED), + } + } + + /// Checks the spent/unspent status of one or more nullifiers. + #[wasm_bindgen( + js_name = "getShieldedNullifiers", + unchecked_return_type = "Array" + )] + pub async fn get_shielded_nullifiers( + &self, + nullifiers: js_sys::Array, + ) -> Result { + use dash_sdk::platform::Fetch; + use drive_proof_verifier::types::{ShieldedNullifierStatuses, ShieldedNullifiersQuery}; + + let nullifier_arrays: Vec<[u8; 32]> = nullifiers + .iter() + .map(|n| { + let uint8_arr = Uint8Array::new(&n); + let bytes = uint8_arr.to_vec(); + let mut arr = [0u8; 32]; + let len = bytes.len().min(32); + arr[..len].copy_from_slice(&bytes[..len]); + arr + }) + .collect(); + + let query = ShieldedNullifiersQuery(nullifier_arrays); + let result = ShieldedNullifierStatuses::fetch(self.as_ref(), query).await?; + + let array = Array::new(); + if let Some(statuses) = result { + for status in statuses.0 { + array.push(&JsValue::from(ShieldedNullifierStatusWasm { + nullifier: status.nullifier.to_vec(), + is_spent: status.is_spent, + })); + } + } + Ok(array) + } + + // ── Proof variants ───────────────────────────────────────────────── + + #[wasm_bindgen( + js_name = "getShieldedPoolStateWithProofInfo", + unchecked_return_type = "ProofMetadataResponseTyped" + )] + pub async fn get_shielded_pool_state_with_proof_info( + &self, + ) -> Result { + use dash_sdk::platform::Fetch; + use drive_proof_verifier::types::{NoParamQuery, ShieldedPoolState}; + + let (result, metadata, proof) = + ShieldedPoolState::fetch_with_metadata_and_proof(self.as_ref(), NoParamQuery {}, None) + .await?; + + let data = result + .map(|s| JsValue::from(BigInt::from(s.0))) + .unwrap_or(JsValue::UNDEFINED); + + Ok(ProofMetadataResponseWasm::from_sdk_parts( + data, metadata, proof, + )) + } + + #[wasm_bindgen( + js_name = "getShieldedEncryptedNotesWithProofInfo", + unchecked_return_type = "ProofMetadataResponseTyped>" + )] + pub async fn get_shielded_encrypted_notes_with_proof_info( + &self, + #[wasm_bindgen(js_name = "startIndex")] start_index: u64, + count: u32, + ) -> Result { + use dash_sdk::platform::Fetch; + use drive_proof_verifier::types::{ShieldedEncryptedNotes, ShieldedEncryptedNotesQuery}; + + let query = ShieldedEncryptedNotesQuery { start_index, count }; + let (result, metadata, proof) = + ShieldedEncryptedNotes::fetch_with_metadata_and_proof(self.as_ref(), query, None) + .await?; + + let array = Array::new(); + if let Some(notes) = result { + for note in notes.0 { + array.push(&JsValue::from(ShieldedEncryptedNoteWasm { + cmx: note.cmx, + nullifier: note.nullifier, + encrypted_note: note.encrypted_note, + })); + } + } + + Ok(ProofMetadataResponseWasm::from_sdk_parts( + array, metadata, proof, + )) + } + + #[wasm_bindgen( + js_name = "getShieldedAnchorsWithProofInfo", + unchecked_return_type = "ProofMetadataResponseTyped>" + )] + pub async fn get_shielded_anchors_with_proof_info( + &self, + ) -> Result { + use dash_sdk::platform::Fetch; + use drive_proof_verifier::types::{NoParamQuery, ShieldedAnchors}; + + let (result, metadata, proof) = + ShieldedAnchors::fetch_with_metadata_and_proof(self.as_ref(), NoParamQuery {}, None) + .await?; + + let array = Array::new(); + if let Some(anchors) = result { + for anchor in anchors.0 { + array.push(&Uint8Array::from(anchor.as_slice()).into()); + } + } + + Ok(ProofMetadataResponseWasm::from_sdk_parts( + array, metadata, proof, + )) + } + + #[wasm_bindgen( + js_name = "getMostRecentShieldedAnchorWithProofInfo", + unchecked_return_type = "ProofMetadataResponseTyped" + )] + pub async fn get_most_recent_shielded_anchor_with_proof_info( + &self, + ) -> Result { + use dash_sdk::platform::Fetch; + use drive_proof_verifier::types::{MostRecentShieldedAnchor, NoParamQuery}; + + let (result, metadata, proof) = MostRecentShieldedAnchor::fetch_with_metadata_and_proof( + self.as_ref(), + NoParamQuery {}, + None, + ) + .await?; + + let data = result + .map(|a| JsValue::from(Uint8Array::from(a.0.as_slice()))) + .unwrap_or(JsValue::UNDEFINED); + + Ok(ProofMetadataResponseWasm::from_sdk_parts( + data, metadata, proof, + )) + } + + #[wasm_bindgen( + js_name = "getShieldedNullifiersWithProofInfo", + unchecked_return_type = "ProofMetadataResponseTyped>" + )] + pub async fn get_shielded_nullifiers_with_proof_info( + &self, + nullifiers: js_sys::Array, + ) -> Result { + use dash_sdk::platform::Fetch; + use drive_proof_verifier::types::{ShieldedNullifierStatuses, ShieldedNullifiersQuery}; + + let nullifier_arrays: Vec<[u8; 32]> = nullifiers + .iter() + .map(|n| { + let uint8_arr = Uint8Array::new(&n); + let bytes = uint8_arr.to_vec(); + let mut arr = [0u8; 32]; + let len = bytes.len().min(32); + arr[..len].copy_from_slice(&bytes[..len]); + arr + }) + .collect(); + + let query = ShieldedNullifiersQuery(nullifier_arrays); + let (result, metadata, proof) = + ShieldedNullifierStatuses::fetch_with_metadata_and_proof(self.as_ref(), query, None) + .await?; + + let array = Array::new(); + if let Some(statuses) = result { + for status in statuses.0 { + array.push(&JsValue::from(ShieldedNullifierStatusWasm { + nullifier: status.nullifier.to_vec(), + is_spent: status.is_spent, + })); + } + } + + Ok(ProofMetadataResponseWasm::from_sdk_parts( + array, metadata, proof, + )) + } +} From 325634632f4b3aaf735b9b09f0d04ace24cb843a Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 13 Mar 2026 02:01:00 +0700 Subject: [PATCH 04/15] feat(wasm-dpp2): implement shielded proof result wrappers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace todo!() panics for shielded proof result variants with proper WASM wrapper types: - VerifiedAssetLockConsumed (status + credit values) - VerifiedShieldedNullifiers (nullifier → isSpent map) - VerifiedShieldedNullifiersWithAddressInfos - VerifiedShieldedNullifiersWithWithdrawalDocument This enables broadcasting shielded state transitions through wasm-sdk without panicking on proof verification results. Co-Authored-By: Claude Opus 4.6 --- .../src/state_transitions/proof_result.rs | 215 +++++++++++++++++- 1 file changed, 209 insertions(+), 6 deletions(-) diff --git a/packages/wasm-dpp2/src/state_transitions/proof_result.rs b/packages/wasm-dpp2/src/state_transitions/proof_result.rs index 7faa3284dfb..0d13eb17dc2 100644 --- a/packages/wasm-dpp2/src/state_transitions/proof_result.rs +++ b/packages/wasm-dpp2/src/state_transitions/proof_result.rs @@ -57,7 +57,11 @@ export type StateTransitionProofResultType = | VerifiedNextDistribution | VerifiedAddressInfos | VerifiedIdentityFullWithAddressInfos - | VerifiedIdentityWithAddressInfos; + | VerifiedIdentityWithAddressInfos + | VerifiedAssetLockConsumed + | VerifiedShieldedNullifiers + | VerifiedShieldedNullifiersWithAddressInfos + | VerifiedShieldedNullifiersWithWithdrawalDocument; "#; #[wasm_bindgen] @@ -724,6 +728,15 @@ fn build_address_infos_map( })) } +/// Helper to build `Map` from shielded nullifier results. +fn build_nullifier_map(nullifiers: Vec<(Vec, bool)>) -> Map { + Map::from_entries(nullifiers.into_iter().map(|(nullifier, is_spent)| { + let key: JsValue = hex::encode(&nullifier).into(); + let val: JsValue = is_spent.into(); + (key, val) + })) +} + fn action_status_to_string(status: dpp::group::group_action_status::GroupActionStatus) -> String { match status { dpp::group::group_action_status::GroupActionStatus::ActionActive => { @@ -757,6 +770,148 @@ impl VerifiedShieldedPoolStateWasm { } } } + +// --- VerifiedAssetLockConsumed --- + +#[wasm_bindgen(js_name = "VerifiedAssetLockConsumed")] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VerifiedAssetLockConsumedWasm { + #[wasm_bindgen(getter_with_clone)] + pub status: String, + initial_credit_value: Option, + remaining_credit_value: Option, +} + +#[wasm_bindgen(js_class = VerifiedAssetLockConsumed)] +impl VerifiedAssetLockConsumedWasm { + #[wasm_bindgen(getter, js_name = "initialCreditValue")] + pub fn initial_credit_value(&self) -> JsValue { + match self.initial_credit_value { + Some(v) => BigInt::from(v).into(), + None => JsValue::undefined(), + } + } + + #[wasm_bindgen(getter, js_name = "remainingCreditValue")] + pub fn remaining_credit_value(&self) -> JsValue { + match self.remaining_credit_value { + Some(v) => BigInt::from(v).into(), + None => JsValue::undefined(), + } + } +} + +impl_wasm_type_info!(VerifiedAssetLockConsumedWasm, VerifiedAssetLockConsumed); +impl_wasm_conversions_serde!(VerifiedAssetLockConsumedWasm, VerifiedAssetLockConsumed); + +// --- VerifiedShieldedNullifiers --- + +#[wasm_bindgen(js_name = "VerifiedShieldedNullifiers")] +#[derive(Clone)] +pub struct VerifiedShieldedNullifiersWasm { + nullifiers: Map, // Map +} + +#[wasm_bindgen(js_class = VerifiedShieldedNullifiers)] +impl VerifiedShieldedNullifiersWasm { + #[wasm_bindgen(getter)] + pub fn nullifiers(&self) -> Map { + self.nullifiers.clone() + } + + #[wasm_bindgen(js_name = toObject)] + pub fn to_object(&self) -> JsValue { + js_obj(&[("nullifiers", self.nullifiers.clone().into())]) + } + + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> JsValue { + self.to_object() + } +} + +impl_wasm_type_info!(VerifiedShieldedNullifiersWasm, VerifiedShieldedNullifiers); + +// --- VerifiedShieldedNullifiersWithAddressInfos --- + +#[wasm_bindgen(js_name = "VerifiedShieldedNullifiersWithAddressInfos")] +#[derive(Clone)] +pub struct VerifiedShieldedNullifiersWithAddressInfosWasm { + nullifiers: Map, + address_infos: Map, +} + +#[wasm_bindgen(js_class = VerifiedShieldedNullifiersWithAddressInfos)] +impl VerifiedShieldedNullifiersWithAddressInfosWasm { + #[wasm_bindgen(getter)] + pub fn nullifiers(&self) -> Map { + self.nullifiers.clone() + } + + #[wasm_bindgen(getter = "addressInfos")] + pub fn address_infos(&self) -> Map { + self.address_infos.clone() + } + + #[wasm_bindgen(js_name = toObject)] + pub fn to_object(&self) -> JsValue { + js_obj(&[ + ("nullifiers", self.nullifiers.clone().into()), + ("addressInfos", self.address_infos.clone().into()), + ]) + } + + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> JsValue { + self.to_object() + } +} + +impl_wasm_type_info!( + VerifiedShieldedNullifiersWithAddressInfosWasm, + VerifiedShieldedNullifiersWithAddressInfos +); + +// --- VerifiedShieldedNullifiersWithWithdrawalDocument --- + +#[wasm_bindgen(js_name = "VerifiedShieldedNullifiersWithWithdrawalDocument")] +#[derive(Clone)] +pub struct VerifiedShieldedNullifiersWithWithdrawalDocumentWasm { + nullifiers: Map, + documents: Map, +} + +#[wasm_bindgen(js_class = VerifiedShieldedNullifiersWithWithdrawalDocument)] +impl VerifiedShieldedNullifiersWithWithdrawalDocumentWasm { + #[wasm_bindgen(getter)] + pub fn nullifiers(&self) -> Map { + self.nullifiers.clone() + } + + #[wasm_bindgen(getter)] + pub fn documents(&self) -> Map { + self.documents.clone() + } + + #[wasm_bindgen(js_name = toObject)] + pub fn to_object(&self) -> JsValue { + js_obj(&[ + ("nullifiers", self.nullifiers.clone().into()), + ("documents", self.documents.clone().into()), + ]) + } + + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> JsValue { + self.to_object() + } +} + +impl_wasm_type_info!( + VerifiedShieldedNullifiersWithWithdrawalDocumentWasm, + VerifiedShieldedNullifiersWithWithdrawalDocument +); /// Convert a Rust `StateTransitionProofResult` into the corresponding typed /// WASM wrapper, ready to be returned to JavaScript. pub fn convert_proof_result( @@ -918,11 +1073,59 @@ pub fn convert_proof_result( .into() } - StateTransitionProofResult::VerifiedAssetLockConsumed(_) - | StateTransitionProofResult::VerifiedShieldedNullifiers(_) - | StateTransitionProofResult::VerifiedShieldedNullifiersWithAddressInfos(_, _) - | StateTransitionProofResult::VerifiedShieldedNullifiersWithWithdrawalDocument(_, _) => { - todo!("shielded proof results not yet implemented in wasm") + StateTransitionProofResult::VerifiedAssetLockConsumed(info) => { + use dpp::asset_lock::StoredAssetLockInfo; + use dpp::asset_lock::reduced_asset_lock_value::AssetLockValueGettersV0; + let (status, initial, remaining) = match info { + StoredAssetLockInfo::FullyConsumed => ("FullyConsumed".to_string(), None, None), + StoredAssetLockInfo::PartiallyConsumed(val) => ( + "PartiallyConsumed".to_string(), + Some(val.initial_credit_value()), + Some(val.remaining_credit_value()), + ), + StoredAssetLockInfo::NotPresent => ("NotPresent".to_string(), None, None), + }; + VerifiedAssetLockConsumedWasm { + status, + initial_credit_value: initial, + remaining_credit_value: remaining, + } + .into() + } + + StateTransitionProofResult::VerifiedShieldedNullifiers(nullifiers) => { + VerifiedShieldedNullifiersWasm { + nullifiers: build_nullifier_map(nullifiers), + } + .into() + } + + StateTransitionProofResult::VerifiedShieldedNullifiersWithAddressInfos( + nullifiers, + infos, + ) => VerifiedShieldedNullifiersWithAddressInfosWasm { + nullifiers: build_nullifier_map(nullifiers), + address_infos: build_address_infos_map(infos), + } + .into(), + + StateTransitionProofResult::VerifiedShieldedNullifiersWithWithdrawalDocument( + nullifiers, + docs, + ) => { + let doc_map = Map::from_entries(docs.into_iter().map(|(id, maybe_doc)| { + let key: JsValue = IdentifierWasm::from(id).to_base58().into(); + let val: JsValue = match maybe_doc { + Some(doc) => doc_to_wasm(doc).into(), + None => JsValue::undefined(), + }; + (key, val) + })); + VerifiedShieldedNullifiersWithWithdrawalDocumentWasm { + nullifiers: build_nullifier_map(nullifiers), + documents: doc_map, + } + .into() } }; From d08e087591ef54391decba03581fd7c59b6d96b8 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 13 Mar 2026 02:05:54 +0700 Subject: [PATCH 05/15] fix(wasm-sdk): correct computePlatformSighash doc for extraData format The doc comment incorrectly stated that extraData includes amount for unshield and shielded withdrawal transitions. The actual rs-dpp implementation only uses the address/script bytes without amount. Co-Authored-By: Claude Opus 4.6 --- packages/wasm-dpp2/src/shielded/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/wasm-dpp2/src/shielded/mod.rs b/packages/wasm-dpp2/src/shielded/mod.rs index 96bf9b92ad4..3514b44eb50 100644 --- a/packages/wasm-dpp2/src/shielded/mod.rs +++ b/packages/wasm-dpp2/src/shielded/mod.rs @@ -18,8 +18,8 @@ use wasm_bindgen::prelude::wasm_bindgen; /// `sighash = SHA-256("DashPlatformSighash" || bundleCommitment || extraData)` /// /// - For shield and shielded_transfer transitions, `extraData` should be empty. -/// - For unshield transitions, `extraData` = `outputAddress || amount (LE u64)`. -/// - For shielded withdrawal transitions, `extraData` = `outputScript || amount (LE u64)`. +/// - For unshield transitions, `extraData` = serialized `outputAddress` bytes. +/// - For shielded withdrawal transitions, `extraData` = `outputScript` bytes. #[wasm_bindgen(js_name = computePlatformSighash)] pub fn compute_platform_sighash_wasm( bundle_commitment: &[u8], From 98a20b635413cdb800b044496c30949ee8903efe Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 13 Mar 2026 02:11:39 +0700 Subject: [PATCH 06/15] fix(wasm-sdk): remove needless borrow in computePlatformSighash MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix clippy::needless_borrows_for_generic_args — `&format!()` → `format!()`. Co-Authored-By: Claude Opus 4.6 --- packages/wasm-dpp2/src/shielded/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wasm-dpp2/src/shielded/mod.rs b/packages/wasm-dpp2/src/shielded/mod.rs index 3514b44eb50..ef9bce0fe42 100644 --- a/packages/wasm-dpp2/src/shielded/mod.rs +++ b/packages/wasm-dpp2/src/shielded/mod.rs @@ -26,7 +26,7 @@ pub fn compute_platform_sighash_wasm( extra_data: &[u8], ) -> WasmDppResult> { if bundle_commitment.len() != 32 { - return Err(crate::error::WasmDppError::invalid_argument(&format!( + return Err(crate::error::WasmDppError::invalid_argument(format!( "bundleCommitment must be exactly 32 bytes, got {}", bundle_commitment.len() ))); From 5d4ee200583cd1ee3ee1afeadc2f3276878d5da0 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 13 Mar 2026 12:30:29 +0700 Subject: [PATCH 07/15] fix(wasm): address PR review feedback for shielded bindings - Use null instead of undefined for absent values in shielded queries - Add unchecked_return_type to getMostRecentShieldedAnchor - Add constructor, TypeScript types, and impl_wasm_conversions_serde macro to ShieldFromAssetLockTransitionWasm - Extract shielded proof result types into separate module Co-Authored-By: Claude Opus 4.6 --- .../shield_from_asset_lock_transition.rs | 73 ++++-- .../wasm-dpp2/src/state_transitions/mod.rs | 1 + .../src/state_transitions/proof_result.rs | 197 ++-------------- .../proof_result_shielded.rs | 216 ++++++++++++++++++ packages/wasm-sdk/src/queries/shielded.rs | 17 +- 5 files changed, 301 insertions(+), 203 deletions(-) create mode 100644 packages/wasm-dpp2/src/state_transitions/proof_result_shielded.rs diff --git a/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs b/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs index b168907b19e..ef341975222 100644 --- a/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs @@ -1,12 +1,54 @@ use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; -use crate::impl_wasm_type_info; +use crate::{impl_wasm_conversions_serde, impl_wasm_type_info}; use dpp::serialization::{PlatformDeserializable, PlatformSerializable}; use dpp::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; use dpp::state_transition::{StateTransition, StateTransitionLike}; +use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; -#[derive(Clone)] +#[wasm_bindgen(typescript_custom_section)] +const TS_TYPES: &str = r#" +/** + * ShieldFromAssetLockTransition serialized as a plain object. + */ +export interface ShieldFromAssetLockTransitionObject { + $version: string; + assetLockProof: object; + actions: Array; + valueBalance: bigint; + anchor: Uint8Array; + proof: Uint8Array; + bindingSignature: Uint8Array; + signature: Uint8Array; +} + +/** + * ShieldFromAssetLockTransition serialized as JSON (human-readable). + */ +export interface ShieldFromAssetLockTransitionJSON { + $version: string; + assetLockProof: object; + actions: Array; + valueBalance: number; + anchor: number[]; + proof: number[]; + bindingSignature: number[]; + signature: string; +} +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "ShieldFromAssetLockTransitionObject")] + pub type ShieldFromAssetLockTransitionObjectJs; + + #[wasm_bindgen(typescript_type = "ShieldFromAssetLockTransitionJSON")] + pub type ShieldFromAssetLockTransitionJSONJs; +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(transparent)] #[wasm_bindgen(js_name = ShieldFromAssetLockTransition)] pub struct ShieldFromAssetLockTransitionWasm(ShieldFromAssetLockTransition); @@ -24,6 +66,13 @@ impl From for ShieldFromAssetLockTransition { #[wasm_bindgen(js_class = ShieldFromAssetLockTransition)] impl ShieldFromAssetLockTransitionWasm { + #[wasm_bindgen(constructor)] + pub fn new(value: JsValue) -> WasmDppResult { + let inner: ShieldFromAssetLockTransition = serde_wasm_bindgen::from_value(value) + .map_err(|e| WasmDppError::serialization(e.to_string()))?; + Ok(ShieldFromAssetLockTransitionWasm(inner)) + } + #[wasm_bindgen(js_name = getType)] pub fn get_type(&self) -> u8 { self.0.state_transition_type() as u8 @@ -96,12 +145,6 @@ impl ShieldFromAssetLockTransitionWasm { .collect() } - #[wasm_bindgen(js_name = toObject)] - pub fn to_object(&self) -> WasmDppResult { - serde_wasm_bindgen::to_value(&self.0) - .map_err(|e| WasmDppError::serialization(e.to_string())) - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> WasmDppResult> { Ok(PlatformSerializable::serialize_to_bytes( @@ -120,19 +163,19 @@ impl ShieldFromAssetLockTransitionWasm { } } - #[wasm_bindgen(js_name = toJSON)] - pub fn to_json(&self) -> WasmDppResult { - let json = serde_json::to_value(&self.0) - .map_err(|e| WasmDppError::serialization(e.to_string()))?; - serde_wasm_bindgen::to_value(&json).map_err(|e| WasmDppError::serialization(e.to_string())) - } - #[wasm_bindgen(js_name = toStateTransition)] pub fn to_state_transition(&self) -> crate::state_transitions::base::StateTransitionWasm { StateTransition::ShieldFromAssetLock(self.0.clone()).into() } } +impl_wasm_conversions_serde!( + ShieldFromAssetLockTransitionWasm, + ShieldFromAssetLockTransition, + ShieldFromAssetLockTransitionObjectJs, + ShieldFromAssetLockTransitionJSONJs +); + impl_wasm_type_info!( ShieldFromAssetLockTransitionWasm, ShieldFromAssetLockTransition diff --git a/packages/wasm-dpp2/src/state_transitions/mod.rs b/packages/wasm-dpp2/src/state_transitions/mod.rs index 4b9a878544b..acd9c8ede76 100644 --- a/packages/wasm-dpp2/src/state_transitions/mod.rs +++ b/packages/wasm-dpp2/src/state_transitions/mod.rs @@ -1,6 +1,7 @@ pub mod base; pub mod batch; pub mod proof_result; +pub mod proof_result_shielded; pub use base::{GroupStateTransitionInfoWasm, StateTransitionWasm}; pub use proof_result::{StateTransitionProofResultTypeJs, convert_proof_result}; diff --git a/packages/wasm-dpp2/src/state_transitions/proof_result.rs b/packages/wasm-dpp2/src/state_transitions/proof_result.rs index 0d13eb17dc2..923824cce36 100644 --- a/packages/wasm-dpp2/src/state_transitions/proof_result.rs +++ b/packages/wasm-dpp2/src/state_transitions/proof_result.rs @@ -74,7 +74,7 @@ extern "C" { // Helper: build a plain JS object from key-value pairs // ============================================================================ -fn js_obj(entries: &[(&str, JsValue)]) -> JsValue { +pub(crate) fn js_obj(entries: &[(&str, JsValue)]) -> JsValue { let obj = js_sys::Object::new(); for (key, val) in entries { js_sys::Reflect::set(&obj, &(*key).into(), val).unwrap(); @@ -748,170 +748,13 @@ fn action_status_to_string(status: dpp::group::group_action_status::GroupActionS } } -// --- VerifiedShieldedPoolState --- - -#[wasm_bindgen(js_name = "VerifiedShieldedPoolState")] -#[derive(Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct VerifiedShieldedPoolStateWasm { - pool_balance: Option, -} - -impl_wasm_type_info!(VerifiedShieldedPoolStateWasm, VerifiedShieldedPoolState); -impl_wasm_conversions_serde!(VerifiedShieldedPoolStateWasm, VerifiedShieldedPoolState); - -#[wasm_bindgen(js_class = VerifiedShieldedPoolState)] -impl VerifiedShieldedPoolStateWasm { - #[wasm_bindgen(getter, js_name = "poolBalance")] - pub fn pool_balance(&self) -> JsValue { - match self.pool_balance { - Some(b) => BigInt::from(b).into(), - None => JsValue::undefined(), - } - } -} - -// --- VerifiedAssetLockConsumed --- - -#[wasm_bindgen(js_name = "VerifiedAssetLockConsumed")] -#[derive(Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct VerifiedAssetLockConsumedWasm { - #[wasm_bindgen(getter_with_clone)] - pub status: String, - initial_credit_value: Option, - remaining_credit_value: Option, -} - -#[wasm_bindgen(js_class = VerifiedAssetLockConsumed)] -impl VerifiedAssetLockConsumedWasm { - #[wasm_bindgen(getter, js_name = "initialCreditValue")] - pub fn initial_credit_value(&self) -> JsValue { - match self.initial_credit_value { - Some(v) => BigInt::from(v).into(), - None => JsValue::undefined(), - } - } - - #[wasm_bindgen(getter, js_name = "remainingCreditValue")] - pub fn remaining_credit_value(&self) -> JsValue { - match self.remaining_credit_value { - Some(v) => BigInt::from(v).into(), - None => JsValue::undefined(), - } - } -} - -impl_wasm_type_info!(VerifiedAssetLockConsumedWasm, VerifiedAssetLockConsumed); -impl_wasm_conversions_serde!(VerifiedAssetLockConsumedWasm, VerifiedAssetLockConsumed); - -// --- VerifiedShieldedNullifiers --- - -#[wasm_bindgen(js_name = "VerifiedShieldedNullifiers")] -#[derive(Clone)] -pub struct VerifiedShieldedNullifiersWasm { - nullifiers: Map, // Map -} - -#[wasm_bindgen(js_class = VerifiedShieldedNullifiers)] -impl VerifiedShieldedNullifiersWasm { - #[wasm_bindgen(getter)] - pub fn nullifiers(&self) -> Map { - self.nullifiers.clone() - } - - #[wasm_bindgen(js_name = toObject)] - pub fn to_object(&self) -> JsValue { - js_obj(&[("nullifiers", self.nullifiers.clone().into())]) - } - - #[wasm_bindgen(js_name = toJSON)] - pub fn to_json(&self) -> JsValue { - self.to_object() - } -} - -impl_wasm_type_info!(VerifiedShieldedNullifiersWasm, VerifiedShieldedNullifiers); - -// --- VerifiedShieldedNullifiersWithAddressInfos --- - -#[wasm_bindgen(js_name = "VerifiedShieldedNullifiersWithAddressInfos")] -#[derive(Clone)] -pub struct VerifiedShieldedNullifiersWithAddressInfosWasm { - nullifiers: Map, - address_infos: Map, -} - -#[wasm_bindgen(js_class = VerifiedShieldedNullifiersWithAddressInfos)] -impl VerifiedShieldedNullifiersWithAddressInfosWasm { - #[wasm_bindgen(getter)] - pub fn nullifiers(&self) -> Map { - self.nullifiers.clone() - } - - #[wasm_bindgen(getter = "addressInfos")] - pub fn address_infos(&self) -> Map { - self.address_infos.clone() - } - - #[wasm_bindgen(js_name = toObject)] - pub fn to_object(&self) -> JsValue { - js_obj(&[ - ("nullifiers", self.nullifiers.clone().into()), - ("addressInfos", self.address_infos.clone().into()), - ]) - } - - #[wasm_bindgen(js_name = toJSON)] - pub fn to_json(&self) -> JsValue { - self.to_object() - } -} - -impl_wasm_type_info!( +// Shielded types are in proof_result_shielded module +use super::proof_result_shielded::{ + VerifiedAssetLockConsumedWasm, VerifiedShieldedNullifiersWasm, VerifiedShieldedNullifiersWithAddressInfosWasm, - VerifiedShieldedNullifiersWithAddressInfos -); - -// --- VerifiedShieldedNullifiersWithWithdrawalDocument --- - -#[wasm_bindgen(js_name = "VerifiedShieldedNullifiersWithWithdrawalDocument")] -#[derive(Clone)] -pub struct VerifiedShieldedNullifiersWithWithdrawalDocumentWasm { - nullifiers: Map, - documents: Map, -} - -#[wasm_bindgen(js_class = VerifiedShieldedNullifiersWithWithdrawalDocument)] -impl VerifiedShieldedNullifiersWithWithdrawalDocumentWasm { - #[wasm_bindgen(getter)] - pub fn nullifiers(&self) -> Map { - self.nullifiers.clone() - } - - #[wasm_bindgen(getter)] - pub fn documents(&self) -> Map { - self.documents.clone() - } - - #[wasm_bindgen(js_name = toObject)] - pub fn to_object(&self) -> JsValue { - js_obj(&[ - ("nullifiers", self.nullifiers.clone().into()), - ("documents", self.documents.clone().into()), - ]) - } - - #[wasm_bindgen(js_name = toJSON)] - pub fn to_json(&self) -> JsValue { - self.to_object() - } -} - -impl_wasm_type_info!( VerifiedShieldedNullifiersWithWithdrawalDocumentWasm, - VerifiedShieldedNullifiersWithWithdrawalDocument -); +}; + /// Convert a Rust `StateTransitionProofResult` into the corresponding typed /// WASM wrapper, ready to be returned to JavaScript. pub fn convert_proof_result( @@ -1085,28 +928,20 @@ pub fn convert_proof_result( ), StoredAssetLockInfo::NotPresent => ("NotPresent".to_string(), None, None), }; - VerifiedAssetLockConsumedWasm { - status, - initial_credit_value: initial, - remaining_credit_value: remaining, - } - .into() + VerifiedAssetLockConsumedWasm::new(status, initial, remaining).into() } StateTransitionProofResult::VerifiedShieldedNullifiers(nullifiers) => { - VerifiedShieldedNullifiersWasm { - nullifiers: build_nullifier_map(nullifiers), - } - .into() + VerifiedShieldedNullifiersWasm::from_map(build_nullifier_map(nullifiers)).into() } StateTransitionProofResult::VerifiedShieldedNullifiersWithAddressInfos( nullifiers, infos, - ) => VerifiedShieldedNullifiersWithAddressInfosWasm { - nullifiers: build_nullifier_map(nullifiers), - address_infos: build_address_infos_map(infos), - } + ) => VerifiedShieldedNullifiersWithAddressInfosWasm::new( + build_nullifier_map(nullifiers), + build_address_infos_map(infos), + ) .into(), StateTransitionProofResult::VerifiedShieldedNullifiersWithWithdrawalDocument( @@ -1121,10 +956,10 @@ pub fn convert_proof_result( }; (key, val) })); - VerifiedShieldedNullifiersWithWithdrawalDocumentWasm { - nullifiers: build_nullifier_map(nullifiers), - documents: doc_map, - } + VerifiedShieldedNullifiersWithWithdrawalDocumentWasm::new( + build_nullifier_map(nullifiers), + doc_map, + ) .into() } }; diff --git a/packages/wasm-dpp2/src/state_transitions/proof_result_shielded.rs b/packages/wasm-dpp2/src/state_transitions/proof_result_shielded.rs new file mode 100644 index 00000000000..8235e2a341e --- /dev/null +++ b/packages/wasm-dpp2/src/state_transitions/proof_result_shielded.rs @@ -0,0 +1,216 @@ +//! Shielded pool WASM wrappers for `StateTransitionProofResult` variants. +//! +//! These types were extracted from `proof_result` to keep shielded-specific +//! code in its own module. + +use crate::impl_wasm_conversions_serde; +use crate::impl_wasm_type_info; +use js_sys::{BigInt, Map}; +use serde::{Deserialize, Serialize}; +use wasm_bindgen::JsValue; +use wasm_bindgen::prelude::*; + +use super::proof_result::js_obj; + +// --- VerifiedShieldedPoolState --- + +#[wasm_bindgen(js_name = "VerifiedShieldedPoolState")] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VerifiedShieldedPoolStateWasm { + pool_balance: Option, +} + +impl_wasm_type_info!(VerifiedShieldedPoolStateWasm, VerifiedShieldedPoolState); +impl_wasm_conversions_serde!(VerifiedShieldedPoolStateWasm, VerifiedShieldedPoolState); + +#[wasm_bindgen(js_class = VerifiedShieldedPoolState)] +impl VerifiedShieldedPoolStateWasm { + #[wasm_bindgen(getter, js_name = "poolBalance")] + pub fn pool_balance(&self) -> JsValue { + match self.pool_balance { + Some(b) => BigInt::from(b).into(), + None => JsValue::undefined(), + } + } +} + +// --- VerifiedAssetLockConsumed --- + +#[wasm_bindgen(js_name = "VerifiedAssetLockConsumed")] +#[derive(Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct VerifiedAssetLockConsumedWasm { + #[wasm_bindgen(getter_with_clone)] + pub status: String, + initial_credit_value: Option, + remaining_credit_value: Option, +} + +#[wasm_bindgen(js_class = VerifiedAssetLockConsumed)] +impl VerifiedAssetLockConsumedWasm { + #[wasm_bindgen(getter, js_name = "initialCreditValue")] + pub fn initial_credit_value(&self) -> JsValue { + match self.initial_credit_value { + Some(v) => BigInt::from(v).into(), + None => JsValue::undefined(), + } + } + + #[wasm_bindgen(getter, js_name = "remainingCreditValue")] + pub fn remaining_credit_value(&self) -> JsValue { + match self.remaining_credit_value { + Some(v) => BigInt::from(v).into(), + None => JsValue::undefined(), + } + } +} + +impl VerifiedAssetLockConsumedWasm { + pub fn new( + status: String, + initial_credit_value: Option, + remaining_credit_value: Option, + ) -> Self { + Self { + status, + initial_credit_value, + remaining_credit_value, + } + } +} + +impl_wasm_type_info!(VerifiedAssetLockConsumedWasm, VerifiedAssetLockConsumed); +impl_wasm_conversions_serde!(VerifiedAssetLockConsumedWasm, VerifiedAssetLockConsumed); + +// --- VerifiedShieldedNullifiers --- + +#[wasm_bindgen(js_name = "VerifiedShieldedNullifiers")] +#[derive(Clone)] +pub struct VerifiedShieldedNullifiersWasm { + nullifiers: Map, // Map +} + +#[wasm_bindgen(js_class = VerifiedShieldedNullifiers)] +impl VerifiedShieldedNullifiersWasm { + #[wasm_bindgen(getter)] + pub fn nullifiers(&self) -> Map { + self.nullifiers.clone() + } + + #[wasm_bindgen(js_name = toObject)] + pub fn to_object(&self) -> JsValue { + js_obj(&[("nullifiers", self.nullifiers.clone().into())]) + } + + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> JsValue { + self.to_object() + } +} + +impl VerifiedShieldedNullifiersWasm { + pub fn from_map(nullifiers: Map) -> Self { + Self { nullifiers } + } +} + +impl_wasm_type_info!(VerifiedShieldedNullifiersWasm, VerifiedShieldedNullifiers); + +// --- VerifiedShieldedNullifiersWithAddressInfos --- + +#[wasm_bindgen(js_name = "VerifiedShieldedNullifiersWithAddressInfos")] +#[derive(Clone)] +pub struct VerifiedShieldedNullifiersWithAddressInfosWasm { + nullifiers: Map, + address_infos: Map, +} + +#[wasm_bindgen(js_class = VerifiedShieldedNullifiersWithAddressInfos)] +impl VerifiedShieldedNullifiersWithAddressInfosWasm { + #[wasm_bindgen(getter)] + pub fn nullifiers(&self) -> Map { + self.nullifiers.clone() + } + + #[wasm_bindgen(getter = "addressInfos")] + pub fn address_infos(&self) -> Map { + self.address_infos.clone() + } + + #[wasm_bindgen(js_name = toObject)] + pub fn to_object(&self) -> JsValue { + js_obj(&[ + ("nullifiers", self.nullifiers.clone().into()), + ("addressInfos", self.address_infos.clone().into()), + ]) + } + + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> JsValue { + self.to_object() + } +} + +impl VerifiedShieldedNullifiersWithAddressInfosWasm { + pub fn new(nullifiers: Map, address_infos: Map) -> Self { + Self { + nullifiers, + address_infos, + } + } +} + +impl_wasm_type_info!( + VerifiedShieldedNullifiersWithAddressInfosWasm, + VerifiedShieldedNullifiersWithAddressInfos +); + +// --- VerifiedShieldedNullifiersWithWithdrawalDocument --- + +#[wasm_bindgen(js_name = "VerifiedShieldedNullifiersWithWithdrawalDocument")] +#[derive(Clone)] +pub struct VerifiedShieldedNullifiersWithWithdrawalDocumentWasm { + nullifiers: Map, + documents: Map, +} + +#[wasm_bindgen(js_class = VerifiedShieldedNullifiersWithWithdrawalDocument)] +impl VerifiedShieldedNullifiersWithWithdrawalDocumentWasm { + #[wasm_bindgen(getter)] + pub fn nullifiers(&self) -> Map { + self.nullifiers.clone() + } + + #[wasm_bindgen(getter)] + pub fn documents(&self) -> Map { + self.documents.clone() + } + + #[wasm_bindgen(js_name = toObject)] + pub fn to_object(&self) -> JsValue { + js_obj(&[ + ("nullifiers", self.nullifiers.clone().into()), + ("documents", self.documents.clone().into()), + ]) + } + + #[wasm_bindgen(js_name = toJSON)] + pub fn to_json(&self) -> JsValue { + self.to_object() + } +} + +impl VerifiedShieldedNullifiersWithWithdrawalDocumentWasm { + pub fn new(nullifiers: Map, documents: Map) -> Self { + Self { + nullifiers, + documents, + } + } +} + +impl_wasm_type_info!( + VerifiedShieldedNullifiersWithWithdrawalDocumentWasm, + VerifiedShieldedNullifiersWithWithdrawalDocument +); diff --git a/packages/wasm-sdk/src/queries/shielded.rs b/packages/wasm-sdk/src/queries/shielded.rs index 0759b5ab788..7874a075b09 100644 --- a/packages/wasm-sdk/src/queries/shielded.rs +++ b/packages/wasm-sdk/src/queries/shielded.rs @@ -144,8 +144,11 @@ impl WasmSdk { Ok(array) } - /// Returns the most recent shielded anchor (32 bytes), or undefined if none exists. - #[wasm_bindgen(js_name = "getMostRecentShieldedAnchor")] + /// Returns the most recent shielded anchor (32 bytes), or null if none exists. + #[wasm_bindgen( + js_name = "getMostRecentShieldedAnchor", + unchecked_return_type = "Uint8Array | null" + )] pub async fn get_most_recent_shielded_anchor(&self) -> Result { use dash_sdk::platform::Fetch; use drive_proof_verifier::types::{MostRecentShieldedAnchor, NoParamQuery}; @@ -153,7 +156,7 @@ impl WasmSdk { let result = MostRecentShieldedAnchor::fetch(self.as_ref(), NoParamQuery {}).await?; match result { Some(anchor) => Ok(Uint8Array::from(anchor.0.as_slice()).into()), - None => Ok(JsValue::UNDEFINED), + None => Ok(JsValue::NULL), } } @@ -200,7 +203,7 @@ impl WasmSdk { #[wasm_bindgen( js_name = "getShieldedPoolStateWithProofInfo", - unchecked_return_type = "ProofMetadataResponseTyped" + unchecked_return_type = "ProofMetadataResponseTyped" )] pub async fn get_shielded_pool_state_with_proof_info( &self, @@ -214,7 +217,7 @@ impl WasmSdk { let data = result .map(|s| JsValue::from(BigInt::from(s.0))) - .unwrap_or(JsValue::UNDEFINED); + .unwrap_or(JsValue::NULL); Ok(ProofMetadataResponseWasm::from_sdk_parts( data, metadata, proof, @@ -282,7 +285,7 @@ impl WasmSdk { #[wasm_bindgen( js_name = "getMostRecentShieldedAnchorWithProofInfo", - unchecked_return_type = "ProofMetadataResponseTyped" + unchecked_return_type = "ProofMetadataResponseTyped" )] pub async fn get_most_recent_shielded_anchor_with_proof_info( &self, @@ -299,7 +302,7 @@ impl WasmSdk { let data = result .map(|a| JsValue::from(Uint8Array::from(a.0.as_slice()))) - .unwrap_or(JsValue::UNDEFINED); + .unwrap_or(JsValue::NULL); Ok(ProofMetadataResponseWasm::from_sdk_parts( data, metadata, proof, From 35b7ba4acb64831c8e2d457cbfa484acc8623cac Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Fri, 13 Mar 2026 12:47:15 +0700 Subject: [PATCH 08/15] fix(wasm-sdk): reject nullifiers that are not exactly 32 bytes Instead of silently truncating/padding, return an InvalidArgument error when a nullifier Uint8Array is not exactly 32 bytes. Co-Authored-By: Claude Opus 4.6 --- packages/wasm-sdk/src/queries/shielded.rs | 24 +++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/wasm-sdk/src/queries/shielded.rs b/packages/wasm-sdk/src/queries/shielded.rs index 7874a075b09..a7ad061fb8c 100644 --- a/packages/wasm-sdk/src/queries/shielded.rs +++ b/packages/wasm-sdk/src/queries/shielded.rs @@ -177,12 +177,16 @@ impl WasmSdk { .map(|n| { let uint8_arr = Uint8Array::new(&n); let bytes = uint8_arr.to_vec(); + if bytes.len() != 32 { + return Err(WasmSdkError::invalid_argument( + "Each nullifier must be exactly 32 bytes", + )); + } let mut arr = [0u8; 32]; - let len = bytes.len().min(32); - arr[..len].copy_from_slice(&bytes[..len]); - arr + arr.copy_from_slice(&bytes); + Ok(arr) }) - .collect(); + .collect::, _>>()?; let query = ShieldedNullifiersQuery(nullifier_arrays); let result = ShieldedNullifierStatuses::fetch(self.as_ref(), query).await?; @@ -325,12 +329,16 @@ impl WasmSdk { .map(|n| { let uint8_arr = Uint8Array::new(&n); let bytes = uint8_arr.to_vec(); + if bytes.len() != 32 { + return Err(WasmSdkError::invalid_argument( + "Each nullifier must be exactly 32 bytes", + )); + } let mut arr = [0u8; 32]; - let len = bytes.len().min(32); - arr[..len].copy_from_slice(&bytes[..len]); - arr + arr.copy_from_slice(&bytes); + Ok(arr) }) - .collect(); + .collect::, _>>()?; let query = ShieldedNullifiersQuery(nullifier_arrays); let (result, metadata, proof) = From 74e92903b7c120f4f34b41fb2d3b624941acfd7f Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 16 Mar 2026 15:58:17 +0700 Subject: [PATCH 09/15] feat(wasm): address all review feedback on shielded WASM bindings Address shumkov, thepastaclaw, and CodeRabbit review comments: Shielded transition wrappers (wasm-dpp2): - Add Serialize/Deserialize derives, TypeScript interface declarations, extern "C" typed JS/JSON types, constructors, and impl_wasm_conversions_serde macro to ShieldTransition, ShieldedTransferTransition, ShieldedWithdrawalTransition, and UnshieldTransition (matching the pattern already used by ShieldFromAssetLockTransition) Shielded queries (wasm-sdk): - Change JsValue::NULL to JsValue::UNDEFINED for missing values (JS convention: missing = undefined, explicit empty = null) - Fix getShieldedPoolState to return undefined for None instead of BigInt(0), making None-handling consistent with WithProofInfo variant - Add unchecked_return_type annotations for TypeScript ("bigint | undefined", "Uint8Array | undefined") - Extract duplicated nullifier parsing into parse_nullifiers() helper Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/shielded/shield_transition.rs | 77 +++++++++++++---- .../shielded/shielded_transfer_transition.rs | 69 +++++++++++---- .../shielded_withdrawal_transition.rs | 75 +++++++++++++---- .../src/shielded/unshield_transition.rs | 71 ++++++++++++---- packages/wasm-sdk/src/queries/shielded.rs | 84 +++++++++---------- 5 files changed, 274 insertions(+), 102 deletions(-) diff --git a/packages/wasm-dpp2/src/shielded/shield_transition.rs b/packages/wasm-dpp2/src/shielded/shield_transition.rs index d8ea36d4483..3dc13859430 100644 --- a/packages/wasm-dpp2/src/shielded/shield_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shield_transition.rs @@ -1,12 +1,58 @@ use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; -use crate::impl_wasm_type_info; +use crate::{impl_wasm_conversions_serde, impl_wasm_type_info}; use dpp::serialization::{PlatformDeserializable, PlatformSerializable}; use dpp::state_transition::shield_transition::ShieldTransition; use dpp::state_transition::{StateTransition, StateTransitionLike}; +use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; -#[derive(Clone)] +#[wasm_bindgen(typescript_custom_section)] +const TS_TYPES: &str = r#" +/** + * ShieldTransition serialized as a plain object. + */ +export interface ShieldTransitionObject { + $version: string; + inputs: object; + actions: Array; + amount: bigint; + anchor: Uint8Array; + proof: Uint8Array; + bindingSignature: Uint8Array; + feeStrategy: object; + userFeeIncrease: number; + inputWitnesses: object; +} + +/** + * ShieldTransition serialized as JSON (human-readable). + */ +export interface ShieldTransitionJSON { + $version: string; + inputs: object; + actions: Array; + amount: number; + anchor: number[]; + proof: number[]; + bindingSignature: number[]; + feeStrategy: object; + userFeeIncrease: number; + inputWitnesses: object; +} +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "ShieldTransitionObject")] + pub type ShieldTransitionObjectJs; + + #[wasm_bindgen(typescript_type = "ShieldTransitionJSON")] + pub type ShieldTransitionJSONJs; +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(transparent)] #[wasm_bindgen(js_name = ShieldTransition)] pub struct ShieldTransitionWasm(ShieldTransition); @@ -24,6 +70,13 @@ impl From for ShieldTransition { #[wasm_bindgen(js_class = ShieldTransition)] impl ShieldTransitionWasm { + #[wasm_bindgen(constructor)] + pub fn new(value: JsValue) -> WasmDppResult { + let inner: ShieldTransition = serde_wasm_bindgen::from_value(value) + .map_err(|e| WasmDppError::serialization(e.to_string()))?; + Ok(ShieldTransitionWasm(inner)) + } + #[wasm_bindgen(js_name = getType)] pub fn get_type(&self) -> u8 { self.0.state_transition_type() as u8 @@ -116,12 +169,6 @@ impl ShieldTransitionWasm { .collect() } - #[wasm_bindgen(js_name = toObject)] - pub fn to_object(&self) -> WasmDppResult { - serde_wasm_bindgen::to_value(&self.0) - .map_err(|e| WasmDppError::serialization(e.to_string())) - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> WasmDppResult> { Ok(PlatformSerializable::serialize_to_bytes( @@ -140,17 +187,17 @@ impl ShieldTransitionWasm { } } - #[wasm_bindgen(js_name = toJSON)] - pub fn to_json(&self) -> WasmDppResult { - let json = serde_json::to_value(&self.0) - .map_err(|e| WasmDppError::serialization(e.to_string()))?; - serde_wasm_bindgen::to_value(&json).map_err(|e| WasmDppError::serialization(e.to_string())) - } - #[wasm_bindgen(js_name = toStateTransition)] pub fn to_state_transition(&self) -> crate::state_transitions::base::StateTransitionWasm { StateTransition::Shield(self.0.clone()).into() } } +impl_wasm_conversions_serde!( + ShieldTransitionWasm, + ShieldTransition, + ShieldTransitionObjectJs, + ShieldTransitionJSONJs +); + impl_wasm_type_info!(ShieldTransitionWasm, ShieldTransition); diff --git a/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs b/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs index a1263a8603f..175cc34aae0 100644 --- a/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs @@ -1,12 +1,50 @@ use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; -use crate::impl_wasm_type_info; +use crate::{impl_wasm_conversions_serde, impl_wasm_type_info}; use dpp::serialization::{PlatformDeserializable, PlatformSerializable}; use dpp::state_transition::shielded_transfer_transition::ShieldedTransferTransition; use dpp::state_transition::{StateTransition, StateTransitionLike}; +use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; -#[derive(Clone)] +#[wasm_bindgen(typescript_custom_section)] +const TS_TYPES: &str = r#" +/** + * ShieldedTransferTransition serialized as a plain object. + */ +export interface ShieldedTransferTransitionObject { + $version: string; + actions: Array; + valueBalance: bigint; + anchor: Uint8Array; + proof: Uint8Array; + bindingSignature: Uint8Array; +} + +/** + * ShieldedTransferTransition serialized as JSON (human-readable). + */ +export interface ShieldedTransferTransitionJSON { + $version: string; + actions: Array; + valueBalance: number; + anchor: number[]; + proof: number[]; + bindingSignature: number[]; +} +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "ShieldedTransferTransitionObject")] + pub type ShieldedTransferTransitionObjectJs; + + #[wasm_bindgen(typescript_type = "ShieldedTransferTransitionJSON")] + pub type ShieldedTransferTransitionJSONJs; +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(transparent)] #[wasm_bindgen(js_name = ShieldedTransferTransition)] pub struct ShieldedTransferTransitionWasm(ShieldedTransferTransition); @@ -24,6 +62,13 @@ impl From for ShieldedTransferTransition { #[wasm_bindgen(js_class = ShieldedTransferTransition)] impl ShieldedTransferTransitionWasm { + #[wasm_bindgen(constructor)] + pub fn new(value: JsValue) -> WasmDppResult { + let inner: ShieldedTransferTransition = serde_wasm_bindgen::from_value(value) + .map_err(|e| WasmDppError::serialization(e.to_string()))?; + Ok(ShieldedTransferTransitionWasm(inner)) + } + #[wasm_bindgen(js_name = getType)] pub fn get_type(&self) -> u8 { self.0.state_transition_type() as u8 @@ -79,12 +124,6 @@ impl ShieldedTransferTransitionWasm { .collect() } - #[wasm_bindgen(js_name = toObject)] - pub fn to_object(&self) -> WasmDppResult { - serde_wasm_bindgen::to_value(&self.0) - .map_err(|e| WasmDppError::serialization(e.to_string())) - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> WasmDppResult> { Ok(PlatformSerializable::serialize_to_bytes( @@ -103,17 +142,17 @@ impl ShieldedTransferTransitionWasm { } } - #[wasm_bindgen(js_name = toJSON)] - pub fn to_json(&self) -> WasmDppResult { - let json = serde_json::to_value(&self.0) - .map_err(|e| WasmDppError::serialization(e.to_string()))?; - serde_wasm_bindgen::to_value(&json).map_err(|e| WasmDppError::serialization(e.to_string())) - } - #[wasm_bindgen(js_name = toStateTransition)] pub fn to_state_transition(&self) -> crate::state_transitions::base::StateTransitionWasm { StateTransition::ShieldedTransfer(self.0.clone()).into() } } +impl_wasm_conversions_serde!( + ShieldedTransferTransitionWasm, + ShieldedTransferTransition, + ShieldedTransferTransitionObjectJs, + ShieldedTransferTransitionJSONJs +); + impl_wasm_type_info!(ShieldedTransferTransitionWasm, ShieldedTransferTransition); diff --git a/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs b/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs index d41678c3ef3..4b2b8b9581d 100644 --- a/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs @@ -1,12 +1,56 @@ use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; -use crate::impl_wasm_type_info; +use crate::{impl_wasm_conversions_serde, impl_wasm_type_info}; use dpp::serialization::{PlatformDeserializable, PlatformSerializable}; use dpp::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; use dpp::state_transition::{StateTransition, StateTransitionLike}; +use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; -#[derive(Clone)] +#[wasm_bindgen(typescript_custom_section)] +const TS_TYPES: &str = r#" +/** + * ShieldedWithdrawalTransition serialized as a plain object. + */ +export interface ShieldedWithdrawalTransitionObject { + $version: string; + actions: Array; + unshieldingAmount: bigint; + anchor: Uint8Array; + proof: Uint8Array; + bindingSignature: Uint8Array; + coreFeePerByte: number; + pooling: number; + outputScript: Uint8Array; +} + +/** + * ShieldedWithdrawalTransition serialized as JSON (human-readable). + */ +export interface ShieldedWithdrawalTransitionJSON { + $version: string; + actions: Array; + unshieldingAmount: number; + anchor: number[]; + proof: number[]; + bindingSignature: number[]; + coreFeePerByte: number; + pooling: number; + outputScript: number[]; +} +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "ShieldedWithdrawalTransitionObject")] + pub type ShieldedWithdrawalTransitionObjectJs; + + #[wasm_bindgen(typescript_type = "ShieldedWithdrawalTransitionJSON")] + pub type ShieldedWithdrawalTransitionJSONJs; +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(transparent)] #[wasm_bindgen(js_name = ShieldedWithdrawalTransition)] pub struct ShieldedWithdrawalTransitionWasm(ShieldedWithdrawalTransition); @@ -24,6 +68,13 @@ impl From for ShieldedWithdrawalTransition { #[wasm_bindgen(js_class = ShieldedWithdrawalTransition)] impl ShieldedWithdrawalTransitionWasm { + #[wasm_bindgen(constructor)] + pub fn new(value: JsValue) -> WasmDppResult { + let inner: ShieldedWithdrawalTransition = serde_wasm_bindgen::from_value(value) + .map_err(|e| WasmDppError::serialization(e.to_string()))?; + Ok(ShieldedWithdrawalTransitionWasm(inner)) + } + #[wasm_bindgen(js_name = getType)] pub fn get_type(&self) -> u8 { self.0.state_transition_type() as u8 @@ -103,12 +154,6 @@ impl ShieldedWithdrawalTransitionWasm { .collect() } - #[wasm_bindgen(js_name = toObject)] - pub fn to_object(&self) -> WasmDppResult { - serde_wasm_bindgen::to_value(&self.0) - .map_err(|e| WasmDppError::serialization(e.to_string())) - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> WasmDppResult> { Ok(PlatformSerializable::serialize_to_bytes( @@ -127,19 +172,19 @@ impl ShieldedWithdrawalTransitionWasm { } } - #[wasm_bindgen(js_name = toJSON)] - pub fn to_json(&self) -> WasmDppResult { - let json = serde_json::to_value(&self.0) - .map_err(|e| WasmDppError::serialization(e.to_string()))?; - serde_wasm_bindgen::to_value(&json).map_err(|e| WasmDppError::serialization(e.to_string())) - } - #[wasm_bindgen(js_name = toStateTransition)] pub fn to_state_transition(&self) -> crate::state_transitions::base::StateTransitionWasm { StateTransition::ShieldedWithdrawal(self.0.clone()).into() } } +impl_wasm_conversions_serde!( + ShieldedWithdrawalTransitionWasm, + ShieldedWithdrawalTransition, + ShieldedWithdrawalTransitionObjectJs, + ShieldedWithdrawalTransitionJSONJs +); + impl_wasm_type_info!( ShieldedWithdrawalTransitionWasm, ShieldedWithdrawalTransition diff --git a/packages/wasm-dpp2/src/shielded/unshield_transition.rs b/packages/wasm-dpp2/src/shielded/unshield_transition.rs index bae28992d30..5a28fd56ede 100644 --- a/packages/wasm-dpp2/src/shielded/unshield_transition.rs +++ b/packages/wasm-dpp2/src/shielded/unshield_transition.rs @@ -1,12 +1,52 @@ use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; -use crate::impl_wasm_type_info; +use crate::{impl_wasm_conversions_serde, impl_wasm_type_info}; use dpp::serialization::{PlatformDeserializable, PlatformSerializable}; use dpp::state_transition::unshield_transition::UnshieldTransition; use dpp::state_transition::{StateTransition, StateTransitionLike}; +use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; -#[derive(Clone)] +#[wasm_bindgen(typescript_custom_section)] +const TS_TYPES: &str = r#" +/** + * UnshieldTransition serialized as a plain object. + */ +export interface UnshieldTransitionObject { + $version: string; + outputAddress: object; + actions: Array; + unshieldingAmount: bigint; + anchor: Uint8Array; + proof: Uint8Array; + bindingSignature: Uint8Array; +} + +/** + * UnshieldTransition serialized as JSON (human-readable). + */ +export interface UnshieldTransitionJSON { + $version: string; + outputAddress: object; + actions: Array; + unshieldingAmount: number; + anchor: number[]; + proof: number[]; + bindingSignature: number[]; +} +"#; + +#[wasm_bindgen] +extern "C" { + #[wasm_bindgen(typescript_type = "UnshieldTransitionObject")] + pub type UnshieldTransitionObjectJs; + + #[wasm_bindgen(typescript_type = "UnshieldTransitionJSON")] + pub type UnshieldTransitionJSONJs; +} + +#[derive(Clone, Serialize, Deserialize)] +#[serde(transparent)] #[wasm_bindgen(js_name = UnshieldTransition)] pub struct UnshieldTransitionWasm(UnshieldTransition); @@ -24,6 +64,13 @@ impl From for UnshieldTransition { #[wasm_bindgen(js_class = UnshieldTransition)] impl UnshieldTransitionWasm { + #[wasm_bindgen(constructor)] + pub fn new(value: JsValue) -> WasmDppResult { + let inner: UnshieldTransition = serde_wasm_bindgen::from_value(value) + .map_err(|e| WasmDppError::serialization(e.to_string()))?; + Ok(UnshieldTransitionWasm(inner)) + } + #[wasm_bindgen(js_name = getType)] pub fn get_type(&self) -> u8 { self.0.state_transition_type() as u8 @@ -88,12 +135,6 @@ impl UnshieldTransitionWasm { .collect() } - #[wasm_bindgen(js_name = toObject)] - pub fn to_object(&self) -> WasmDppResult { - serde_wasm_bindgen::to_value(&self.0) - .map_err(|e| WasmDppError::serialization(e.to_string())) - } - #[wasm_bindgen(js_name = toBytes)] pub fn to_bytes(&self) -> WasmDppResult> { Ok(PlatformSerializable::serialize_to_bytes( @@ -112,17 +153,17 @@ impl UnshieldTransitionWasm { } } - #[wasm_bindgen(js_name = toJSON)] - pub fn to_json(&self) -> WasmDppResult { - let json = serde_json::to_value(&self.0) - .map_err(|e| WasmDppError::serialization(e.to_string()))?; - serde_wasm_bindgen::to_value(&json).map_err(|e| WasmDppError::serialization(e.to_string())) - } - #[wasm_bindgen(js_name = toStateTransition)] pub fn to_state_transition(&self) -> crate::state_transitions::base::StateTransitionWasm { StateTransition::Unshield(self.0.clone()).into() } } +impl_wasm_conversions_serde!( + UnshieldTransitionWasm, + UnshieldTransition, + UnshieldTransitionObjectJs, + UnshieldTransitionJSONJs +); + impl_wasm_type_info!(UnshieldTransitionWasm, UnshieldTransition); diff --git a/packages/wasm-sdk/src/queries/shielded.rs b/packages/wasm-sdk/src/queries/shielded.rs index a7ad061fb8c..7843513d14d 100644 --- a/packages/wasm-sdk/src/queries/shielded.rs +++ b/packages/wasm-sdk/src/queries/shielded.rs @@ -80,19 +80,47 @@ impl ShieldedNullifierStatusWasm { } impl_wasm_serde_conversions!(ShieldedNullifierStatusWasm, ShieldedNullifierStatus); +// ── Helpers ─────────────────────────────────────────────────────────── + +/// Parse a JS `Array` of nullifiers into `Vec<[u8; 32]>`. +/// +/// Each element must be exactly 32 bytes, otherwise an error is returned. +fn parse_nullifiers(nullifiers: &js_sys::Array) -> Result, WasmSdkError> { + nullifiers + .iter() + .map(|n| { + let uint8_arr = Uint8Array::new(&n); + let bytes = uint8_arr.to_vec(); + if bytes.len() != 32 { + return Err(WasmSdkError::invalid_argument( + "Each nullifier must be exactly 32 bytes", + )); + } + let mut arr = [0u8; 32]; + arr.copy_from_slice(&bytes); + Ok(arr) + }) + .collect() +} + // ── Query methods ────────────────────────────────────────────────────── #[wasm_bindgen] impl WasmSdk { - /// Returns the total shielded pool balance as a BigInt. - #[wasm_bindgen(js_name = "getShieldedPoolState")] - pub async fn get_shielded_pool_state(&self) -> Result { + /// Returns the total shielded pool balance as a BigInt, or undefined if not available. + #[wasm_bindgen( + js_name = "getShieldedPoolState", + unchecked_return_type = "bigint | undefined" + )] + pub async fn get_shielded_pool_state(&self) -> Result { use dash_sdk::platform::Fetch; use drive_proof_verifier::types::{NoParamQuery, ShieldedPoolState}; let result = ShieldedPoolState::fetch(self.as_ref(), NoParamQuery {}).await?; - let balance = result.map(|s| s.0).unwrap_or(0); - Ok(BigInt::from(balance)) + match result { + Some(s) => Ok(JsValue::from(BigInt::from(s.0))), + None => Ok(JsValue::UNDEFINED), + } } /// Fetches encrypted notes from the shielded pool, paginated. @@ -144,10 +172,10 @@ impl WasmSdk { Ok(array) } - /// Returns the most recent shielded anchor (32 bytes), or null if none exists. + /// Returns the most recent shielded anchor (32 bytes), or undefined if none exists. #[wasm_bindgen( js_name = "getMostRecentShieldedAnchor", - unchecked_return_type = "Uint8Array | null" + unchecked_return_type = "Uint8Array | undefined" )] pub async fn get_most_recent_shielded_anchor(&self) -> Result { use dash_sdk::platform::Fetch; @@ -156,7 +184,7 @@ impl WasmSdk { let result = MostRecentShieldedAnchor::fetch(self.as_ref(), NoParamQuery {}).await?; match result { Some(anchor) => Ok(Uint8Array::from(anchor.0.as_slice()).into()), - None => Ok(JsValue::NULL), + None => Ok(JsValue::UNDEFINED), } } @@ -172,21 +200,7 @@ impl WasmSdk { use dash_sdk::platform::Fetch; use drive_proof_verifier::types::{ShieldedNullifierStatuses, ShieldedNullifiersQuery}; - let nullifier_arrays: Vec<[u8; 32]> = nullifiers - .iter() - .map(|n| { - let uint8_arr = Uint8Array::new(&n); - let bytes = uint8_arr.to_vec(); - if bytes.len() != 32 { - return Err(WasmSdkError::invalid_argument( - "Each nullifier must be exactly 32 bytes", - )); - } - let mut arr = [0u8; 32]; - arr.copy_from_slice(&bytes); - Ok(arr) - }) - .collect::, _>>()?; + let nullifier_arrays = parse_nullifiers(&nullifiers)?; let query = ShieldedNullifiersQuery(nullifier_arrays); let result = ShieldedNullifierStatuses::fetch(self.as_ref(), query).await?; @@ -207,7 +221,7 @@ impl WasmSdk { #[wasm_bindgen( js_name = "getShieldedPoolStateWithProofInfo", - unchecked_return_type = "ProofMetadataResponseTyped" + unchecked_return_type = "ProofMetadataResponseTyped" )] pub async fn get_shielded_pool_state_with_proof_info( &self, @@ -221,7 +235,7 @@ impl WasmSdk { let data = result .map(|s| JsValue::from(BigInt::from(s.0))) - .unwrap_or(JsValue::NULL); + .unwrap_or(JsValue::UNDEFINED); Ok(ProofMetadataResponseWasm::from_sdk_parts( data, metadata, proof, @@ -289,7 +303,7 @@ impl WasmSdk { #[wasm_bindgen( js_name = "getMostRecentShieldedAnchorWithProofInfo", - unchecked_return_type = "ProofMetadataResponseTyped" + unchecked_return_type = "ProofMetadataResponseTyped" )] pub async fn get_most_recent_shielded_anchor_with_proof_info( &self, @@ -306,7 +320,7 @@ impl WasmSdk { let data = result .map(|a| JsValue::from(Uint8Array::from(a.0.as_slice()))) - .unwrap_or(JsValue::NULL); + .unwrap_or(JsValue::UNDEFINED); Ok(ProofMetadataResponseWasm::from_sdk_parts( data, metadata, proof, @@ -324,21 +338,7 @@ impl WasmSdk { use dash_sdk::platform::Fetch; use drive_proof_verifier::types::{ShieldedNullifierStatuses, ShieldedNullifiersQuery}; - let nullifier_arrays: Vec<[u8; 32]> = nullifiers - .iter() - .map(|n| { - let uint8_arr = Uint8Array::new(&n); - let bytes = uint8_arr.to_vec(); - if bytes.len() != 32 { - return Err(WasmSdkError::invalid_argument( - "Each nullifier must be exactly 32 bytes", - )); - } - let mut arr = [0u8; 32]; - arr.copy_from_slice(&bytes); - Ok(arr) - }) - .collect::, _>>()?; + let nullifier_arrays = parse_nullifiers(&nullifiers)?; let query = ShieldedNullifiersQuery(nullifier_arrays); let (result, metadata, proof) = From 7f216ec844ff68185395ef6f47563dccc985c443 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 16 Mar 2026 16:53:38 +0700 Subject: [PATCH 10/15] feat(wasm): improve TypeScript interface definitions per shumkov review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address ivan's review comments on shielded transition TS types: - Change $version to $formatVersion in all interfaces - Use AssetLockProofObject/JSON instead of generic object - Define SerializedOrchardAction interface with typed fields - Define FeeStrategyStepObject, AddressWitnessObject types - Use proper types for inputs, feeStrategy, inputWitnesses - Fix JSON byte fields: number[] → string (base64-encoded) - Use typed arrays for actions instead of Array Co-Authored-By: Claude Opus 4.6 (1M context) --- .../shield_from_asset_lock_transition.rs | 42 +++++++--- .../src/shielded/shield_transition.rs | 78 +++++++++++++++---- .../shielded/shielded_transfer_transition.rs | 14 ++-- .../shielded_withdrawal_transition.rs | 16 ++-- .../src/shielded/unshield_transition.rs | 14 ++-- 5 files changed, 120 insertions(+), 44 deletions(-) diff --git a/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs b/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs index ef341975222..bc76d192bcf 100644 --- a/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs @@ -9,13 +9,37 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen(typescript_custom_section)] const TS_TYPES: &str = r#" +/** + * A serialized Orchard action (spend-output pair) in binary/Object form. + */ +export interface SerializedOrchardAction { + nullifier: Uint8Array; + rk: Uint8Array; + cmx: Uint8Array; + encryptedNote: Uint8Array; + cvNet: Uint8Array; + spendAuthSig: Uint8Array; +} + +/** + * A serialized Orchard action (spend-output pair) in JSON form. + */ +export interface SerializedOrchardActionJSON { + nullifier: number[]; + rk: number[]; + cmx: number[]; + encryptedNote: number[]; + cvNet: number[]; + spendAuthSig: string; +} + /** * ShieldFromAssetLockTransition serialized as a plain object. */ export interface ShieldFromAssetLockTransitionObject { - $version: string; - assetLockProof: object; - actions: Array; + $formatVersion: string; + assetLockProof: AssetLockProofObject; + actions: SerializedOrchardAction[]; valueBalance: bigint; anchor: Uint8Array; proof: Uint8Array; @@ -27,13 +51,13 @@ export interface ShieldFromAssetLockTransitionObject { * ShieldFromAssetLockTransition serialized as JSON (human-readable). */ export interface ShieldFromAssetLockTransitionJSON { - $version: string; - assetLockProof: object; - actions: Array; + $formatVersion: string; + assetLockProof: AssetLockProofJSON; + actions: SerializedOrchardActionJSON[]; valueBalance: number; - anchor: number[]; - proof: number[]; - bindingSignature: number[]; + anchor: string; + proof: string; + bindingSignature: string; signature: string; } "#; diff --git a/packages/wasm-dpp2/src/shielded/shield_transition.rs b/packages/wasm-dpp2/src/shielded/shield_transition.rs index 3dc13859430..dedb20468bb 100644 --- a/packages/wasm-dpp2/src/shielded/shield_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shield_transition.rs @@ -9,36 +9,88 @@ use wasm_bindgen::prelude::*; #[wasm_bindgen(typescript_custom_section)] const TS_TYPES: &str = r#" +/** + * Fee strategy step: defines how fees are paid from inputs or outputs. + */ +export interface FeeStrategyStepObject { + type: string; + index: number; +} + +/** + * Address witness for P2PKH spending. + */ +export interface AddressWitnessP2pkhObject { + type: "p2pkh"; + signature: Uint8Array; +} + +/** + * Address witness for P2SH spending. + */ +export interface AddressWitnessP2shObject { + type: "p2sh"; + signatures: Uint8Array[]; + redeemScript: Uint8Array; +} + +/** + * Address witness (P2PKH or P2SH) in Object form. + */ +export type AddressWitnessObject = AddressWitnessP2pkhObject | AddressWitnessP2shObject; + +/** + * Address witness for P2PKH spending (JSON form). + */ +export interface AddressWitnessP2pkhJSON { + type: "p2pkh"; + signature: string; +} + +/** + * Address witness for P2SH spending (JSON form). + */ +export interface AddressWitnessP2shJSON { + type: "p2sh"; + signatures: string[]; + redeemScript: string; +} + +/** + * Address witness (P2PKH or P2SH) in JSON form. + */ +export type AddressWitnessJSON = AddressWitnessP2pkhJSON | AddressWitnessP2shJSON; + /** * ShieldTransition serialized as a plain object. */ export interface ShieldTransitionObject { - $version: string; - inputs: object; - actions: Array; + $formatVersion: string; + inputs: Map; + actions: SerializedOrchardAction[]; amount: bigint; anchor: Uint8Array; proof: Uint8Array; bindingSignature: Uint8Array; - feeStrategy: object; + feeStrategy: FeeStrategyStepObject[]; userFeeIncrease: number; - inputWitnesses: object; + inputWitnesses: AddressWitnessObject[]; } /** * ShieldTransition serialized as JSON (human-readable). */ export interface ShieldTransitionJSON { - $version: string; - inputs: object; - actions: Array; + $formatVersion: string; + inputs: Record; + actions: SerializedOrchardActionJSON[]; amount: number; - anchor: number[]; - proof: number[]; - bindingSignature: number[]; - feeStrategy: object; + anchor: string; + proof: string; + bindingSignature: string; + feeStrategy: FeeStrategyStepObject[]; userFeeIncrease: number; - inputWitnesses: object; + inputWitnesses: AddressWitnessJSON[]; } "#; diff --git a/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs b/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs index 175cc34aae0..62053c579be 100644 --- a/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs @@ -13,8 +13,8 @@ const TS_TYPES: &str = r#" * ShieldedTransferTransition serialized as a plain object. */ export interface ShieldedTransferTransitionObject { - $version: string; - actions: Array; + $formatVersion: string; + actions: SerializedOrchardAction[]; valueBalance: bigint; anchor: Uint8Array; proof: Uint8Array; @@ -25,12 +25,12 @@ export interface ShieldedTransferTransitionObject { * ShieldedTransferTransition serialized as JSON (human-readable). */ export interface ShieldedTransferTransitionJSON { - $version: string; - actions: Array; + $formatVersion: string; + actions: SerializedOrchardActionJSON[]; valueBalance: number; - anchor: number[]; - proof: number[]; - bindingSignature: number[]; + anchor: string; + proof: string; + bindingSignature: string; } "#; diff --git a/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs b/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs index 4b2b8b9581d..3895b1b840a 100644 --- a/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs @@ -13,8 +13,8 @@ const TS_TYPES: &str = r#" * ShieldedWithdrawalTransition serialized as a plain object. */ export interface ShieldedWithdrawalTransitionObject { - $version: string; - actions: Array; + $formatVersion: string; + actions: SerializedOrchardAction[]; unshieldingAmount: bigint; anchor: Uint8Array; proof: Uint8Array; @@ -28,15 +28,15 @@ export interface ShieldedWithdrawalTransitionObject { * ShieldedWithdrawalTransition serialized as JSON (human-readable). */ export interface ShieldedWithdrawalTransitionJSON { - $version: string; - actions: Array; + $formatVersion: string; + actions: SerializedOrchardActionJSON[]; unshieldingAmount: number; - anchor: number[]; - proof: number[]; - bindingSignature: number[]; + anchor: string; + proof: string; + bindingSignature: string; coreFeePerByte: number; pooling: number; - outputScript: number[]; + outputScript: string; } "#; diff --git a/packages/wasm-dpp2/src/shielded/unshield_transition.rs b/packages/wasm-dpp2/src/shielded/unshield_transition.rs index 5a28fd56ede..5bc9bf52725 100644 --- a/packages/wasm-dpp2/src/shielded/unshield_transition.rs +++ b/packages/wasm-dpp2/src/shielded/unshield_transition.rs @@ -13,9 +13,9 @@ const TS_TYPES: &str = r#" * UnshieldTransition serialized as a plain object. */ export interface UnshieldTransitionObject { - $version: string; + $formatVersion: string; outputAddress: object; - actions: Array; + actions: SerializedOrchardAction[]; unshieldingAmount: bigint; anchor: Uint8Array; proof: Uint8Array; @@ -26,13 +26,13 @@ export interface UnshieldTransitionObject { * UnshieldTransition serialized as JSON (human-readable). */ export interface UnshieldTransitionJSON { - $version: string; + $formatVersion: string; outputAddress: object; - actions: Array; + actions: SerializedOrchardActionJSON[]; unshieldingAmount: number; - anchor: number[]; - proof: number[]; - bindingSignature: number[]; + anchor: string; + proof: string; + bindingSignature: string; } "#; From c6a73b7ad064fc269a437b63980ac7e431deda36 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 16 Mar 2026 17:09:41 +0700 Subject: [PATCH 11/15] fix(wasm): use base64 string for all byte fields in OrchardActionJSON SerializedOrchardActionJSON had byte fields as number[] but they should be string (base64-encoded) to match the JSON serialization convention. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/shielded/shield_from_asset_lock_transition.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs b/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs index bc76d192bcf..5202d5e94a1 100644 --- a/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs @@ -25,11 +25,11 @@ export interface SerializedOrchardAction { * A serialized Orchard action (spend-output pair) in JSON form. */ export interface SerializedOrchardActionJSON { - nullifier: number[]; - rk: number[]; - cmx: number[]; - encryptedNote: number[]; - cvNet: number[]; + nullifier: string; + rk: string; + cmx: string; + encryptedNote: string; + cvNet: string; spendAuthSig: string; } From 4e29ccd71c8ad058a8f7643d7ee3f917a48fa0b9 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 16 Mar 2026 17:18:10 +0700 Subject: [PATCH 12/15] fix(wasm): use number | string for BigInt fields in JSON interfaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit JSON cannot represent BigInt natively — serde serializes u64 values as numbers (when they fit in f64) or strings (when they exceed 2^53). Update all JSON TypeScript interfaces to use `number | string` for u64 fields: valueBalance, amount, unshieldingAmount. The Object interfaces keep `bigint` since wasm_bindgen maps u64 to BigInt directly. The u64 getters are already correct — wasm_bindgen automatically returns BigInt to JS. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs | 2 +- packages/wasm-dpp2/src/shielded/shield_transition.rs | 2 +- packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs | 2 +- .../wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs | 2 +- packages/wasm-dpp2/src/shielded/unshield_transition.rs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs b/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs index 5202d5e94a1..c81b7c10b69 100644 --- a/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs @@ -54,7 +54,7 @@ export interface ShieldFromAssetLockTransitionJSON { $formatVersion: string; assetLockProof: AssetLockProofJSON; actions: SerializedOrchardActionJSON[]; - valueBalance: number; + valueBalance: number | string; anchor: string; proof: string; bindingSignature: string; diff --git a/packages/wasm-dpp2/src/shielded/shield_transition.rs b/packages/wasm-dpp2/src/shielded/shield_transition.rs index dedb20468bb..a5277b7c3b4 100644 --- a/packages/wasm-dpp2/src/shielded/shield_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shield_transition.rs @@ -84,7 +84,7 @@ export interface ShieldTransitionJSON { $formatVersion: string; inputs: Record; actions: SerializedOrchardActionJSON[]; - amount: number; + amount: number | string; anchor: string; proof: string; bindingSignature: string; diff --git a/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs b/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs index 62053c579be..69272bdf415 100644 --- a/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs @@ -27,7 +27,7 @@ export interface ShieldedTransferTransitionObject { export interface ShieldedTransferTransitionJSON { $formatVersion: string; actions: SerializedOrchardActionJSON[]; - valueBalance: number; + valueBalance: number | string; anchor: string; proof: string; bindingSignature: string; diff --git a/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs b/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs index 3895b1b840a..6925899efbe 100644 --- a/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs @@ -30,7 +30,7 @@ export interface ShieldedWithdrawalTransitionObject { export interface ShieldedWithdrawalTransitionJSON { $formatVersion: string; actions: SerializedOrchardActionJSON[]; - unshieldingAmount: number; + unshieldingAmount: number | string; anchor: string; proof: string; bindingSignature: string; diff --git a/packages/wasm-dpp2/src/shielded/unshield_transition.rs b/packages/wasm-dpp2/src/shielded/unshield_transition.rs index 5bc9bf52725..c244eb0228e 100644 --- a/packages/wasm-dpp2/src/shielded/unshield_transition.rs +++ b/packages/wasm-dpp2/src/shielded/unshield_transition.rs @@ -29,7 +29,7 @@ export interface UnshieldTransitionJSON { $formatVersion: string; outputAddress: object; actions: SerializedOrchardActionJSON[]; - unshieldingAmount: number; + unshieldingAmount: number | string; anchor: string; proof: string; bindingSignature: string; From fd850e119f8acd686c1918c4bb5ca6e9c5b92a5b Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 16 Mar 2026 17:39:15 +0700 Subject: [PATCH 13/15] fix(wasm): use typed parameters in shielded transition constructors Replace untyped JsValue parameter with the corresponding typed *ObjectJs extern type in all 5 shielded transition constructors. This provides TypeScript type checking at the call site. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/shielded/shield_from_asset_lock_transition.rs | 4 ++-- packages/wasm-dpp2/src/shielded/shield_transition.rs | 4 ++-- .../wasm-dpp2/src/shielded/shielded_transfer_transition.rs | 4 ++-- .../wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs | 4 ++-- packages/wasm-dpp2/src/shielded/unshield_transition.rs | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs b/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs index c81b7c10b69..650108cf3ce 100644 --- a/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs @@ -91,8 +91,8 @@ impl From for ShieldFromAssetLockTransition { #[wasm_bindgen(js_class = ShieldFromAssetLockTransition)] impl ShieldFromAssetLockTransitionWasm { #[wasm_bindgen(constructor)] - pub fn new(value: JsValue) -> WasmDppResult { - let inner: ShieldFromAssetLockTransition = serde_wasm_bindgen::from_value(value) + pub fn new(value: ShieldFromAssetLockTransitionObjectJs) -> WasmDppResult { + let inner: ShieldFromAssetLockTransition = serde_wasm_bindgen::from_value(value.into()) .map_err(|e| WasmDppError::serialization(e.to_string()))?; Ok(ShieldFromAssetLockTransitionWasm(inner)) } diff --git a/packages/wasm-dpp2/src/shielded/shield_transition.rs b/packages/wasm-dpp2/src/shielded/shield_transition.rs index a5277b7c3b4..a3cc3e7383e 100644 --- a/packages/wasm-dpp2/src/shielded/shield_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shield_transition.rs @@ -123,8 +123,8 @@ impl From for ShieldTransition { #[wasm_bindgen(js_class = ShieldTransition)] impl ShieldTransitionWasm { #[wasm_bindgen(constructor)] - pub fn new(value: JsValue) -> WasmDppResult { - let inner: ShieldTransition = serde_wasm_bindgen::from_value(value) + pub fn new(value: ShieldTransitionObjectJs) -> WasmDppResult { + let inner: ShieldTransition = serde_wasm_bindgen::from_value(value.into()) .map_err(|e| WasmDppError::serialization(e.to_string()))?; Ok(ShieldTransitionWasm(inner)) } diff --git a/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs b/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs index 69272bdf415..19762c1a3e8 100644 --- a/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs @@ -63,8 +63,8 @@ impl From for ShieldedTransferTransition { #[wasm_bindgen(js_class = ShieldedTransferTransition)] impl ShieldedTransferTransitionWasm { #[wasm_bindgen(constructor)] - pub fn new(value: JsValue) -> WasmDppResult { - let inner: ShieldedTransferTransition = serde_wasm_bindgen::from_value(value) + pub fn new(value: ShieldedTransferTransitionObjectJs) -> WasmDppResult { + let inner: ShieldedTransferTransition = serde_wasm_bindgen::from_value(value.into()) .map_err(|e| WasmDppError::serialization(e.to_string()))?; Ok(ShieldedTransferTransitionWasm(inner)) } diff --git a/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs b/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs index 6925899efbe..749cff2f46a 100644 --- a/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs @@ -69,8 +69,8 @@ impl From for ShieldedWithdrawalTransition { #[wasm_bindgen(js_class = ShieldedWithdrawalTransition)] impl ShieldedWithdrawalTransitionWasm { #[wasm_bindgen(constructor)] - pub fn new(value: JsValue) -> WasmDppResult { - let inner: ShieldedWithdrawalTransition = serde_wasm_bindgen::from_value(value) + pub fn new(value: ShieldedWithdrawalTransitionObjectJs) -> WasmDppResult { + let inner: ShieldedWithdrawalTransition = serde_wasm_bindgen::from_value(value.into()) .map_err(|e| WasmDppError::serialization(e.to_string()))?; Ok(ShieldedWithdrawalTransitionWasm(inner)) } diff --git a/packages/wasm-dpp2/src/shielded/unshield_transition.rs b/packages/wasm-dpp2/src/shielded/unshield_transition.rs index c244eb0228e..9a529fae00c 100644 --- a/packages/wasm-dpp2/src/shielded/unshield_transition.rs +++ b/packages/wasm-dpp2/src/shielded/unshield_transition.rs @@ -65,8 +65,8 @@ impl From for UnshieldTransition { #[wasm_bindgen(js_class = UnshieldTransition)] impl UnshieldTransitionWasm { #[wasm_bindgen(constructor)] - pub fn new(value: JsValue) -> WasmDppResult { - let inner: UnshieldTransition = serde_wasm_bindgen::from_value(value) + pub fn new(value: UnshieldTransitionObjectJs) -> WasmDppResult { + let inner: UnshieldTransition = serde_wasm_bindgen::from_value(value.into()) .map_err(|e| WasmDppError::serialization(e.to_string()))?; Ok(UnshieldTransitionWasm(inner)) } From 2db3796a61ed7a63e5be92200f4ca1ea8c65243f Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Mon, 16 Mar 2026 17:41:30 +0700 Subject: [PATCH 14/15] fix(wasm): correct TS types to match actual serde output - Change $formatVersion back to $version (serde uses tag = "$version") - Fix FeeStrategyStepObject to externally-tagged union type matching serde's camelCase enum output: { deductFromInput: n } | { reduceOutput: n } - Fix inputs from Map<> to Record since serialize_maps_as_objects emits plain JS objects, not Maps Co-Authored-By: Claude Opus 4.6 (1M context) --- .../shielded/shield_from_asset_lock_transition.rs | 4 ++-- .../wasm-dpp2/src/shielded/shield_transition.rs | 14 +++++++------- .../src/shielded/shielded_transfer_transition.rs | 4 ++-- .../src/shielded/shielded_withdrawal_transition.rs | 4 ++-- .../wasm-dpp2/src/shielded/unshield_transition.rs | 4 ++-- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs b/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs index 650108cf3ce..166eed4b578 100644 --- a/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs @@ -37,7 +37,7 @@ export interface SerializedOrchardActionJSON { * ShieldFromAssetLockTransition serialized as a plain object. */ export interface ShieldFromAssetLockTransitionObject { - $formatVersion: string; + $version: string; assetLockProof: AssetLockProofObject; actions: SerializedOrchardAction[]; valueBalance: bigint; @@ -51,7 +51,7 @@ export interface ShieldFromAssetLockTransitionObject { * ShieldFromAssetLockTransition serialized as JSON (human-readable). */ export interface ShieldFromAssetLockTransitionJSON { - $formatVersion: string; + $version: string; assetLockProof: AssetLockProofJSON; actions: SerializedOrchardActionJSON[]; valueBalance: number | string; diff --git a/packages/wasm-dpp2/src/shielded/shield_transition.rs b/packages/wasm-dpp2/src/shielded/shield_transition.rs index a3cc3e7383e..63d04d9193d 100644 --- a/packages/wasm-dpp2/src/shielded/shield_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shield_transition.rs @@ -11,11 +11,11 @@ use wasm_bindgen::prelude::*; const TS_TYPES: &str = r#" /** * Fee strategy step: defines how fees are paid from inputs or outputs. + * Externally-tagged enum: exactly one key present per object. */ -export interface FeeStrategyStepObject { - type: string; - index: number; -} +export type FeeStrategyStepObject = + | { deductFromInput: number } + | { reduceOutput: number }; /** * Address witness for P2PKH spending. @@ -65,8 +65,8 @@ export type AddressWitnessJSON = AddressWitnessP2pkhJSON | AddressWitnessP2shJSO * ShieldTransition serialized as a plain object. */ export interface ShieldTransitionObject { - $formatVersion: string; - inputs: Map; + $version: string; + inputs: Record; actions: SerializedOrchardAction[]; amount: bigint; anchor: Uint8Array; @@ -81,7 +81,7 @@ export interface ShieldTransitionObject { * ShieldTransition serialized as JSON (human-readable). */ export interface ShieldTransitionJSON { - $formatVersion: string; + $version: string; inputs: Record; actions: SerializedOrchardActionJSON[]; amount: number | string; diff --git a/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs b/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs index 19762c1a3e8..529cb24fa97 100644 --- a/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs @@ -13,7 +13,7 @@ const TS_TYPES: &str = r#" * ShieldedTransferTransition serialized as a plain object. */ export interface ShieldedTransferTransitionObject { - $formatVersion: string; + $version: string; actions: SerializedOrchardAction[]; valueBalance: bigint; anchor: Uint8Array; @@ -25,7 +25,7 @@ export interface ShieldedTransferTransitionObject { * ShieldedTransferTransition serialized as JSON (human-readable). */ export interface ShieldedTransferTransitionJSON { - $formatVersion: string; + $version: string; actions: SerializedOrchardActionJSON[]; valueBalance: number | string; anchor: string; diff --git a/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs b/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs index 749cff2f46a..0980bb8c714 100644 --- a/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs @@ -13,7 +13,7 @@ const TS_TYPES: &str = r#" * ShieldedWithdrawalTransition serialized as a plain object. */ export interface ShieldedWithdrawalTransitionObject { - $formatVersion: string; + $version: string; actions: SerializedOrchardAction[]; unshieldingAmount: bigint; anchor: Uint8Array; @@ -28,7 +28,7 @@ export interface ShieldedWithdrawalTransitionObject { * ShieldedWithdrawalTransition serialized as JSON (human-readable). */ export interface ShieldedWithdrawalTransitionJSON { - $formatVersion: string; + $version: string; actions: SerializedOrchardActionJSON[]; unshieldingAmount: number | string; anchor: string; diff --git a/packages/wasm-dpp2/src/shielded/unshield_transition.rs b/packages/wasm-dpp2/src/shielded/unshield_transition.rs index 9a529fae00c..1bc98c60ed1 100644 --- a/packages/wasm-dpp2/src/shielded/unshield_transition.rs +++ b/packages/wasm-dpp2/src/shielded/unshield_transition.rs @@ -13,7 +13,7 @@ const TS_TYPES: &str = r#" * UnshieldTransition serialized as a plain object. */ export interface UnshieldTransitionObject { - $formatVersion: string; + $version: string; outputAddress: object; actions: SerializedOrchardAction[]; unshieldingAmount: bigint; @@ -26,7 +26,7 @@ export interface UnshieldTransitionObject { * UnshieldTransition serialized as JSON (human-readable). */ export interface UnshieldTransitionJSON { - $formatVersion: string; + $version: string; outputAddress: object; actions: SerializedOrchardActionJSON[]; unshieldingAmount: number | string; From c6a2b3faa712a7b423ae9ec3eb55df31fefac4c8 Mon Sep 17 00:00:00 2001 From: Quantum Explorer Date: Tue, 17 Mar 2026 14:18:16 +0700 Subject: [PATCH 15/15] fix(wasm): change $version to $formatVersion, add Options constructor Address ivan's review feedback: - Change $version to $formatVersion in all 10 TS interfaces (Object and JSON) across all 5 shielded transition files - Add ShieldFromAssetLockTransitionOptions interface that accepts AssetLockProof as a WASM instance type (not a plain JS object) - Constructor now extracts assetLockProof via try_from_options and deserializes remaining fields via serde Co-Authored-By: Claude Opus 4.6 (1M context) --- .../shield_from_asset_lock_transition.rs | 77 +++++++++++++++++-- .../src/shielded/shield_transition.rs | 4 +- .../shielded/shielded_transfer_transition.rs | 8 +- .../shielded_withdrawal_transition.rs | 8 +- .../src/shielded/unshield_transition.rs | 4 +- 5 files changed, 85 insertions(+), 16 deletions(-) diff --git a/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs b/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs index 166eed4b578..5b47c5d354b 100644 --- a/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shield_from_asset_lock_transition.rs @@ -1,12 +1,33 @@ +use crate::asset_lock_proof::AssetLockProofWasm; use crate::error::{WasmDppError, WasmDppResult}; use crate::identifier::IdentifierWasm; +use crate::utils::try_from_options; use crate::{impl_wasm_conversions_serde, impl_wasm_type_info}; +use dpp::platform_value::BinaryData; use dpp::serialization::{PlatformDeserializable, PlatformSerializable}; use dpp::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; +use dpp::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; use dpp::state_transition::{StateTransition, StateTransitionLike}; use serde::{Deserialize, Serialize}; use wasm_bindgen::prelude::*; +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct ShieldFromAssetLockTransitionSimpleFields { + #[serde(default)] + actions: Vec, + #[serde(default)] + value_balance: u64, + #[serde(default)] + anchor: Vec, + #[serde(default)] + proof: Vec, + #[serde(default)] + binding_signature: Vec, + #[serde(default)] + signature: Vec, +} + #[wasm_bindgen(typescript_custom_section)] const TS_TYPES: &str = r#" /** @@ -33,11 +54,25 @@ export interface SerializedOrchardActionJSON { spendAuthSig: string; } +/** + * Options for constructing a ShieldFromAssetLockTransition. + * Uses WASM instance types for complex fields like AssetLockProof. + */ +export interface ShieldFromAssetLockTransitionOptions { + assetLockProof: AssetLockProof; + actions: SerializedOrchardAction[]; + valueBalance: bigint; + anchor: Uint8Array; + proof: Uint8Array; + bindingSignature: Uint8Array; + signature: Uint8Array; +} + /** * ShieldFromAssetLockTransition serialized as a plain object. */ export interface ShieldFromAssetLockTransitionObject { - $version: string; + $formatVersion: string; assetLockProof: AssetLockProofObject; actions: SerializedOrchardAction[]; valueBalance: bigint; @@ -51,7 +86,7 @@ export interface ShieldFromAssetLockTransitionObject { * ShieldFromAssetLockTransition serialized as JSON (human-readable). */ export interface ShieldFromAssetLockTransitionJSON { - $version: string; + $formatVersion: string; assetLockProof: AssetLockProofJSON; actions: SerializedOrchardActionJSON[]; valueBalance: number | string; @@ -64,6 +99,9 @@ export interface ShieldFromAssetLockTransitionJSON { #[wasm_bindgen] extern "C" { + #[wasm_bindgen(typescript_type = "ShieldFromAssetLockTransitionOptions")] + pub type ShieldFromAssetLockTransitionOptionsJs; + #[wasm_bindgen(typescript_type = "ShieldFromAssetLockTransitionObject")] pub type ShieldFromAssetLockTransitionObjectJs; @@ -91,10 +129,37 @@ impl From for ShieldFromAssetLockTransition { #[wasm_bindgen(js_class = ShieldFromAssetLockTransition)] impl ShieldFromAssetLockTransitionWasm { #[wasm_bindgen(constructor)] - pub fn new(value: ShieldFromAssetLockTransitionObjectJs) -> WasmDppResult { - let inner: ShieldFromAssetLockTransition = serde_wasm_bindgen::from_value(value.into()) - .map_err(|e| WasmDppError::serialization(e.to_string()))?; - Ok(ShieldFromAssetLockTransitionWasm(inner)) + pub fn new( + options: ShieldFromAssetLockTransitionOptionsJs, + ) -> WasmDppResult { + // Extract assetLockProof as a WASM instance (required) + let asset_lock: AssetLockProofWasm = try_from_options(&options, "assetLockProof")?; + + // Extract remaining simple fields via serde + let fields: ShieldFromAssetLockTransitionSimpleFields = + serde_wasm_bindgen::from_value(options.into()) + .map_err(|e| WasmDppError::serialization(e.to_string()))?; + + let anchor: [u8; 32] = fields + .anchor + .try_into() + .map_err(|_| WasmDppError::invalid_argument("anchor must be exactly 32 bytes"))?; + + let binding_signature: [u8; 64] = fields.binding_signature.try_into().map_err(|_| { + WasmDppError::invalid_argument("bindingSignature must be exactly 64 bytes") + })?; + + Ok(ShieldFromAssetLockTransitionWasm( + ShieldFromAssetLockTransition::V0(ShieldFromAssetLockTransitionV0 { + asset_lock_proof: asset_lock.into(), + actions: fields.actions, + value_balance: fields.value_balance, + anchor, + proof: fields.proof, + binding_signature, + signature: BinaryData::from(fields.signature), + }), + )) } #[wasm_bindgen(js_name = getType)] diff --git a/packages/wasm-dpp2/src/shielded/shield_transition.rs b/packages/wasm-dpp2/src/shielded/shield_transition.rs index 63d04d9193d..351e0b1f452 100644 --- a/packages/wasm-dpp2/src/shielded/shield_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shield_transition.rs @@ -65,7 +65,7 @@ export type AddressWitnessJSON = AddressWitnessP2pkhJSON | AddressWitnessP2shJSO * ShieldTransition serialized as a plain object. */ export interface ShieldTransitionObject { - $version: string; + $formatVersion: string; inputs: Record; actions: SerializedOrchardAction[]; amount: bigint; @@ -81,7 +81,7 @@ export interface ShieldTransitionObject { * ShieldTransition serialized as JSON (human-readable). */ export interface ShieldTransitionJSON { - $version: string; + $formatVersion: string; inputs: Record; actions: SerializedOrchardActionJSON[]; amount: number | string; diff --git a/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs b/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs index 529cb24fa97..0b54bb4ae58 100644 --- a/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shielded_transfer_transition.rs @@ -13,7 +13,7 @@ const TS_TYPES: &str = r#" * ShieldedTransferTransition serialized as a plain object. */ export interface ShieldedTransferTransitionObject { - $version: string; + $formatVersion: string; actions: SerializedOrchardAction[]; valueBalance: bigint; anchor: Uint8Array; @@ -25,7 +25,7 @@ export interface ShieldedTransferTransitionObject { * ShieldedTransferTransition serialized as JSON (human-readable). */ export interface ShieldedTransferTransitionJSON { - $version: string; + $formatVersion: string; actions: SerializedOrchardActionJSON[]; valueBalance: number | string; anchor: string; @@ -63,7 +63,9 @@ impl From for ShieldedTransferTransition { #[wasm_bindgen(js_class = ShieldedTransferTransition)] impl ShieldedTransferTransitionWasm { #[wasm_bindgen(constructor)] - pub fn new(value: ShieldedTransferTransitionObjectJs) -> WasmDppResult { + pub fn new( + value: ShieldedTransferTransitionObjectJs, + ) -> WasmDppResult { let inner: ShieldedTransferTransition = serde_wasm_bindgen::from_value(value.into()) .map_err(|e| WasmDppError::serialization(e.to_string()))?; Ok(ShieldedTransferTransitionWasm(inner)) diff --git a/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs b/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs index 0980bb8c714..cd0c85f47fb 100644 --- a/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs +++ b/packages/wasm-dpp2/src/shielded/shielded_withdrawal_transition.rs @@ -13,7 +13,7 @@ const TS_TYPES: &str = r#" * ShieldedWithdrawalTransition serialized as a plain object. */ export interface ShieldedWithdrawalTransitionObject { - $version: string; + $formatVersion: string; actions: SerializedOrchardAction[]; unshieldingAmount: bigint; anchor: Uint8Array; @@ -28,7 +28,7 @@ export interface ShieldedWithdrawalTransitionObject { * ShieldedWithdrawalTransition serialized as JSON (human-readable). */ export interface ShieldedWithdrawalTransitionJSON { - $version: string; + $formatVersion: string; actions: SerializedOrchardActionJSON[]; unshieldingAmount: number | string; anchor: string; @@ -69,7 +69,9 @@ impl From for ShieldedWithdrawalTransition { #[wasm_bindgen(js_class = ShieldedWithdrawalTransition)] impl ShieldedWithdrawalTransitionWasm { #[wasm_bindgen(constructor)] - pub fn new(value: ShieldedWithdrawalTransitionObjectJs) -> WasmDppResult { + pub fn new( + value: ShieldedWithdrawalTransitionObjectJs, + ) -> WasmDppResult { let inner: ShieldedWithdrawalTransition = serde_wasm_bindgen::from_value(value.into()) .map_err(|e| WasmDppError::serialization(e.to_string()))?; Ok(ShieldedWithdrawalTransitionWasm(inner)) diff --git a/packages/wasm-dpp2/src/shielded/unshield_transition.rs b/packages/wasm-dpp2/src/shielded/unshield_transition.rs index 1bc98c60ed1..9a529fae00c 100644 --- a/packages/wasm-dpp2/src/shielded/unshield_transition.rs +++ b/packages/wasm-dpp2/src/shielded/unshield_transition.rs @@ -13,7 +13,7 @@ const TS_TYPES: &str = r#" * UnshieldTransition serialized as a plain object. */ export interface UnshieldTransitionObject { - $version: string; + $formatVersion: string; outputAddress: object; actions: SerializedOrchardAction[]; unshieldingAmount: bigint; @@ -26,7 +26,7 @@ export interface UnshieldTransitionObject { * UnshieldTransition serialized as JSON (human-readable). */ export interface UnshieldTransitionJSON { - $version: string; + $formatVersion: string; outputAddress: object; actions: SerializedOrchardActionJSON[]; unshieldingAmount: number | string;