switchboard-on-demand 0.5.6

A Rust library to interact with the Switchboard Solana program.
Documentation
use rust_decimal::prelude::*;
use faster_hex::hex_string;
use crate::prelude::*;

// Bytes: 32 (only signed data)
#[derive(Copy, Clone, Debug)]
#[repr(packed)]
pub struct PackedBundleHeader {
    pub signed_slothash: [u8; 32], // Slothash signed by the oracle
}

// Bytes: 49
#[derive(Copy, Clone, Debug)]
#[repr(packed)]
pub struct PackedFeedInfo {
    feed_id: [u8; 32],      // Feed ID (32 bytes) - JavaScript puts this first
    feed_value: i128,       // Feed value (i128) - JavaScript puts this second
    min_oracle_samples: u8, // Minimum number of oracle samples required
}

impl PackedFeedInfo {
    pub const PACKED_SIZE: usize = 49;

    #[inline(always)]
    pub fn feed_id(&self) -> &[u8; 32] {
        
        (&self.feed_id) as _
    }

    #[inline(always)]
    pub fn hex_id(&self) -> String {
        String::from("0x") + &hex_string(self.feed_id())
    }

    #[inline(always)]
    pub fn feed_value(&self) -> i128 {
        
        self.feed_value
    }

    /// Returns the feed value as a `Decimal`, scaled using the program-wide `PRECISION`.
    ///
    /// This converts the raw fixed-point integer into a human-readable decimal form.
    #[inline(always)]
    pub fn value(&self) -> Decimal {
        Decimal::from_i128_with_scale(self.feed_value(), PRECISION).normalize()
    }

    /// Returns the minimum number of oracle samples required for this feed
    #[inline(always)]
    pub fn min_oracle_samples(&self) -> u8 {
        self.min_oracle_samples
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use rust_decimal::Decimal;

    #[test]
    fn test_packed_feed_info_layout() {
        // Simulate JavaScript serialized data: feed_id (32) + feed_value (16) + min_oracle_samples (1)
        let mut data = [0u8; 49];

        // Feed ID (32 bytes) - some test hash
        data[0..32].fill(0x42);

        // Feed value at offset 32: 115525650000000000000000 (i128, scaled by 18 decimals)
        // This should decode to approximately 115525.65
        let feed_value_scaled: i128 = 115525650000000000000000i128;
        let value_bytes = feed_value_scaled.to_le_bytes();
        data[32..48].copy_from_slice(&value_bytes);

        // Min oracle samples (1 byte) at offset 48
        data[48] = 5;

        // Cast to PackedFeedInfo
        let feed_info = unsafe { &*(data.as_ptr() as *const PackedFeedInfo) };

        // Test reading the feed value
        let raw_value = feed_info.feed_value();
        println!("Raw feed value: {}", raw_value);
        println!("Expected: {}", feed_value_scaled);
        assert_eq!(raw_value, feed_value_scaled);

        // Test the decimal conversion
        let decimal_value = feed_info.value();
        println!("Decimal value: {}", decimal_value);

        // Test other fields
        assert_eq!(feed_info.min_oracle_samples(), 5);
        assert_eq!(feed_info.feed_id()[0], 0x42);

        println!("✅ Test passed! Feed value reads correctly: {}", decimal_value);
    }
}