// file: crates/ksp-wallet-lib/src/runtime.rs // version: 2 //! Version-neutral unlocked Wallet state dispatch used by stable OWNER/VIEW handles. /// One staged native envelope produced by an authenticated administration operation. pub(crate) enum StagedEnvelope { V1(crate::KspWalletEnvelopeV1), V2(crate::KspWalletEnvelopeV2), } /// Version-neutral OWNER runtime state. pub(crate) enum OwnerState { V1(crate::OwnerStateV1), V2(crate::OwnerStateV2), } impl OwnerState { /// Exports the Solana identity through the requested external transfer format. pub(crate) fn export_transfer(&self, format: crate::WalletTransferFormat) -> ksp_core_lib::Result> { return match self { Self::V1(state) => state.export_transfer(format), Self::V2(state) => state.export_transfer(format), }; } /// Signs one message with the OWNER-authorized Solana identity. pub(crate) fn sign_message(&self, message: &[u8]) -> ksp_core_lib::Result<[u8; crate::KSPWALLET_SOLANA_SIGNATURE_BYTES]> { return match self { Self::V1(state) => state.sign_message(message), Self::V2(state) => state.sign_message(message), }; } /// Stages an authenticated metadata replacement without publishing it. pub(crate) fn stage_metadata_payload(&self, payload: crate::MetadataPayloadV1) -> ksp_core_lib::Result<(StagedEnvelope, crate::WalletInfo)> { return match self { Self::V1(state) => state.stage_metadata_payload(payload).map(|(envelope, info)| return (StagedEnvelope::V1(envelope), info)), Self::V2(state) => state.stage_metadata_payload(payload).map(|(envelope, info)| return (StagedEnvelope::V2(envelope), info)), }; } /// Stages an OWNER credential rotation in the state native format. pub(crate) async fn stage_owner_password_rotation(&self, password: crate::OwnerPassword) -> ksp_core_lib::Result { return match self { Self::V1(state) => state.stage_owner_password_rotation(password).await.map(StagedEnvelope::V1), Self::V2(state) => state.stage_owner_password_rotation(password).await.map(StagedEnvelope::V2), }; } /// Stages a VIEW credential rotation in the state native format. pub(crate) async fn stage_view_password_rotation(&self, password: crate::ViewPassword) -> ksp_core_lib::Result { return match self { Self::V1(state) => state.stage_view_password_rotation(password).await.map(StagedEnvelope::V1), Self::V2(state) => state.stage_view_password_rotation(password).await.map(StagedEnvelope::V2), }; } /// Stages strong VIEW disable while retaining OWNER authority. pub(crate) fn stage_disable_view(&self) -> ksp_core_lib::Result<(StagedEnvelope, crate::SecretKeyV1)> { return match self { Self::V1(state) => state.stage_disable_view().map(|(envelope, key)| return (StagedEnvelope::V1(envelope), key)), Self::V2(state) => state.stage_disable_view().map(|(envelope, key)| return (StagedEnvelope::V2(envelope), key)), }; } /// Stages strong VIEW recreation under a new credential. pub(crate) async fn stage_recreate_view(&self, password: crate::ViewPassword) -> ksp_core_lib::Result<(StagedEnvelope, crate::SecretKeyV1)> { return match self { Self::V1(state) => state.stage_recreate_view(password).await.map(|(envelope, key)| return (StagedEnvelope::V1(envelope), key)), Self::V2(state) => state.stage_recreate_view(password).await.map(|(envelope, key)| return (StagedEnvelope::V2(envelope), key)), }; } /// Atomically publishes a staged envelope after format-specific state-conflict verification. pub(crate) async fn persist_staged(&self, destination: std::path::PathBuf, envelope: &StagedEnvelope) -> ksp_core_lib::Result<()> { return match (self, envelope) { (Self::V1(state), StagedEnvelope::V1(staged)) => { let serialized = match staged.to_json_bytes() { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; crate::replace_wallet_file_v1(destination, state.envelope().clone(), serialized).await }, (Self::V2(state), StagedEnvelope::V2(staged)) => { let serialized = match staged.to_binary_bytes() { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; crate::replace_wallet_file_v2(destination, state.envelope().clone(), serialized).await }, _ => std::result::Result::Err(version_state_error()), }; } /// Replaces the in-memory authenticated envelope after successful persistence. pub(crate) fn apply_envelope(&mut self, envelope: StagedEnvelope) -> ksp_core_lib::Result<()> { return match (self, envelope) { (Self::V1(state), StagedEnvelope::V1(value)) => { state.apply_envelope(value); std::result::Result::Ok(()) }, (Self::V2(state), StagedEnvelope::V2(value)) => { state.apply_envelope(value); std::result::Result::Ok(()) }, _ => std::result::Result::Err(version_state_error()), }; } /// Replaces the envelope and metadata key after a strong VIEW administration operation. pub(crate) fn apply_strong_view_state(&mut self, envelope: StagedEnvelope, metadata_key: crate::SecretKeyV1) -> ksp_core_lib::Result<()> { return match (self, envelope) { (Self::V1(state), StagedEnvelope::V1(value)) => { state.apply_strong_view_state(value, metadata_key); std::result::Result::Ok(()) }, (Self::V2(state), StagedEnvelope::V2(value)) => { state.apply_strong_view_state(value, metadata_key); std::result::Result::Ok(()) }, _ => std::result::Result::Err(version_state_error()), }; } /// Serializes the authenticated state in its native V1 or V2 wire format. pub(crate) fn native_bytes(&self) -> ksp_core_lib::Result> { return match self { Self::V1(state) => state.envelope().to_json_bytes(), Self::V2(state) => state.envelope().to_binary_bytes(), }; } /// Serializes only V1 state as JSON and rejects V2 instead of converting formats. pub(crate) fn json_bytes_v1(&self) -> ksp_core_lib::Result> { return match self { Self::V1(state) => state.envelope().to_json_bytes(), Self::V2(_) => std::result::Result::Err(v1_serialization_error()), }; } } /// Version-neutral VIEW runtime state. pub(crate) enum ViewState { V1(crate::ViewStateV1), V2(crate::ViewStateV2), } impl ViewState { /// Stages a VIEW credential rotation in the state native format. pub(crate) async fn stage_view_password_rotation(&self, password: crate::ViewPassword) -> ksp_core_lib::Result { return match self { Self::V1(state) => state.stage_view_password_rotation(password).await.map(StagedEnvelope::V1), Self::V2(state) => state.stage_view_password_rotation(password).await.map(StagedEnvelope::V2), }; } /// Atomically publishes a staged envelope after format-specific state-conflict verification. pub(crate) async fn persist_staged(&self, destination: std::path::PathBuf, envelope: &StagedEnvelope) -> ksp_core_lib::Result<()> { return match (self, envelope) { (Self::V1(state), StagedEnvelope::V1(staged)) => { let serialized = match staged.to_json_bytes() { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; crate::replace_wallet_file_v1(destination, state.envelope().clone(), serialized).await }, (Self::V2(state), StagedEnvelope::V2(staged)) => { let serialized = match staged.to_binary_bytes() { std::result::Result::Ok(value) => value, std::result::Result::Err(error) => return std::result::Result::Err(error), }; crate::replace_wallet_file_v2(destination, state.envelope().clone(), serialized).await }, _ => std::result::Result::Err(version_state_error()), }; } /// Replaces the in-memory authenticated envelope after successful persistence. pub(crate) fn apply_envelope(&mut self, envelope: StagedEnvelope) -> ksp_core_lib::Result<()> { return match (self, envelope) { (Self::V1(state), StagedEnvelope::V1(value)) => { state.apply_envelope(value); std::result::Result::Ok(()) }, (Self::V2(state), StagedEnvelope::V2(value)) => { state.apply_envelope(value); std::result::Result::Ok(()) }, _ => std::result::Result::Err(version_state_error()), }; } /// Serializes the authenticated state in its native V1 or V2 wire format. pub(crate) fn native_bytes(&self) -> ksp_core_lib::Result> { return match self { Self::V1(state) => state.envelope().to_json_bytes(), Self::V2(state) => state.envelope().to_binary_bytes(), }; } /// Serializes only V1 state as JSON and rejects V2 instead of converting formats. pub(crate) fn json_bytes_v1(&self) -> ksp_core_lib::Result> { return match self { Self::V1(state) => state.envelope().to_json_bytes(), Self::V2(_) => std::result::Result::Err(v1_serialization_error()), }; } } fn version_state_error() -> ksp_core_lib::Error { return ksp_core_lib::Error::new(crate::ERROR_CODE_FORMAT_INVALID, "Wallet runtime state and staged format are inconsistent"); } fn v1_serialization_error() -> ksp_core_lib::Error { return ksp_core_lib::Error::new(crate::ERROR_CODE_FORMAT_INVALID, "Wallet is not a V1 JSON document"); }