Fetch Data via Push Model

This guide is designed for external developers who need to retrieve and use prices provided through the Push Model of the Entangle Universal Data Feeds protocol on the Solana blockchain.

The price feeds are regularly pushed to and stored within the Solana Price Oracle on-chain program.

Step 1. Making a sample consumer program

First, a Solana program implementing the core business logic is required. The next step is to integrate the price-consuming functionality, as demonstrated in the example below. This Rust program shows how to retrieve asset prices from the Price Oracle program. The provided example can serve as a template for external developers to create their own price-consumer programs.

Key Function: consume_price

The consume_price method in the example program retrieves the price for a given asset by constructing an instruction and invoking the on-chain Entangle Price Oracle.

  • The Price Oracle address is provided as part of the accounts set.

  • The target asset's address is defined as a Program Derived Address (PDA), derived using predefined attributes and the asset name according to Price Oracle rules.

  • The returned data consists of two parts: price and timestamp.

    • The price is a 32-byte value, but only the rightmost 16 bytes are used.

    • The timestamp is a Unix timestamp.

Program Setup

To begin, you’ll need to create a consumer program using the Solana Rust SDK. The program will interact with the Entangle Price Oracle and retrieve asset prices. The following sample is also available on the GitHub.

use anchor_lang::prelude::*;
declare_id!("3r5ixGQu8DRmJWgFEjwnDUQ6yasfYFXDsUbqkA6gkRtv");

#[program]
pub mod price_consumer {
    use super::*;
    use anchor_lang::solana_program::{
        instruction::Instruction,
        program::{get_return_data, invoke},
    };

    pub fn consume_price(ctx: Context<ConsumePrice>, asset: String) -> Result<()> {
        // Convert the asset to a byte vector
        let payload = asset
            .try_to_vec()
            .expect("Asset is expected to be converted to the vec of bytes");

        // Prepare the data to be passed in the instruction
        let data = [&sighash("global", "last_price")[..], &payload[..]].concat();

        // Create the instruction to invoke the price oracle
        let ix = Instruction {
            program_id: ctx.accounts.price_oracle.key(),
            accounts: ctx.accounts.latest_update.to_account_metas(Some(false)),
            data,
        };

        // Invoke the price oracle program
        invoke(&ix, &[ctx.accounts.latest_update.to_account_info()])?;

        // Get the returned data from the price oracle
        let (_, data) = get_return_data().expect("Data expected to be retrieved from the price oracle");

        // Deserialize the price and timestamp from the returned data
        let (price, timestamp) = <([u8; 32], u64)>::try_from_slice(&data)
            .expect("Expected price and timestamp to be deserialized with Borsh");

        // Extract the price
        let (_, price_right) = price.split_at(16);
        let price = u128::try_from_slice(price_right).unwrap().to_be();

        // Log the result
        msg!("Price of: {} is: {} at: {}", asset, price, timestamp);

        Ok(())
    }
}

// Function to generate a sighash
fn sighash(namespace: &str, name: &str) -> [u8; 8] {
    let preimage = format!("{}:{}", namespace, name);
    let mut sighash = [0u8; 8];
    sighash.copy_from_slice(
        &anchor_lang::solana_program::hash::hash(preimage.as_bytes()).to_bytes()[..8],
    );
    sighash
}

// Constants for seeds and protocol ID
pub const ROOT: &[u8] = b"UDF0";

#[cfg(not(feature = "mainnet"))]
const UDF_PROTOCOL_ID: &[u8] = b"universal-datafeeds3\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";

#[cfg(feature = "mainnet")]
const UDF_PROTOCOL_ID: &[u8] = b"universal-datafeeds\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";

// Struct to define the accounts used in the consume_price function
#[derive(Accounts)]
#[instruction(asset: String)]
pub struct ConsumePrice<'info> {
    #[account(signer, mut)]
    payer: Signer<'info>,

    /// CHECK: This is an external price oracle, and no specific owner is expected. 
    /// The account is marked as executable to ensure it's a program.
    #[account(executable)]
    price_oracle: UncheckedAccount<'info>,

    /// CHECK: This account is derived using specific seeds, including the asset. 
    /// Ensure the seeds match the provided asset to trust the account.
    #[account(
        seeds = [ROOT, b"LAST_UPDATE", UDF_PROTOCOL_ID, asset.as_bytes()],
        bump, 
        seeds::program = price_oracle
    )]
    latest_update: UncheckedAccount<'info>,
}

Step 2. Invoking the program on the client side

The sample script is available on GitHub that demonstrates how to invoke the Consumer Program Sample on Solana. It provides an example of how to interact with the Consumer Program and retrieve asset prices or other relevant data. It can serve as a useful starting point for developers working with price oracles on the Solana blockchain.

There are two articles could be used to check the PUSH model works well on the Solana:

  1. Solana. Setting up the Entangle UDF local testing environment The local testing environment should be initialized by following the instructions provided in the guide of the Price Oracle Solana repository. This ensures that all necessary components are set up correctly, including deploying programs, configuring accounts, and running tests, to create a fully functional local environment for further development and testing of the price oracle and associated programs.

  2. Solana. Sample client for fetching data via the PUSH MODEL This sample client includes a script that interacts with the udf_solana program to retrieve the latest price of the certain data key like "NGL/USD."

Step 3. Ensuring price consuming works well

By following the instructions provided above, the process results in a finalized transaction on the Solana network. In this specific case, the transaction contains logs that indicate the price retrieved through cross-program invocation. These logs provide evidence of the successful interaction between the programs, showcasing the retrieved price data.

The following logs are generated when the transaction is executed. The call invokes two Solana programs in turn:

> Program logged: "Instruction: ConsumePrice"
> Program invoked: Unknown Program (7HramSnctpbXqZ4SEzqvqteZdMdj3tEB2c9NT7egPQi7)  
> Program logged: "Instruction: LastPrice"  
> Program consumed: 5014 of 192696 compute units  
> Program return: 7HramSnctpbXqZ4SEzqvqteZdMdj3tEB2c9NT7egPQi7 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc19zO3642eEkLpmAAAAAA==  
> Program returned success
> Program logged: "Price of: NGL/USD is: 129898283383055207 at: 1723502724"
> Program consumed: 14134 of 200000 compute units
> Program return: 3r5ixGQu8DRmJWgFEjwnDUQ6yasfYFXDsUbqkA6gkRtv AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc19zO3642eEkLpmAAAAAA==
> Program returned success
  1. Consumer Program:

    • Program ID: 3r5ixGQu8DRmJWgFEjwnDUQ6yasfYFXDsUbqkA6gkRtv

  2. Entangle Price Oracle:

    • Program ID: 7HramSnctpbXqZ4SEzqvqteZdMdj3tEB2c9NT7egPQi7

Price Data for NGL/USD

  • The resulting price for the NGL/USD pair is interpreted as a u128 big endian value: 129898283383055207

  • This price should be divided by 10^18 to get the final value.

Interpretation of the Result:

  • Final Price: 129898283383055207 multiplied by 10^18 is interpreted as 0.12989.

  • Timestamp: The price was issued on August 12, 2024, at 22:45:24.


With this guide, you can now integrate Solana's Entangle Universal Data Feeds price data into your decentralized applications. This system efficiently retrieves price and timestamp information, ensuring that your program can access accurate on-chain data with minimal latency.

Last updated