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 (UDF) protocol on the Solana blockchain. The price feeds are regularly pushed to and stored within the Solana Price Oracle program.
Below is a sample Solana consumer program written in Rust that demonstrates how to retrieve prices for a specific asset from the Price Oracle program. This example can serve as a template for external developers to build 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.
use anchor_lang::prelude::*;declare_id!("3r5ixGQu8DRmJWgFEjwnDUQ6yasfYFXDsUbqkA6gkRtv");#[program]pubmod price_consumer {use super::*;use anchor_lang::solana_program::{ instruction::Instruction, program::{get_return_data, invoke}, };pubfnconsume_price(ctx:Context<ConsumePrice>, asset:String) ->Result<()> {// Convert the asset to a byte vectorlet 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 instructionlet data = [&sighash("global", "last_price")[..], &payload[..]].concat();// Create the instruction to invoke the price oraclelet ix =Instruction { program_id: ctx.accounts.price_oracle.key(), accounts: ctx.accounts.latest_update.to_account_metas(Some(false)), data, };// Invoke the price oracle programinvoke(&ix, &[ctx.accounts.latest_update.to_account_info()])?;// Get the returned data from the price oraclelet (_, data) =get_return_data().expect("Data expected to be retrieved from the price oracle");// Deserialize the price and timestamp from the returned datalet (price, timestamp) = <([u8; 32], u64)>::try_from_slice(&data).expect("Expected price and timestamp to be deserialized with Borsh");// Extract the pricelet (_, price_right) = price.split_at(16);let price =u128::try_from_slice(price_right).unwrap().to_be();// Log the resultmsg!("Price of: {} is: {} at: {}", asset, price, timestamp);Ok(()) }}// Function to generate a sighashfnsighash(namespace:&str, name:&str) -> [u8; 8] {let preimage =format!("{}:{}", namespace, name);letmut 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 IDpubconst 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)]pubstructConsumePrice<'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>,}
TypeScript Snippet: Invoking the Consumer Program from the Backend
The following TypeScript snippet demonstrates how to invoke the given consumer program from the backend. This example follows a common approach for constructing transactions.
Key Details
Entangle Price Oracle Deployment:
The address 7HramSnctpbXqZ4SEzqvqteZdMdj3tEB2c9NT7egPQi7 is where the Entangle Price Oracle is deployed, and it is accessible on both the Solana Devnet and Mainnet.
Asset to Retrieve:
The "NGL/USD" asset is intended to be retrieved on-chain using the Entangle Price Oracle.
latestUpdatePda Calculation:
The latestUpdatePda is calculated according to predefined rules, which include using certain seeds and the asset name.
import*as anchor from"@coral-xyz/anchor";import { Program, web3 } from"@coral-xyz/anchor";import { PriceConsumer } from"../target/types/price_consumer";// Initialize the PriceConsumer programconstconsumer_program=anchor.workspace.PriceConsumer asProgram<PriceConsumer>;// Constants for UDF protocolconstUDF_ROOT=utf8.encode("UDF0");constUDF_PROTOCOL_ID=Buffer.from(utf8.encode("universal-data-feeds\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" ));// Encode asset name as a UTF-8 byte arraylet utf8Encode =newTextEncoder();constdataKey=newUint8Array(32);dataKey.set(utf8Encode.encode("NGL/USD"));// Define the UDF program IDconstudf_program_id=newweb3.PublicKey("7HramSnctpbXqZ4SEzqvqteZdMdj3tEB2c9NT7egPQi7");// Find the PDA (Program Derived Address) for the latest updatelet latestUpdatePda =web3.PublicKey.findProgramAddressSync( [UDF_ROOT,utf8.encode("LAST_UPDATE"),UDF_PROTOCOL_ID, dataKey], udf_program_id)[0];// Send a transaction to consume the price from the oracleconstgetLastPriceTx=awaitconsumer_program.methods.consumePrice(dataKey).accounts({ signer:owner.publicKey, priceOracle:udf_program.programId, latestUpdate: latestUpdatePda, }).signers([owner]).rpc();// Log the transaction signatureconsole.log("Consume tx signature", getLastPriceTx);// Some code
The code above requires the following dependencies:
Example of Logs Generated During the Call
The following logs are generated when the transaction is executed. The call invokes two Solana programs in turn:
Consumer Program:
Program ID: 3r5ixGQu8DRmJWgFEjwnDUQ6yasfYFXDsUbqkA6gkRtv
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 multiplied 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.