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,5 +1,5 @@
<!-- file: crates/ksp-app-wallet-desk/frontend/main.html -->
<!-- version: 6 -->
<!-- version: 7 -->
<!DOCTYPE html>
<html lang="fr">
<head>
@@ -53,7 +53,7 @@
<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>
</div>
<span class="badge text-bg-secondary">0.2.6</span>
<span id="appVersionBadge" class="badge text-bg-secondary"></span>
</div>
<section data-view-panel="dashboard">
<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">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">Équivalent USD</dt><dd id="currentWalletSolUsdEquivalent" class="col-7">N.A.</dd>
</dl>
</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">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/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 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>

View File

@@ -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<void> {
@@ -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");

View File

@@ -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",

View File

@@ -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);
}

View File

@@ -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.

View File

@@ -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<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.
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<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 {
while scale > 0 && coefficient.is_multiple_of(10) {
coefficient /= 10;

View File

@@ -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": {

View File

@@ -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("));

View File

@@ -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);
}