Skip to main content

Chaos

Overview

Edge by Chaos Labs represents a breakthrough in delivering secure, accurate, and real-time market data with integrated risk management for decentralized finance protocols.

The Chaos Edge oracle does not have a program ID. You are required to verify the signed price updates yourself using their static signing pubkey.

Please reach out to the Chaos team for an API key. See their official documentation to learn more.

Available price feeds

The full, up-to-date list of price feeds can be found on the Chaos Labs website here.

Integrating the Chaos Edge oracle

The following Solana program shows how to verify a signed price message from the Chaos Edge oracle.

Chaos Edge Example Verification Program
use borsh::{BorshDeserialize, BorshSerialize};
use solana_program::{
account_info::AccountInfo, declare_id, entrypoint::ProgramResult, keccak, msg,
program_error::ProgramError, pubkey::Pubkey, secp256k1_recover,
};
use std::ops::Rem;

declare_id!("DkZTx3SmzkD9xjTH6epmw26djPtTT9nKaZifco3bbsyb");

#[cfg(not(feature = "no-entrypoint"))]
solana_program::entrypoint!(process_instruction);

// staging signer, contact Chaos for the signing key
pub static CHAOS_SIGNER: [u8; 33] = todo!();

pub fn process_instruction(
_program_id: &Pubkey,
_accounts: &[AccountInfo],
instruction_data: &[u8],
) -> ProgramResult {
let signed_price_message = SignedPriceMessage::try_from_slice(instruction_data)
.map_err(|_| ProgramError::InvalidInstructionData)?;

// this example only supports SOLUSD
let pair_id = "SOLUSD".as_bytes();
let mut pair = [0u8; 32];
pair[0..pair_id.len()].copy_from_slice(pair_id);

let price_feed = PriceFeed {
pair,
signer: CHAOS_SIGNER,
};

signed_price_message
.verify_signature(&price_feed)
.map_err(|_| ProgramError::InvalidInstructionData)?;

// @dev - we should verify the timestamp here but it would break our tests
msg!("SOLUSD price: {}", signed_price_message.price);

Ok(())
}

/// Represents a signed price message from the Edge Pull Oracle
/// Each message contains:
/// - A price value with its exponent (e.g., 19476500000 * 10^-8 = $194.765)
/// - A timestamp in seconds since Unix epoch
/// - A secp256k1 signature (64 bytes) and recovery ID (1 byte)
#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)]
pub struct SignedPriceMessage {
/// Recovery ID (0-3) used in secp256k1 signature recovery
pub recovery_id: u8,
/// 64-byte secp256k1 signature of the encoded price message
pub signature: [u8; 64],
/// Integer price value (must be combined with expo for actual price)
pub price: u64,
/// Price exponent (e.g., -8 means price is divided by 10^8)
pub expo: i8,
/// Unix timestamp in seconds when the price was signed
pub timestamp: i64,
}

/// Individual price data within a batch
#[derive(Debug)]
pub struct PriceFeed {
pub pair: [u8; 32],
pub signer: [u8; 33],
}

/// Individual price data within a batch
#[derive(Debug)]
pub struct PriceData {
/// The price value
pub price: u64,
/// The price exponent
pub expo: i8,
/// Timestamp for this price
pub timestamp: i64,
/// The feed this price belongs to
pub feed: PriceFeed,
}

impl SignedPriceMessage {
/// Encodes the price message into the standard 49-byte format for verification
/// Format:
/// - Bytes 0-31: Feed pair ID (e.g., "BTCUSD" padded with zeros)
/// - Bytes 32-39: Price as little-endian u64
/// - Bytes 40: Exponent as little-endian i8
/// - Bytes 41-48: Timestamp as little-endian i64
pub fn encode_price_message(&self, feed: &PriceFeed) -> [u8; 49] {
let mut m = [0u8; 49];
// First 32 bytes: Feed pair identifier
m[..32].clone_from_slice(&feed.pair);
// Next 8 bytes: Price in little-endian
m[32..40].clone_from_slice(&self.price.to_le_bytes());
// Next 1 byte: Exponent in little-endian
m[40..41].clone_from_slice(&self.expo.to_le_bytes());
// Last 8 bytes: Timestamp in little-endian
m[41..49].clone_from_slice(&self.timestamp.to_le_bytes());
m
}

/// Creates the Keccak256 hash of the encoded price message
/// This hash is what gets signed by the oracle
pub fn create_verification_hash(&self, feed: &PriceFeed) -> [u8; 32] {
keccak::hash(&self.encode_price_message(feed)).to_bytes()
}

/// Verifies the signature using Solana's secp256k1 recovery
/// Steps:
/// 1. Creates message hash from encoded price data
/// 2. Recovers the public key from the signature
/// 3. Verifies the recovered key matches the authorized signer
/// 4. Checks both X-coordinate and parity (even/odd) of the key
pub fn verify_signature(&self, feed: &PriceFeed) -> Result<(), ProgramError> {
let signer: &[u8; 33] = &feed.signer;
// Check if the expected public key is even or odd
let is_even = signer[0].rem(2) == 0;

// Recover the public key from signature
let Ok(pubkey) = secp256k1_recover::secp256k1_recover(
&self.create_verification_hash(feed),
self.recovery_id,
&self.signature,
) else {
return Err(ProgramError::MissingRequiredSignature);
};

// Verify the X-coordinate matches
if !pubkey.0[..32].eq(&signer[1..]) {
return Err(ProgramError::MissingRequiredSignature);
}

// Verify the parity (even/odd) matches
if (pubkey.0[63].rem(2) == 0) != is_even {
return Err(ProgramError::MissingRequiredSignature);
}

Ok(())
}
}

The following example shows how to build a transaction to verify a signed price. You will need to have access to a Chaos API key to source these signed price updates.

Chaos Edge Example Transaction Building
#[derive(BorshSerialize, BorshDeserialize, Clone, Debug)]
pub struct ChaosPriceFeed {
feed_id: String,
/// Integer price value (must be combined with expo for actual price)
price: u64,
ts: u64,
/// Price exponent (e.g., -8 means price is divided by 10^8)
expo: i8,
/// 64-byte secp256k1 signature of the encoded price message
signature: [u8; 64],
/// Recovery ID (0-3) used in secp256k1 signature recovery
recovery_id: u32,
}

fn build_chaos_transaction(payer: &Keypair, blockhash: Hash) -> Transaction {
// sourced from https://oracle-staging.chaoslabs.co/prices/SOLUSD/latest
let signed_price_message = SignedPriceMessage {
recovery_id: 1,
signature: hex::decode("8726789cc00b6c293efa36c1a0c293ab12bf59c9d83774124c61830749e3045d5c2cf163cb9c20b20f1748cf19640ac9bad4877bff6dc955e146f5d7a7791a52").unwrap()[0..64]
.try_into()
.unwrap(),
price: 12705500000,
expo: -8,
timestamp: 1742569181,
};

let mut tx = Transaction::new_with_payer(
&[Instruction {
accounts: [AccountMeta {
pubkey: payer.pubkey(),
is_writable: true,
is_signer: false,
}]
.to_vec(),
program_id: chaos_example::ID,
data: borsh::to_vec(&signed_price_message).unwrap(),
}],
Some(&payer.pubkey()),
);
tx.sign(&[&payer], blockhash);
}