v0.2.12-pre.008-fix.002

This commit is contained in:
2026-08-27 15:18:34 +02:00
parent fe46cbe3bd
commit f0493df09e
13 changed files with 248 additions and 38 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml # file: Cargo.toml
# version: 296 # version: 297
[workspace] [workspace]
resolver = "3" 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"] 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] [workspace.package]
version = "0.2.12-pre.8.fix.1" version = "0.2.12-pre.8.fix.2"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -1,5 +1,5 @@
<!-- file: crates/ksp-app-wallet-desk/frontend/main.html --> <!-- file: crates/ksp-app-wallet-desk/frontend/main.html -->
<!-- version: 6 --> <!-- version: 7 -->
<!DOCTYPE html> <!DOCTYPE html>
<html lang="fr"> <html lang="fr">
<head> <head>
@@ -53,7 +53,7 @@
<h1 id="viewTitle" class="h3 mb-1">Dashboard</h1> <h1 id="viewTitle" class="h3 mb-1">Dashboard</h1>
<p class="text-body-secondary mb-0">Identité autorisée et balance SOL via le Transport HTTP composite sont actives.</p> <p class="text-body-secondary mb-0">Identité autorisée et balance SOL via le Transport HTTP composite sont actives.</p>
</div> </div>
<span class="badge text-bg-secondary">0.2.6</span> <span id="appVersionBadge" class="badge text-bg-secondary"></span>
</div> </div>
<section data-view-panel="dashboard"> <section data-view-panel="dashboard">
<div class="row g-3"> <div class="row g-3">
@@ -98,6 +98,7 @@
<dt class="col-5">Notes</dt><dd id="currentWalletNotes" class="col-7 text-break"></dd> <dt class="col-5">Notes</dt><dd id="currentWalletNotes" class="col-7 text-break"></dd>
<dt class="col-5">Balance SOL</dt><dd id="currentWalletBalanceSol" class="col-7"></dd> <dt class="col-5">Balance SOL</dt><dd id="currentWalletBalanceSol" class="col-7"></dd>
<dt class="col-5">SOL/USD moyen</dt><dd id="currentWalletSolUsdAverage" class="col-7">N.A.</dd> <dt class="col-5">SOL/USD moyen</dt><dd id="currentWalletSolUsdAverage" class="col-7">N.A.</dd>
<dt class="col-5">Équivalent USD</dt><dd id="currentWalletSolUsdEquivalent" class="col-7">N.A.</dd>
</dl> </dl>
</div> </div>
</div> </div>
@@ -257,6 +258,8 @@
<dt class="col-md-4">Notes</dt><dd id="detailsWalletNotes" class="col-md-8 text-break"></dd> <dt class="col-md-4">Notes</dt><dd id="detailsWalletNotes" class="col-md-8 text-break"></dd>
<dt class="col-md-4">Lamports</dt><dd id="detailsBalanceLamports" class="col-md-8"></dd> <dt class="col-md-4">Lamports</dt><dd id="detailsBalanceLamports" class="col-md-8"></dd>
<dt class="col-md-4">SOL</dt><dd id="detailsBalanceSol" class="col-md-8"></dd> <dt class="col-md-4">SOL</dt><dd id="detailsBalanceSol" class="col-md-8"></dd>
<dt class="col-md-4">SOL/USD moyen</dt><dd id="detailsSolUsdAverage" class="col-md-8">N.A.</dd>
<dt class="col-md-4">Équivalent USD</dt><dd id="detailsSolUsdEquivalent" class="col-md-8">N.A.</dd>
<dt class="col-md-4">RPC slot</dt><dd id="detailsBalanceSlot" class="col-md-8"></dd> <dt class="col-md-4">RPC slot</dt><dd id="detailsBalanceSlot" class="col-md-8"></dd>
<dt class="col-md-4">RPC api_version</dt><dd id="detailsBalanceApiVersion" class="col-md-8"></dd> <dt class="col-md-4">RPC api_version</dt><dd id="detailsBalanceApiVersion" class="col-md-8"></dd>
<dt class="col-md-4">Transport</dt><dd id="detailsBalanceTransport" class="col-md-8"></dd> <dt class="col-md-4">Transport</dt><dd id="detailsBalanceTransport" class="col-md-8"></dd>

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-wallet-desk/frontend/ts/main.ts // file: crates/ksp-app-wallet-desk/frontend/ts/main.ts
// version: 17 // version: 18
import { Modal } from "bootstrap"; import { Modal } from "bootstrap";
import DataTable from "datatables.net-bs5"; import DataTable from "datatables.net-bs5";
@@ -379,8 +379,11 @@ function clearStrongViewSensitiveInputs(): void {
function clearBalanceProjection(): void { function clearBalanceProjection(): void {
setText("#currentWalletBalanceSol", "—"); setText("#currentWalletBalanceSol", "—");
setText("#currentWalletSolUsdAverage", "N.A."); setText("#currentWalletSolUsdAverage", "N.A.");
setText("#currentWalletSolUsdEquivalent", "N.A.");
setText("#detailsBalanceLamports", "—"); setText("#detailsBalanceLamports", "—");
setText("#detailsBalanceSol", "—"); setText("#detailsBalanceSol", "—");
setText("#detailsSolUsdAverage", "N.A.");
setText("#detailsSolUsdEquivalent", "N.A.");
setText("#detailsBalanceSlot", "—"); setText("#detailsBalanceSlot", "—");
setText("#detailsBalanceApiVersion", "—"); setText("#detailsBalanceApiVersion", "—");
setText("#detailsBalanceTransport", "—"); setText("#detailsBalanceTransport", "—");
@@ -1380,14 +1383,17 @@ function renderWalletBalance(balance: WalletBalanceDto): void {
return; return;
} }
setText("#currentWalletBalanceSol", balance.sol); 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("#detailsBalanceLamports", balance.lamports);
setText("#detailsBalanceSol", balance.sol); 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("#detailsBalanceSlot", balance.slot.toString());
setText("#detailsBalanceApiVersion", balance.apiVersion ?? "—"); setText("#detailsBalanceApiVersion", balance.apiVersion ?? "—");
setText("#detailsBalanceTransport", `${balance.transportProfile} / ${balance.transportRole}`); setText("#detailsBalanceTransport", `${balance.transportProfile} / ${balance.transportRole}`);
setText("#balanceStatus", "Balance réseau et prix moyen SOL/USD actualisés."); setText("#balanceStatus", "Balance réseau, prix moyen SOL/USD et équivalent USD actualisés.");
frontendInfo("main", "Wallet balance rendered", { averageAvailable: balance.solUsdAverage !== null, lamports: balance.lamports, slot: balance.slot, walletId: balance.walletId }); 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<void> { async function refreshWalletBalance(): Promise<void> {
@@ -1417,6 +1423,7 @@ function bindBalanceActions(): void {
} }
function renderRuntimeStatus(status: RuntimeStatusDto): void { function renderRuntimeStatus(status: RuntimeStatusDto): void {
setText("#appVersionBadge", status.applicationVersion);
setText("#runtimeVersion", status.applicationVersion); setText("#runtimeVersion", status.applicationVersion);
setText("#runtimeCompositeProfile", status.activeCompositeProfile); setText("#runtimeCompositeProfile", status.activeCompositeProfile);
setText("#runtimeLoggingProfile", status.activeLoggingProfile ?? "fallback transitoire"); setText("#runtimeLoggingProfile", status.activeLoggingProfile ?? "fallback transitoire");

View File

@@ -1,7 +1,7 @@
{ {
"name": "ksp-app-wallet-desk", "name": "ksp-app-wallet-desk",
"private": true, "private": true,
"version": "0.2.12-pre.8.fix.1", "version": "0.2.12-pre.8.fix.2",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-wallet-desk/src/app_state.rs // file: crates/ksp-app-wallet-desk/src/app_state.rs
// version: 20 // version: 21
//! Shared backend state owned by the Wallet Desk Tauri application. //! Shared backend state owned by the Wallet Desk Tauri application.
@@ -1160,17 +1160,19 @@ impl AppState {
)); ));
} }
let lamports = result.value(); let lamports = result.value();
let sol_usd_equivalent = crate::sol_usd_equivalent(lamports, sol_usd_average.as_deref());
let dto = crate::WalletBalanceDto { let dto = crate::WalletBalanceDto {
api_version: result.context().api_version().map(str::to_owned), api_version: result.context().api_version().map(str::to_owned),
lamports: lamports.to_string(), lamports: lamports.to_string(),
slot: result.context().slot(), slot: result.context().slot(),
sol: crate::format_lamports_as_sol(lamports), sol: crate::format_lamports_as_sol(lamports),
sol_usd_average, sol_usd_average,
sol_usd_equivalent,
transport_profile: self.transport_runtime.profile_id().to_owned(), transport_profile: self.transport_runtime.profile_id().to_owned(),
transport_role: self.transport_runtime.role().as_str().to_owned(), transport_role: self.transport_runtime.role().as_str().to_owned(),
wallet_id: context.wallet_id.clone(), 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); return std::result::Result::Ok(dto);
} }

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-wallet-desk/src/lib.rs // file: crates/ksp-app-wallet-desk/src/lib.rs
// version: 14 // version: 15
//! Tauri desktop application shell for KSP Wallet management and inspection. //! 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; pub(crate) use self::wallet_balance::average_sol_usd_prices;
/// Formats exact lamports as a decimal SOL string without floating point. /// Formats exact lamports as a decimal SOL string without floating point.
pub(crate) use self::wallet_balance::format_lamports_as_sol; 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. /// Resolved Wallet Config and directory preparation status captured during application bootstrap.
pub(crate) use self::wallet_config::WalletConfigStartup; pub(crate) use self::wallet_config::WalletConfigStartup;
/// Resolves the composite-selected Wallet Config and prepares its application-owned directory tree. /// Resolves the composite-selected Wallet Config and prepares its application-owned directory tree.

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-wallet-desk/src/wallet_balance.rs // 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. //! Safe Wallet balance projection produced by the typed HTTP Transport path.
@@ -20,6 +20,8 @@ pub(crate) struct WalletBalanceDto {
pub(crate) sol: String, pub(crate) sol: String,
/// Arithmetic mean of successful SOL/USD observations produced by this refresh, or unavailable when no provider returned one. /// 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<String>, pub(crate) sol_usd_average: std::option::Option<String>,
/// 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<String>,
/// Composite-selected Transport profile used for the RPC. /// Composite-selected Transport profile used for the RPC.
pub(crate) transport_profile: String, pub(crate) transport_profile: String,
/// Logical Transport role used for the RPC. /// 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)); 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<String> {
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 { fn format_scaled_decimal(mut coefficient: u128, mut scale: u8) -> String {
while scale > 0 && coefficient.is_multiple_of(10) { while scale > 0 && coefficient.is_multiple_of(10) {
coefficient /= 10; coefficient /= 10;

View File

@@ -1,7 +1,7 @@
{ {
"$schema": "https://schema.tauri.app/config/2", "$schema": "https://schema.tauri.app/config/2",
"productName": "KSP Wallet Desk", "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", "identifier": "com.sasedev.ksp-app-wallet-desk",
"build": { "build": {
"beforeDevCommand": { "beforeDevCommand": {

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-wallet-desk/tests/desktop_contract.rs // 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. //! 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); let package_version = package.pointer("/version").and_then(serde_json::Value::as_str);
assert_packaged_version_floor(package_version, "package.json"); assert_packaged_version_floor(package_version, "package.json");
assert_eq!(tauri_version, package_version, "desktop package metadata versions must remain synchronized"); 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); 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"); assert!(resources.is_some(), "packaged Wallet Desk Config resources map must exist");
if let std::option::Option::Some(resources) = resources { 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()); 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()); 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("ksp_config_lib::prepare_packaged_runtime"));
assert!(tauri_source.contains("tauri::utils::platform::resource_dir")); 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] #[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 root = app_root();
let manifest = read_text(root.join("Cargo.toml").as_path()); 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()); 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("tokio::join!"));
assert!(state.contains("refresh_all().await")); assert!(state.contains("refresh_all().await"));
assert!(balance.contains("sol_usd_average")); assert!(balance.contains("sol_usd_average"));
assert!(balance.contains("sol_usd_equivalent"));
assert!(balance.contains("average_sol_usd_prices")); assert!(balance.contains("average_sol_usd_prices"));
assert!(html.contains(r#"id="currentWalletSolUsdAverage""#)); assert!(html.contains(r#"id="currentWalletSolUsdAverage""#));
assert_eq!(html.matches("currentWalletSolUsdAverage").count(), 1); assert!(html.contains(r#"id="currentWalletSolUsdEquivalent""#));
assert!(main.contains(r#"balance.solUsdAverage ? `${balance.solUsdAverage} USD` : "N.A.""#)); 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_market_price"));
assert!(!main.contains("refresh_all_market_prices")); assert!(!main.contains("refresh_all_market_prices"));
assert!(!main.contains("fetch(")); assert!(!main.contains("fetch("));

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-wallet-desk/unit_tests/wallet_balance.rs // file: crates/ksp-app-wallet-desk/unit_tests/wallet_balance.rs
// version: 2 // version: 3
//! Unit tests for exact Wallet balance presentation. //! 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())); 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);
}

View File

@@ -0,0 +1,78 @@
<!-- file: deltas/0.2.12/pre.008-fix.002.md -->
<!-- version: 1 -->
# 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<String>` ;
- 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.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/019-V0_2_12_SOL_PRICES_DESK_PLAN.md --> <!-- file: docs/plans/019-V0_2_12_SOL_PRICES_DESK_PLAN.md -->
<!-- version: 15 --> <!-- version: 16 -->
# Plan `0.2.12` — SOL Prices Desk + intégration prix Wallet Desk # 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 ### `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 #### `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.** **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 ### `pre.009` — Hardening transversal + gate technique/live final

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/015-V0_2_12_SOL_PRICES_DESK.md --> <!-- file: docs/validation/015-V0_2_12_SOL_PRICES_DESK.md -->
<!-- version: 14 --> <!-- version: 15 -->
# Validation `0.2.12` — SOL Prices Desk + intégration Wallet Desk # 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 observations prises pour moyenne uniquement succès du refresh courant
moyenne backend, aucune conversion f64, 8 décimales max moyenne backend, aucune conversion f64, 8 décimales max
WalletBalanceDto.sol_usd_average Option<String> WalletBalanceDto.sol_usd_average Option<String>
UI Wallet une case SOL/USD moyen / N.A. WalletBalanceDto.sol_usd_equivalent Option<String>, calcul exact backend
UI Wallet moyenne + équivalent dans Dashboard/Details, sinon N.A.
provider_id / URL / credential / command prix dans frontend absent 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 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 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 ```text
desktop_contract.rs raw strings Rust valides cargo fmt --all PASS
wallet_balance.rs coefficient.is_multiple_of(10) python3 scripts/audit_rust_workspace_rules.py PASS
version 0.2.12-pre.8.fix.1 python3 scripts/audit_markdown_tables.py README.md ... deltas PASS
logique moyenne / Off-chain / DTO / frontend inchangée 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<String>
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 ```bash
cargo fmt --all cargo fmt --all
@@ -636,4 +681,3 @@ cargo test -p ksp-app-wallet-desk
cargo test --workspace cargo test --workspace
(cd crates/ksp-app-wallet-desk && cargo tauri dev) (cd crates/ksp-app-wallet-desk && cargo tauri dev)
``` ```