diff --git a/Cargo.toml b/Cargo.toml
index 686f517..8d1f7d3 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,12 +1,12 @@
# file: Cargo.toml
-# version: 296
+# version: 297
[workspace]
resolver = "3"
members = ["crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-wallet-lib"]
[workspace.package]
-version = "0.2.12-pre.8.fix.1"
+version = "0.2.12-pre.8.fix.2"
edition = "2024"
license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
diff --git a/crates/ksp-app-wallet-desk/frontend/main.html b/crates/ksp-app-wallet-desk/frontend/main.html
index df91c34..5bd5934 100644
--- a/crates/ksp-app-wallet-desk/frontend/main.html
+++ b/crates/ksp-app-wallet-desk/frontend/main.html
@@ -1,5 +1,5 @@
-
+
@@ -53,7 +53,7 @@
Dashboard
Identité autorisée et balance SOL via le Transport HTTP composite sont actives.
- 0.2.6
+ —
@@ -98,6 +98,7 @@
Notes—
Balance SOL—
SOL/USD moyenN.A.
+ Équivalent USDN.A.
@@ -257,6 +258,8 @@
Notes—
Lamports—
SOL—
+ SOL/USD moyenN.A.
+ Équivalent USDN.A.
RPC slot—
RPC api_version—
Transport—
diff --git a/crates/ksp-app-wallet-desk/frontend/ts/main.ts b/crates/ksp-app-wallet-desk/frontend/ts/main.ts
index f8d3e77..acb3359 100644
--- a/crates/ksp-app-wallet-desk/frontend/ts/main.ts
+++ b/crates/ksp-app-wallet-desk/frontend/ts/main.ts
@@ -1,5 +1,5 @@
// file: crates/ksp-app-wallet-desk/frontend/ts/main.ts
-// version: 17
+// version: 18
import { Modal } from "bootstrap";
import DataTable from "datatables.net-bs5";
@@ -379,8 +379,11 @@ function clearStrongViewSensitiveInputs(): void {
function clearBalanceProjection(): void {
setText("#currentWalletBalanceSol", "—");
setText("#currentWalletSolUsdAverage", "N.A.");
+ setText("#currentWalletSolUsdEquivalent", "N.A.");
setText("#detailsBalanceLamports", "—");
setText("#detailsBalanceSol", "—");
+ setText("#detailsSolUsdAverage", "N.A.");
+ setText("#detailsSolUsdEquivalent", "N.A.");
setText("#detailsBalanceSlot", "—");
setText("#detailsBalanceApiVersion", "—");
setText("#detailsBalanceTransport", "—");
@@ -1380,14 +1383,17 @@ function renderWalletBalance(balance: WalletBalanceDto): void {
return;
}
setText("#currentWalletBalanceSol", balance.sol);
- setText("#currentWalletSolUsdAverage", balance.solUsdAverage ? `${balance.solUsdAverage} USD` : "N.A.");
+ setText("#currentWalletSolUsdAverage", balance.solUsdAverage ? `${balance.solUsdAverage} USD/SOL` : "N.A.");
+ setText("#currentWalletSolUsdEquivalent", balance.solUsdEquivalent ? `${balance.solUsdEquivalent} USD` : "N.A.");
setText("#detailsBalanceLamports", balance.lamports);
setText("#detailsBalanceSol", balance.sol);
+ setText("#detailsSolUsdAverage", balance.solUsdAverage ? `${balance.solUsdAverage} USD/SOL` : "N.A.");
+ setText("#detailsSolUsdEquivalent", balance.solUsdEquivalent ? `${balance.solUsdEquivalent} USD` : "N.A.");
setText("#detailsBalanceSlot", balance.slot.toString());
setText("#detailsBalanceApiVersion", balance.apiVersion ?? "—");
setText("#detailsBalanceTransport", `${balance.transportProfile} / ${balance.transportRole}`);
- setText("#balanceStatus", "Balance réseau et prix moyen SOL/USD actualisés.");
- frontendInfo("main", "Wallet balance rendered", { averageAvailable: balance.solUsdAverage !== null, lamports: balance.lamports, slot: balance.slot, walletId: balance.walletId });
+ setText("#balanceStatus", "Balance réseau, prix moyen SOL/USD et équivalent USD actualisés.");
+ frontendInfo("main", "Wallet balance rendered", { averageAvailable: balance.solUsdAverage !== null, equivalentAvailable: balance.solUsdEquivalent !== null, lamports: balance.lamports, slot: balance.slot, walletId: balance.walletId });
}
async function refreshWalletBalance(): Promise {
@@ -1417,6 +1423,7 @@ function bindBalanceActions(): void {
}
function renderRuntimeStatus(status: RuntimeStatusDto): void {
+ setText("#appVersionBadge", status.applicationVersion);
setText("#runtimeVersion", status.applicationVersion);
setText("#runtimeCompositeProfile", status.activeCompositeProfile);
setText("#runtimeLoggingProfile", status.activeLoggingProfile ?? "fallback transitoire");
diff --git a/crates/ksp-app-wallet-desk/package.json b/crates/ksp-app-wallet-desk/package.json
index 30532d5..3688b2c 100644
--- a/crates/ksp-app-wallet-desk/package.json
+++ b/crates/ksp-app-wallet-desk/package.json
@@ -1,7 +1,7 @@
{
"name": "ksp-app-wallet-desk",
"private": true,
- "version": "0.2.12-pre.8.fix.1",
+ "version": "0.2.12-pre.8.fix.2",
"type": "module",
"scripts": {
"dev": "vite",
diff --git a/crates/ksp-app-wallet-desk/src/app_state.rs b/crates/ksp-app-wallet-desk/src/app_state.rs
index c8face0..a133177 100644
--- a/crates/ksp-app-wallet-desk/src/app_state.rs
+++ b/crates/ksp-app-wallet-desk/src/app_state.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-app-wallet-desk/src/app_state.rs
-// version: 20
+// version: 21
//! Shared backend state owned by the Wallet Desk Tauri application.
@@ -1160,17 +1160,19 @@ impl AppState {
));
}
let lamports = result.value();
+ let sol_usd_equivalent = crate::sol_usd_equivalent(lamports, sol_usd_average.as_deref());
let dto = crate::WalletBalanceDto {
api_version: result.context().api_version().map(str::to_owned),
lamports: lamports.to_string(),
slot: result.context().slot(),
sol: crate::format_lamports_as_sol(lamports),
sol_usd_average,
+ sol_usd_equivalent,
transport_profile: self.transport_runtime.profile_id().to_owned(),
transport_role: self.transport_runtime.role().as_str().to_owned(),
wallet_id: context.wallet_id.clone(),
};
- ksp_logging_lib::info!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_TRANSPORT, wallet_id = context.wallet_id.as_str(), capability = context.capability, lamports, slot = dto.slot, sol_usd_average_available = dto.sol_usd_average.is_some(), "Wallet balance refresh succeeded");
+ ksp_logging_lib::info!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_TRANSPORT, wallet_id = context.wallet_id.as_str(), capability = context.capability, lamports, slot = dto.slot, sol_usd_average_available = dto.sol_usd_average.is_some(), sol_usd_equivalent_available = dto.sol_usd_equivalent.is_some(), "Wallet balance refresh succeeded");
return std::result::Result::Ok(dto);
}
diff --git a/crates/ksp-app-wallet-desk/src/lib.rs b/crates/ksp-app-wallet-desk/src/lib.rs
index 13425a3..cc56958 100644
--- a/crates/ksp-app-wallet-desk/src/lib.rs
+++ b/crates/ksp-app-wallet-desk/src/lib.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-app-wallet-desk/src/lib.rs
-// version: 14
+// version: 15
//! Tauri desktop application shell for KSP Wallet management and inspection.
@@ -168,6 +168,8 @@ pub(crate) use self::wallet_balance::WalletBalanceDto;
pub(crate) use self::wallet_balance::average_sol_usd_prices;
/// Formats exact lamports as a decimal SOL string without floating point.
pub(crate) use self::wallet_balance::format_lamports_as_sol;
+/// Computes the exact USD equivalent of a lamport balance from the auxiliary SOL/USD average.
+pub(crate) use self::wallet_balance::sol_usd_equivalent;
/// Resolved Wallet Config and directory preparation status captured during application bootstrap.
pub(crate) use self::wallet_config::WalletConfigStartup;
/// Resolves the composite-selected Wallet Config and prepares its application-owned directory tree.
diff --git a/crates/ksp-app-wallet-desk/src/wallet_balance.rs b/crates/ksp-app-wallet-desk/src/wallet_balance.rs
index cfa5850..ec23ec8 100644
--- a/crates/ksp-app-wallet-desk/src/wallet_balance.rs
+++ b/crates/ksp-app-wallet-desk/src/wallet_balance.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-app-wallet-desk/src/wallet_balance.rs
-// version: 3
+// version: 4
//! Safe Wallet balance projection produced by the typed HTTP Transport path.
@@ -20,6 +20,8 @@ pub(crate) struct WalletBalanceDto {
pub(crate) sol: String,
/// Arithmetic mean of successful SOL/USD observations produced by this refresh, or unavailable when no provider returned one.
pub(crate) sol_usd_average: std::option::Option,
+ /// Exact USD equivalent of the current SOL balance using `sol_usd_average`, or unavailable when the auxiliary price is unavailable.
+ pub(crate) sol_usd_equivalent: std::option::Option,
/// Composite-selected Transport profile used for the RPC.
pub(crate) transport_profile: String,
/// Logical Transport role used for the RPC.
@@ -111,6 +113,58 @@ pub(crate) fn average_sol_usd_prices(prices: &[ksp_offchain_transport_lib::Marke
return std::option::Option::Some(format_scaled_decimal(coefficient, target_scale));
}
+/// Computes the exact USD equivalent of a lamport balance from the current SOL/USD average.
+///
+/// The output is rounded half-up to at most eight fractional digits. Missing or invalid auxiliary price data and arithmetic overflow degrade to `None`; the
+/// authoritative SOL balance remains unaffected.
+#[must_use]
+pub(crate) fn sol_usd_equivalent(lamports: u64, sol_usd_average: std::option::Option<&str>) -> std::option::Option {
+ const LAMPORT_SCALE: u8 = 9;
+ const OUTPUT_SCALE: u8 = 8;
+ let source = match sol_usd_average {
+ std::option::Option::Some(value) => value,
+ std::option::Option::None => return std::option::Option::None,
+ };
+ let average = match ksp_offchain_transport_lib::MarketPriceDecimal::parse(source) {
+ std::result::Result::Ok(value) => value,
+ std::result::Result::Err(_) => return std::option::Option::None,
+ };
+ let coefficient = match u128::from(lamports).checked_mul(average.coefficient()) {
+ std::option::Option::Some(value) => value,
+ std::option::Option::None => return std::option::Option::None,
+ };
+ let source_scale = u16::from(LAMPORT_SCALE) + u16::from(average.scale());
+ let target_scale = u16::from(OUTPUT_SCALE);
+ let rounded_coefficient = if source_scale <= target_scale {
+ let exponent = u32::from(target_scale - source_scale);
+ let factor = match 10_u128.checked_pow(exponent) {
+ std::option::Option::Some(value) => value,
+ std::option::Option::None => return std::option::Option::None,
+ };
+ match coefficient.checked_mul(factor) {
+ std::option::Option::Some(value) => value,
+ std::option::Option::None => return std::option::Option::None,
+ }
+ } else {
+ let exponent = u32::from(source_scale - target_scale);
+ let divisor = match 10_u128.checked_pow(exponent) {
+ std::option::Option::Some(value) => value,
+ std::option::Option::None => return std::option::Option::None,
+ };
+ let quotient = coefficient / divisor;
+ let remainder = coefficient % divisor;
+ if remainder >= divisor.div_ceil(2) {
+ match quotient.checked_add(1) {
+ std::option::Option::Some(value) => value,
+ std::option::Option::None => return std::option::Option::None,
+ }
+ } else {
+ quotient
+ }
+ };
+ return std::option::Option::Some(format_scaled_decimal(rounded_coefficient, OUTPUT_SCALE));
+}
+
fn format_scaled_decimal(mut coefficient: u128, mut scale: u8) -> String {
while scale > 0 && coefficient.is_multiple_of(10) {
coefficient /= 10;
diff --git a/crates/ksp-app-wallet-desk/tauri.conf.json b/crates/ksp-app-wallet-desk/tauri.conf.json
index 77d67c2..54e3e13 100644
--- a/crates/ksp-app-wallet-desk/tauri.conf.json
+++ b/crates/ksp-app-wallet-desk/tauri.conf.json
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "KSP Wallet Desk",
- "version": "0.2.12-pre.8.fix.1",
+ "version": "0.2.12-pre.8.fix.2",
"identifier": "com.sasedev.ksp-app-wallet-desk",
"build": {
"beforeDevCommand": {
diff --git a/crates/ksp-app-wallet-desk/tests/desktop_contract.rs b/crates/ksp-app-wallet-desk/tests/desktop_contract.rs
index b36a70e..86ec4a3 100644
--- a/crates/ksp-app-wallet-desk/tests/desktop_contract.rs
+++ b/crates/ksp-app-wallet-desk/tests/desktop_contract.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-app-wallet-desk/tests/desktop_contract.rs
-// version: 26
+// version: 27
//! Desktop build, shell and Config-status contract audits for Wallet Desk.
@@ -465,10 +465,6 @@ fn pre_018_packaged_runtime_bundles_config_resources_and_keeps_wallet_desk_versi
let package_version = package.pointer("/version").and_then(serde_json::Value::as_str);
assert_packaged_version_floor(package_version, "package.json");
assert_eq!(tauri_version, package_version, "desktop package metadata versions must remain synchronized");
- let packaged_version = match package_version {
- std::option::Option::Some(value) => value,
- std::option::Option::None => return,
- };
let resources = tauri.pointer("/bundle/resources").and_then(serde_json::Value::as_object);
assert!(resources.is_some(), "packaged Wallet Desk Config resources map must exist");
if let std::option::Option::Some(resources) = resources {
@@ -495,7 +491,9 @@ fn pre_018_packaged_runtime_bundles_config_resources_and_keeps_wallet_desk_versi
);
}
let main = read_text(root.join("frontend/main.html").as_path());
- assert!(main.contains(packaged_version));
+ let main_ts = read_text(root.join("frontend/ts/main.ts").as_path());
+ assert!(main.contains(r#"id="appVersionBadge""#));
+ assert!(main_ts.contains(r#"setText("#appVersionBadge", status.applicationVersion);"#));
let tauri_source = read_text(root.join("src/tauri.rs").as_path());
assert!(tauri_source.contains("ksp_config_lib::prepare_packaged_runtime"));
assert!(tauri_source.contains("tauri::utils::platform::resource_dir"));
@@ -503,7 +501,7 @@ fn pre_018_packaged_runtime_bundles_config_resources_and_keeps_wallet_desk_versi
}
#[test]
-fn v0_2_12_pre_008_wallet_balance_refresh_adds_one_auxiliary_sol_usd_average_only() {
+fn v0_2_12_pre_008_wallet_balance_refresh_adds_average_and_exact_usd_equivalent() {
let root = app_root();
let manifest = read_text(root.join("Cargo.toml").as_path());
let composite = read_text(root.join("../../config/composite.ksp-app-wallet-desk.json").as_path());
@@ -519,10 +517,16 @@ fn v0_2_12_pre_008_wallet_balance_refresh_adds_one_auxiliary_sol_usd_average_onl
assert!(state.contains("tokio::join!"));
assert!(state.contains("refresh_all().await"));
assert!(balance.contains("sol_usd_average"));
+ assert!(balance.contains("sol_usd_equivalent"));
assert!(balance.contains("average_sol_usd_prices"));
assert!(html.contains(r#"id="currentWalletSolUsdAverage""#));
- assert_eq!(html.matches("currentWalletSolUsdAverage").count(), 1);
- assert!(main.contains(r#"balance.solUsdAverage ? `${balance.solUsdAverage} USD` : "N.A.""#));
+ assert!(html.contains(r#"id="currentWalletSolUsdEquivalent""#));
+ assert!(html.contains(r#"id="detailsSolUsdAverage""#));
+ assert!(html.contains(r#"id="detailsSolUsdEquivalent""#));
+ assert!(main.contains(r#"balance.solUsdAverage ? `${balance.solUsdAverage} USD/SOL` : "N.A.""#));
+ assert!(main.contains(r#"balance.solUsdEquivalent ? `${balance.solUsdEquivalent} USD` : "N.A.""#));
+ assert!(!main.contains("parseFloat("));
+ assert!(!main.contains("Number(balance.sol"));
assert!(!main.contains("refresh_market_price"));
assert!(!main.contains("refresh_all_market_prices"));
assert!(!main.contains("fetch("));
diff --git a/crates/ksp-app-wallet-desk/unit_tests/wallet_balance.rs b/crates/ksp-app-wallet-desk/unit_tests/wallet_balance.rs
index e9cc9b9..b5bb293 100644
--- a/crates/ksp-app-wallet-desk/unit_tests/wallet_balance.rs
+++ b/crates/ksp-app-wallet-desk/unit_tests/wallet_balance.rs
@@ -1,5 +1,5 @@
// file: crates/ksp-app-wallet-desk/unit_tests/wallet_balance.rs
-// version: 2
+// version: 3
//! Unit tests for exact Wallet balance presentation.
@@ -31,3 +31,13 @@ fn sol_usd_average_returns_none_without_observation_and_handles_integer_fraction
assert_eq!(crate::average_sol_usd_prices(&[one, two]), std::option::Option::Some("1.5".to_owned()));
}
}
+
+#[test]
+fn sol_usd_equivalent_is_exact_rounded_and_unavailable_without_average() {
+ assert_eq!(crate::sol_usd_equivalent(0, std::option::Option::Some("104.01730745")), std::option::Option::Some("0".to_owned()));
+ assert_eq!(crate::sol_usd_equivalent(1_000_000_000, std::option::Option::Some("104.01730745")), std::option::Option::Some("104.01730745".to_owned()),);
+ assert_eq!(crate::sol_usd_equivalent(1_500_000_000, std::option::Option::Some("104.01730745")), std::option::Option::Some("156.02596118".to_owned()),);
+ assert_eq!(crate::sol_usd_equivalent(1, std::option::Option::Some("104.01730745")), std::option::Option::Some("0.0000001".to_owned()));
+ assert_eq!(crate::sol_usd_equivalent(1_000_000_000, std::option::Option::None), std::option::Option::None);
+ assert_eq!(crate::sol_usd_equivalent(1_000_000_000, std::option::Option::Some("invalid")), std::option::Option::None);
+}
diff --git a/deltas/0.2.12/pre.008-fix.002.md b/deltas/0.2.12/pre.008-fix.002.md
new file mode 100644
index 0000000..0d0696d
--- /dev/null
+++ b/deltas/0.2.12/pre.008-fix.002.md
@@ -0,0 +1,78 @@
+
+
+
+# Delta `0.2.12-pre.008-fix.002` — équivalent USD Wallet et version desktop dynamique
+
+## Base
+
+```text
+0.2.12-pre.008-fix.001
+```
+
+## Cause
+
+Le gate opérateur de `pre.008-fix.001` confirme `cargo fmt`, les audits KSP, `cargo check --workspace`, Clippy et le démarrage Tauri. Le refresh Wallet exécute aussi correctement `getBalance` et la collecte SOL/USD : huit providers sont chargés, six observations sont produites et la moyenne est disponible.
+
+Deux écarts restent néanmoins visibles :
+
+- la vue Details n'affiche ni le prix moyen SOL/USD calculé ni l'équivalent USD de la balance demandé par l'opérateur ;
+- le badge principal de Wallet Desk reste figé à `0.2.6`, et le canari historique de packaging recherche encore une version littérale dans `frontend/main.html`, ce qui fait échouer `cargo test -p ksp-app-wallet-desk` après le passage à `0.2.12-pre.8.fix.1`.
+
+## Correctif
+
+- version workspace : `0.2.12-pre.8.fix.2` ;
+- versions npm/Tauri Wallet Desk synchronisées sur `0.2.12-pre.8.fix.2` ;
+- `WalletBalanceDto` gagne `sol_usd_equivalent: Option` ;
+- l'équivalent USD est calculé côté Rust à partir des lamports et de `sol_usd_average`, sans `f64`/`f32`, avec arrondi half-up à huit décimales maximum et dégradation à `None` si le prix auxiliaire est absent ou si l'arithmétique ne peut pas être représentée ;
+- Dashboard et Details affichent désormais le prix moyen en `USD/SOL` et l'équivalent de la balance en `USD`, ou `N.A.` lorsque la moyenne auxiliaire est indisponible ;
+- le badge statique `0.2.6` est remplacé par un élément alimenté par `RuntimeStatusDto.application_version` ;
+- le canari `pre_018_packaged_runtime_bundles_config_resources_and_keeps_wallet_desk_version_coherent` vérifie le binding dynamique de version au lieu de rechercher la version courante en littéral dans le HTML ;
+- les canaris `pre.008` vérifient l'absence de calcul flottant frontend et la présence des projections moyenne/équivalent dans Dashboard et Details.
+
+## Invariants préservés
+
+```text
+getBalance autoritatif inchangé
+MarketPriceService::refresh_all inchangé
+providers utilisés pour la moyenne succès du refresh courant uniquement
+prix auxiliaire indisponible balance SOL valide, moyenne/équivalent N.A.
+provider_id / URL / credential frontend absents
+commande prix dédiée Wallet absente
+fetch / calcul prix frontend absents
+WalletSession VIEW/OWNER inchangée
+```
+
+Aucune donnée provider détaillée n'est ajoutée à Wallet Desk. L'équivalent USD est une projection auxiliaire dérivée de la moyenne déjà calculée par `pre.008` ; SOL Prices Desk reste l'application dédiée aux observations et disponibilités provider par provider.
+
+## Fichiers modifiés/ajoutés
+
+```text
+Cargo.toml
+crates/ksp-app-wallet-desk/package.json
+crates/ksp-app-wallet-desk/tauri.conf.json
+crates/ksp-app-wallet-desk/frontend/main.html
+crates/ksp-app-wallet-desk/frontend/ts/main.ts
+crates/ksp-app-wallet-desk/src/app_state.rs
+crates/ksp-app-wallet-desk/src/lib.rs
+crates/ksp-app-wallet-desk/src/wallet_balance.rs
+crates/ksp-app-wallet-desk/tests/desktop_contract.rs
+crates/ksp-app-wallet-desk/unit_tests/wallet_balance.rs
+docs/plans/019-V0_2_12_SOL_PRICES_DESK_PLAN.md
+docs/validation/015-V0_2_12_SOL_PRICES_DESK.md
+deltas/0.2.12/pre.008-fix.002.md
+```
+
+## Gate opérateur attendu
+
+```bash
+cargo fmt --all
+python3 scripts/audit_rust_workspace_rules.py
+python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.2.12
+cargo check --workspace
+cargo clippy --workspace --all-targets
+cargo test -p ksp-app-wallet-desk
+cargo test --workspace
+(cd crates/ksp-app-wallet-desk && cargo tauri dev)
+```
+
+Le contrôle manuel doit confirmer que Details affiche le prix moyen SOL/USD et l'équivalent USD après `Refresh balance`, que Dashboard reflète les mêmes valeurs, et que le badge d'application n'affiche plus une version statique obsolète.
diff --git a/docs/plans/019-V0_2_12_SOL_PRICES_DESK_PLAN.md b/docs/plans/019-V0_2_12_SOL_PRICES_DESK_PLAN.md
index b4d7626..0cf5cbc 100644
--- a/docs/plans/019-V0_2_12_SOL_PRICES_DESK_PLAN.md
+++ b/docs/plans/019-V0_2_12_SOL_PRICES_DESK_PLAN.md
@@ -1,5 +1,5 @@
-
+
# Plan `0.2.12` — SOL Prices Desk + intégration prix Wallet Desk
@@ -555,15 +555,21 @@ Finalisation de la table responsive sans changer le contrat de dispatch : résum
### `pre.008` — Intégration Wallet minimale + recalibrage de clôture
-**Statut : implémenté ; corrigé par `pre.008-fix.001`, gate opérateur à confirmer.**
+**Statut : réalisé ; corrigé par `pre.008-fix.001` puis `pre.008-fix.002`, gate opérateur final à confirmer.**
-Absorption de l'ancien couloir Wallet `pre.009`–`pre.011` dans une seule tranche : ajout `offchain_transport/public_keyless` au composite Wallet, runtime Off-chain auxiliaire, `refresh_all` lors de `refresh_wallet_balance`, moyenne SOL/USD backend sans `f64`, `WalletBalanceDto.sol_usd_average` et une seule case UI `SOL/USD moyen`/`N.A.`. Aucun panneau provider, aucune command prix Wallet, aucun provider ID frontend et aucune valorisation USD du solde. La tranche réconcilie également le présent forecast avec cette réduction de scope.
+Absorption de l'ancien couloir Wallet `pre.009`–`pre.011` dans une seule tranche : ajout `offchain_transport/public_keyless` au composite Wallet, runtime Off-chain auxiliaire, `refresh_all` lors de `refresh_wallet_balance`, moyenne SOL/USD backend sans `f64` et projection compacte dans Wallet Desk. La correction `fix.002` complète le besoin opérateur réellement attendu : `WalletBalanceDto` expose également un équivalent USD exact calculé côté Rust à partir des lamports et de la moyenne du refresh courant ; Dashboard et Details affichent le prix moyen et l'équivalent ou `N.A.`. Aucun panneau provider, aucune command prix Wallet et aucun provider ID frontend ne sont ajoutés. La tranche réconcilie également le forecast avec cette réduction de scope.
#### `pre.008-fix.001` — Correction du canari desktop et Clippy de la moyenne
+**Statut : réalisé ; gate opérateur technique partiellement PASS, interrompu par un canari historique de version.**
+
+Le premier gate de `pre.008` confirme `cargo check --workspace` mais révèle deux défauts purement techniques : des littéraux Rust non échappés dans le nouveau canari `desktop_contract.rs`, qui empêchent `cargo fmt` et les targets de test de parser le fichier, et un warning Clippy `manual_is_multiple_of` dans le formateur décimal Wallet. Le fix remplace les littéraux fragiles par des raw strings Rust et applique `u128::is_multiple_of(10)`. Son gate confirme ensuite `fmt`, audits, `check` et Clippy, puis s'arrête sur le canari `pre_018_packaged_runtime_bundles_config_resources_and_keeps_wallet_desk_version_coherent`, encore couplé à une version littérale dans le HTML. Le runtime Tauri et le refresh balance/prix fonctionnent.
+
+#### `pre.008-fix.002` — Équivalent USD, projection Details et version desktop dynamique
+
**Statut : implémenté ; gate opérateur à confirmer.**
-Le premier gate de `pre.008` confirme `cargo check --workspace` mais révèle deux défauts purement techniques : des littéraux Rust non échappés dans le nouveau canari `desktop_contract.rs`, qui empêchent `cargo fmt` et les targets de test de parser le fichier, et un warning Clippy `manual_is_multiple_of` dans le formateur décimal Wallet. Le fix remplace les littéraux fragiles par des raw strings Rust et applique `u128::is_multiple_of(10)`. Aucune logique de moyenne, aucun appel Off-chain, aucun DTO ni aucune UI n'est modifié.
+Correction du dernier écart fonctionnel de la tranche : le refresh produisait bien une moyenne SOL/USD mais elle n'était visible que dans le résumé Dashboard, tandis que Details n'affichait ni cette moyenne ni la valeur USD équivalente de la balance. `WalletBalanceDto.sol_usd_equivalent` est désormais calculé côté Rust sans flottant, à partir des lamports et de `sol_usd_average`, avec arrondi half-up à huit décimales maximum ; l'UI rend moyenne et équivalent dans Dashboard et Details, ou `N.A.` si le prix auxiliaire est indisponible. Le badge statique `0.2.6` est remplacé par `RuntimeStatusDto.application_version`, et le canari packaging vérifie ce binding version-neutral au lieu d'exiger la version courante en littéral dans `main.html`.
### `pre.009` — Hardening transversal + gate technique/live final
diff --git a/docs/validation/015-V0_2_12_SOL_PRICES_DESK.md b/docs/validation/015-V0_2_12_SOL_PRICES_DESK.md
index bc1635a..f3e3975 100644
--- a/docs/validation/015-V0_2_12_SOL_PRICES_DESK.md
+++ b/docs/validation/015-V0_2_12_SOL_PRICES_DESK.md
@@ -1,5 +1,5 @@
-
+
# Validation `0.2.12` — SOL Prices Desk + intégration Wallet Desk
@@ -586,9 +586,10 @@ getBalance + refresh_all lancés dans l
observations prises pour moyenne uniquement succès du refresh courant
moyenne backend, aucune conversion f64, 8 décimales max
WalletBalanceDto.sol_usd_average Option
-UI Wallet une case SOL/USD moyen / N.A.
+WalletBalanceDto.sol_usd_equivalent Option, calcul exact backend
+UI Wallet moyenne + équivalent dans Dashboard/Details, sinon N.A.
provider_id / URL / credential / command prix dans frontend absent
-valorisation balance SOL en USD absente
+calcul de valorisation frontend / f64 absent
WalletSession / VIEW / OWNER / secrets inchangés
```
@@ -615,16 +616,60 @@ cargo check --workspace PASS
cargo clippy --workspace --all-targets FAIL — desktop_contract.rs + manual_is_multiple_of
```
-Le correctif attendu est strictement mécanique :
+`pre.008-fix.001` corrige ces deux défauts mécaniques. Son gate opérateur du `27 août 2026` donne ensuite :
```text
-desktop_contract.rs raw strings Rust valides
-wallet_balance.rs coefficient.is_multiple_of(10)
-version 0.2.12-pre.8.fix.1
-logique moyenne / Off-chain / DTO / frontend inchangée
+cargo fmt --all PASS
+python3 scripts/audit_rust_workspace_rules.py PASS
+python3 scripts/audit_markdown_tables.py README.md ... deltas PASS
+cargo check --workspace PASS
+cargo clippy --workspace --all-targets PASS
+cargo test -p ksp-app-wallet-desk FAIL — 1 canari desktop historique
+cargo test --workspace NOT RUN après l'échec ciblé
+cargo tauri dev Wallet Desk PASS
```
-Gate opérateur à rejouer après application :
+Le test ciblé passe `61` unit tests, `3` tests de composition et `18/19` tests `desktop_contract`. L'unique échec est :
+
+```text
+pre_018_packaged_runtime_bundles_config_resources_and_keeps_wallet_desk_version_coherent
+assertion failed: main.contains(packaged_version)
+```
+
+Le runtime manuel confirme en parallèle que le chemin métier est opérationnel :
+
+```text
+provider_count=8
+observed_provider_count=6
+average_available=true
+sol_usd_average_available=true
+getBalance succès
+cargo tauri dev succès
+```
+
+Deux écarts restent donc à corriger dans `pre.008-fix.002` : le badge principal est encore figé à `0.2.6`, et la vue Details ne montre pas l'information prix auxiliaire. La demande opérateur précise en outre que l'équivalent USD de la balance doit être visible.
+
+## 26. Gate `0.2.12-pre.008-fix.002` — équivalent USD et version dynamique
+
+Le correctif attendu reste dans la responsabilité Wallet condensée de `pre.008` :
+
+```text
+WalletBalanceDto.sol_usd_average conservé
+WalletBalanceDto.sol_usd_equivalent ajouté, Option
+source de l'équivalent lamports + moyenne du refresh courant
+calcul Rust/u128, aucun f64/f32/JS
+arrondi équivalent half-up, 8 décimales max
+prix auxiliaire indisponible moyenne N.A. + équivalent N.A.
+balance SOL autoritative inchangée
+Dashboard moyenne + équivalent
+Details moyenne + équivalent
+badge 0.2.6 statique supprimé
+version affichée RuntimeStatusDto.application_version
+canari packaging version-neutral, vérifie le binding dynamique
+provider_id / URL / credential / command prix Wallet absent
+```
+
+Gate opérateur à rejouer :
```bash
cargo fmt --all
@@ -636,4 +681,3 @@ cargo test -p ksp-app-wallet-desk
cargo test --workspace
(cd crates/ksp-app-wallet-desk && cargo tauri dev)
```
-