v0.2.12-pre.008

This commit is contained in:
2026-08-27 14:41:08 +02:00
parent 2042482592
commit f1e365ec97
22 changed files with 631 additions and 129 deletions

View File

@@ -1,5 +1,5 @@
// file: crates/ksp-app-wallet-desk/src/wallet_balance.rs
// version: 1
// version: 2
//! Safe Wallet balance projection produced by the typed HTTP Transport path.
@@ -18,6 +18,8 @@ pub(crate) struct WalletBalanceDto {
pub(crate) slot: u64,
/// Exact decimal SOL presentation derived from lamports without floating point.
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>,
/// Composite-selected Transport profile used for the RPC.
pub(crate) transport_profile: String,
/// Logical Transport role used for the RPC.
@@ -34,6 +36,98 @@ pub(crate) fn format_lamports_as_sol(lamports: u64) -> String {
return format!("{whole}.{fractional:09}");
}
/// Computes a deterministic SOL/USD arithmetic mean without IEEE-754 conversion.
///
/// The output is rounded half-up to at most eight fractional digits and trailing zeroes are removed. Arithmetic overflow degrades to `None`, which Wallet Desk
/// renders as `N.A.` without affecting the authoritative SOL balance.
#[must_use]
pub(crate) fn average_sol_usd_prices(prices: &[ksp_offchain_transport_lib::MarketPriceDecimal]) -> std::option::Option<String> {
const OUTPUT_SCALE: u8 = 8;
if prices.is_empty() {
return std::option::Option::None;
}
let max_scale = prices.iter().map(ksp_offchain_transport_lib::MarketPriceDecimal::scale).max();
let max_scale = match max_scale {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::option::Option::None,
};
let mut sum = 0_u128;
for price in prices {
let exponent = u32::from(max_scale.saturating_sub(price.scale()));
let factor = 10_u128.checked_pow(exponent);
let factor = match factor {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::option::Option::None,
};
let coefficient = price.coefficient().checked_mul(factor);
let coefficient = match coefficient {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::option::Option::None,
};
sum = match sum.checked_add(coefficient) {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::option::Option::None,
};
}
let count = match u128::try_from(prices.len()) {
std::result::Result::Ok(value) if value > 0 => value,
_ => return std::option::Option::None,
};
let target_scale = OUTPUT_SCALE;
let (numerator, denominator) = if max_scale <= target_scale {
let factor = 10_u128.checked_pow(u32::from(target_scale - max_scale));
let factor = match factor {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::option::Option::None,
};
let numerator = match sum.checked_mul(factor) {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::option::Option::None,
};
(numerator, count)
} else {
let factor = 10_u128.checked_pow(u32::from(max_scale - target_scale));
let factor = match factor {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::option::Option::None,
};
let denominator = match count.checked_mul(factor) {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::option::Option::None,
};
(sum, denominator)
};
let quotient = numerator / denominator;
let remainder = numerator % denominator;
let round_up = remainder >= denominator.div_ceil(2);
let coefficient = if round_up {
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(coefficient, target_scale));
}
fn format_scaled_decimal(mut coefficient: u128, mut scale: u8) -> String {
while scale > 0 && coefficient % 10 == 0 {
coefficient /= 10;
scale -= 1;
}
let digits = coefficient.to_string();
if scale == 0 {
return digits;
}
let scale = usize::from(scale);
if digits.len() > scale {
let split = digits.len() - scale;
return format!("{}.{}", &digits[..split], &digits[split..]);
}
return format!("0.{}{}", "0".repeat(scale - digits.len()), digits);
}
#[cfg(test)]
#[path = "../unit_tests/wallet_balance.rs"]
mod tests;