### Domains
| Description | Testnet URL | Mainnet URL |
| --------------------------------------- | --------------------------------------------------------------- | ----------------------------------------------------- |
| REST Endpoint to query specific reports | [https://live-api-test.apro.com](http://live-api-test.apro.com) | [https://live-api.apro.com](http://live-api.apro.com) |
### Program IDs
| Description | Devnet | Mainnet |
| ----------------- | -------------------------------------------- | -------------------------------------------- |
| Apro\_SVM\_Oracle | 4Mvy4RKRyJMf4PHavvGUuTj9agoddUZ9atQoFma1tyMY | 4Mvy4RKRyJMf4PHavvGUuTj9agoddUZ9atQoFma1tyMY |
| Sample\_Client | HUJ8ouH6fVonhF1hPV6ENoLid5nbHfyZSpvfujw6X6Hm | HUJ8ouH6fVonhF1hPV6ENoLid5nbHfyZSpvfujw6X6Hm |
### Authentication
#### Headers
All routes require the following two headers for user authentication:
| Header | Description |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Authorization | The user’s unique identifier, provided as a UUID (Universally Unique IDentifier). |
| X-Authorization-Timestamp | The current timestamp, with precision up to milliseconds. The timestamp must closely synchronize with the server time, allowing a maximum discrepancy of 5 seconds (by default). For a value of 10 digits, it is multiplied directly by 1000 to milliseconds. |
To get authorization, please contact our BD Team:
* Email: <bd@apro.com>
* Telegram:[ Head of Business Development](https://t.me/Annie_LLLEEE)
### API endpoints
1. #### Return a single report at a given timestamp
Endpoint
> /api/svmbnb/reports
| Type | Description | Parameter(s) |
| -------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| HTTP GET | Returns a single report for a given timestamp. | <ul><li><strong><code>feedID</code></strong>: A Data Streams feed ID.</li><li><strong><code>timestamp</code></strong>: The Unix timestamp for the report.</li></ul> |
Sample request
> GET /api/svmbnb/reports?feedID=\<feedID>\×tamp=\<timestamp>
Sample response
<figure><img src="https://227137242-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FUIpyIkxGAPXFuivFcvwB%2Fuploads%2F82jOz5vLcbC7GXfB9DZB%2Fimage.png?alt=media&token=554c2d2a-9abe-48ff-8578-ed0c5f6f3a4b" alt=""><figcaption></figcaption></figure>
2. #### Return a single report with the latest timestamp
Endpoint
> /api/svmbnb/reports/latest
| Type | Parameter(s) |
| -------- | ------------------------------------- |
| HTTP GET | `feedID`: A Data Streams feed ID. |
Sample request
> GET /api/svmbnb/reports/latest?feedID=\<feedID>
Sample response
<figure><img src="https://227137242-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FUIpyIkxGAPXFuivFcvwB%2Fuploads%2Fhw8SQ3na2ec6Vzuss0t8%2Fimage.png?alt=media&token=e9f70fd5-d6d2-4001-b2cd-ea955e4b9391" alt=""><figcaption></figcaption></figure>
3. #### Return a report for multiple FeedIDs at a given timestamp
Endpoint
> /api/svmbnb/reports/bulk
| Type | Description | Parameter(s) |
| -------- | ---------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| HTTP GET | Return a report for multiple FeedIDs at a given timestamp. | <ul><li><strong><code>feedIDs</code></strong>: A comma-separated list of Data Streams feed IDs.</li><li><strong><code>timestamp</code></strong>: The Unix timestamp for the first report or the string 'latest' for getting the latest report.</li></ul> |
Sample request
> GET /api/svmbnb/reports/bulk?feedIDs=\<FeedID1>,\<FeedID2>,...\×tamp=\<timestamp>
>
> GET /api/svmbnb/reports/bulk?feedIDs=\<FeedID1>,\<FeedID2>,...\×tamp=latest
Sample response
<figure><img src="https://227137242-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FUIpyIkxGAPXFuivFcvwB%2Fuploads%2F7G4o7z1f7SBUHLuqdQuR%2Fimage.png?alt=media&token=bfe0daf8-842d-4b39-b7ad-8e2eee583b25" alt=""><figcaption></figcaption></figure>
<figure><img src="https://227137242-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FUIpyIkxGAPXFuivFcvwB%2Fuploads%2FVGqh1dZ0Brvy32hfvAez%2Fimage.png?alt=media&token=d02c1afd-03ff-4e15-802f-3408b13413dd" alt=""><figcaption></figcaption></figure>
4. #### Return multiple sequential reports for a single FeedID, starting at a given timestamp
Endpoint
> /api/svmbnb/reports/page
| Type | Description | Parameter(s) |
| -------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| HTTP GET | Return multiple sequential reports for a single FeedID, starting at a given timestamp. | <ul><li><strong><code>feedID</code></strong>: A Data Streams feed ID.</li><li><strong><code>startTimestamp</code></strong>: The Unix timestamp for the first report.</li><li><strong><code>limit</code></strong> (optional): The number of reports to return.</li></ul> |
Sample request
> GET /api/svmbnb/reports/page?feedID=\<FeedID>\&startTimestamp=\<StartTimestamp>\&limit=\<Limit>
Sample response
<figure><img src="https://227137242-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FUIpyIkxGAPXFuivFcvwB%2Fuploads%2Fa8rLwxM03aTkgPa4ZZLN%2Fimage.png?alt=media&token=6ae690ba-0cbc-4f04-a77f-39820b9fdc2e" alt=""><figcaption></figcaption></figure>
5. #### WebSocket Connection
Establish a streaming WebSocket connection that sends reports for the given feedID(s) after they are verified.
Endpoint
> /api/svmbnb/ws
| Type | Parameter(s) |
| --------- | --------------------------------------------------------------- |
| WebSocket | `feedIDs`: A comma-separated list of Data Streams feed IDs. |
Sample request
> GET /api/svmbnb/ws?feedIDs=\<feedID1>,\<feedID2>,...
Sample response
<figure><img src="https://227137242-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FUIpyIkxGAPXFuivFcvwB%2Fuploads%2FxCN973z1S4JSGn2jhcAg%2Fimage.png?alt=media&token=1ce860c4-d917-4a80-99f7-5b39a497f4f3" alt=""><figcaption></figcaption></figure>
### Price Feed ID
Devnet:
<table><thead><tr><th width="188">Feed</th><th>Information</th></tr></thead><tbody><tr><td><strong>ETH/USD</strong></td><td><ul><li>I<strong>D:</strong> 0x0003555ace6b39aae1b894097d0a9fc17f504c62fea598fa206cc6f5088e6e45</li><li><strong>Decimals:</strong> 18</li><li><strong>oracle_state_ID:</strong> 2</li></ul></td></tr><tr><td><strong>SOL/USD</strong></td><td><ul><li><strong>ID:</strong> 0x000343ec7f6691d6bf679978bab5c093fa45ee74c0baac6cc75649dc59cc21d3</li><li><strong>Decimals:</strong> 18</li><li><strong>oracle_state_ID:</strong> 4</li></ul></td></tr><tr><td><strong>USDC/USD</strong></td><td><ul><li><strong>ID:</strong> 0x00034b881a0c0fff844177f881a313ff894bfc6093d33b5514e34d7faa41b7ef</li><li><strong>Decimals:</strong> 18</li><li><strong>oracle_state_ID:</strong> 6</li></ul></td></tr><tr><td><strong>BNB/USD</strong></td><td><ul><li><strong>ID:</strong> 0x00039630e8b9343f7fccdecf67bf1a59fe8d2b58e4e8d3246a74f5cd6d6fed5d</li><li><strong>Decimals:</strong> 18</li><li><strong>oracle_state_ID:</strong> 8</li></ul></td></tr></tbody></table>
Mainnet:
<table><thead><tr><th width="188">Feed</th><th>Information</th></tr></thead><tbody><tr><td><strong>ETH/USD</strong></td><td><ul><li>I<strong>D:</strong> 0x0003555ace6b39aae1b894097d0a9fc17f504c62fea598fa206cc6f5088e6e45</li><li><strong>Decimals:</strong> 18</li><li><strong>oracle_state_ID:</strong> 2</li></ul></td></tr><tr><td><strong>USDC/USD</strong></td><td><ul><li><strong>ID:</strong> 0x00034b881a0c0fff844177f881a313ff894bfc6093d33b5514e34d7faa41b7ef</li><li><strong>Decimals:</strong> 18</li><li><strong>oracle_state_ID:</strong> 5</li></ul></td></tr><tr><td><strong>BNB/USD</strong></td><td><ul><li><strong>ID:</strong> 0x00039630e8b9343f7fccdecf67bf1a59fe8d2b58e4e8d3246a74f5cd6d6fed5d</li><li><strong>Decimals:</strong> 18</li><li><strong>oracle_state_ID:</strong> 6</li></ul></td></tr><tr><td><strong>SOL/USD</strong></td><td><ul><li><strong>ID:</strong> 0x000343ec7f6691d6bf679978bab5c093fa45ee74c0baac6cc75649dc59cc21d3</li><li><strong>Decimals:</strong> 18</li><li><strong>oracle_state_ID:</strong> 7</li></ul></td></tr></tbody></table>
### Error response codes
| Status Code | Description |
| -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400 Bad Request | <p>This error is triggered when:</p><ul><li>There is any missing/malformed query argument.</li><li>Required headers are missing or provided with incorrect values.</li></ul> |
| 401 Unauthorized User | <p>This error is triggered when:</p><ul><li>Authentication fails, typically because the HMAC signature provided by the client doesn't match the one expected by the server.</li><li>A user requests access to a feed without the appropriate permission or that does not exist.</li></ul> |
| 500 Internal Server | Indicates an unexpected condition encountered by the server, preventing it from fulfilling the request. This error typically points to issues on the server side. |
| 206 Missing data (**`/bulk`** endpoint only) | Indicates that at least one feed ID data is missing from the report. E.g., you requested a report for feed IDs `<feedID1>`, `<feedID2>`, and `<feedID3>` at a given timestamp. If data for `<feedID2>` is missing from the report (not available yet at the specified timestamp), you get `[<feedID1 data>, <feedID3 data>]` and a 206 response. |
### Sample Integration Program
#### oracle\_sdk:
```rust
use anchor_lang::prelude::*;
use anchor_lang::solana_program::hash::hash;
use anchor_lang::solana_program::instruction::Instruction;
use anchor_lang::solana_program::program::invoke;
pub const APRO_SVM_PROGRAM_ID: &str = "4Mvy4RKRyJMf4PHavvGUuTj9agoddUZ9atQoFma1tyMY";
pub fn load_price_feed_from_account_info(price_account_info: &AccountInfo) -> Result<PriceFeed> {
let data = price_account_info.try_borrow_data()?;
let mut price_feed_data = &data[8..];
let price_feed = PriceFeed::deserialize(&mut price_feed_data)?;
Ok(price_feed)
}
pub fn update_price<'info>(
oracle_state: &AccountInfo<'info>,
price_feed: &AccountInfo<'info>,
payer: &AccountInfo<'info>,
admin: &AccountInfo<'info>,
system_program: &AccountInfo<'info>,
oracle_program: &AccountInfo<'info>,
feed_id: [u8; 32],
valid_time_stamp: u128,
observe_time_stamp: u128,
native_fee: u128,
apro_token_fee: u128,
expire_at: u128,
benchmark_price: u128,
ask_price: u128,
bid_price: u128,
config_digest: [u8; 32],
epoch_and_round: u128,
extra_hash: [u8; 32],
signatures: Vec<[u8; 64]>,
recovery_ids: Vec<u8>,
) -> Result<()> {
let ix = Instruction {
program_id: *oracle_program.key,
accounts: vec![
AccountMeta::new_readonly(*oracle_state.key, false),
AccountMeta::new(*price_feed.key, false),
AccountMeta::new(*payer.key, true),
AccountMeta::new(*admin.key, false),
AccountMeta::new_readonly(*system_program.key, false),
],
data: UpdatePriceArgs {
feed_id,
valid_time_stamp,
observe_time_stamp,
native_fee,
apro_token_fee,
expire_at,
benchmark_price,
ask_price,
bid_price,
config_digest,
epoch_and_round,
extra_hash,
signatures,
recovery_ids,
}
.data(),
};
invoke(
&ix,
&[
oracle_state.clone(),
price_feed.clone(),
payer.clone(),
admin.clone(),
system_program.clone(),
oracle_program.clone(),
],
)?;
Ok(())
}
#[derive(AnchorDeserialize, AnchorSerialize, Clone, Debug)]
pub struct PriceFeed {
pub feed_id: [u8; 32],
pub valid_time_stamp: u128,
pub observe_time_stamp: u128,
pub native_fee: u128,
pub apro_token_fee: u128,
pub expire_at: u128,
pub benchmark_price: u128,
pub ask_price: u128,
pub bid_price: u128,
pub config_digest: [u8; 32],
pub epoch_and_round: u128,
pub extra_hash: [u8; 32],
}
#[derive(AnchorSerialize, AnchorDeserialize)]
struct UpdatePriceArgs {
feed_id: [u8; 32],
valid_time_stamp: u128,
observe_time_stamp: u128,
native_fee: u128,
apro_token_fee: u128,
expire_at: u128,
benchmark_price: u128,
ask_price: u128,
bid_price: u128,
config_digest: [u8; 32],
epoch_and_round: u128,
extra_hash: [u8; 32],
signatures: Vec<[u8; 64]>,
recovery_ids: Vec<u8>,
}
impl UpdatePriceArgs {
fn data(&self) -> Vec<u8> {
let mut data = Vec::new();
let preimage = "global:update_price".as_bytes();
let hash = hash(preimage);
let discriminator = &hash.to_bytes()[..8];
data.extend_from_slice(discriminator);
data.extend_from_slice(&AnchorSerialize::try_to_vec(self).unwrap());
data
}
}
```
#### Clients code:
```rust
use anchor_lang::prelude::*;
use oracle_sdk::{load_price_feed_from_account_info, update_price};
declare_id!("GzWu85MdbZtVBDcC4Xrmp1dWix7Qo52nUAkD2FjraJ6f");//modify by solana address -k
#[program]
pub mod oracle_client_example {
use super::*;
pub fn fetch_price(ctx: Context<FetchPrice>) -> Result<()> {
//fetch price feed from oracle
let price_feed = load_price_feed_from_account_info(&ctx.accounts.price_account)?;
msg!("Price Feed Details:");
msg!("Feed ID: {:?}", price_feed.feed_id);
msg!("Valid Timestamp: {}", price_feed.valid_time_stamp);
msg!("Observe Timestamp: {}", price_feed.observe_time_stamp);
msg!("Benchmark Price: {}", price_feed.benchmark_price);
msg!("Ask Price: {}", price_feed.ask_price);
msg!("Bid Price: {}", price_feed.bid_price);
let current_timestamp = Clock::get()?.unix_timestamp as u128;
let staleness_threshold = 3600u128;
//store the price in the price_result account
let price_result = if current_timestamp - price_feed.valid_time_stamp <= staleness_threshold
{
PriceResult {
price: price_feed.benchmark_price,
is_valid: true,
}
} else {
PriceResult {
price: 0,
is_valid: false,
}
};
ctx.accounts.price_result.set_inner(price_result);
Ok(())
}
pub fn update_oracle_price(
ctx: Context<UpdateOraclePrice>,
feed_id: [u8; 32],
valid_time_stamp: u128,
observe_time_stamp: u128,
native_fee: u128,
apro_token_fee: u128,
expire_at: u128,
benchmark_price: u128,
ask_price: u128,
bid_price: u128,
config_digest: [u8; 32],
epoch_and_round: u128,
extra_hash: [u8; 32],
signatures: Vec<[u8; 64]>,
recovery_ids: Vec<u8>,
) -> Result<()> {
//update the price feed through CPI
update_price(
&ctx.accounts.oracle_state.to_account_info(),
&ctx.accounts.price_feed.to_account_info(),
&ctx.accounts.payer.to_account_info(),
&ctx.accounts.admin.to_account_info(),
&ctx.accounts.system_program.to_account_info(),
&ctx.accounts.oracle_program.to_account_info(),
feed_id,
valid_time_stamp,
observe_time_stamp,
native_fee,
apro_token_fee,
expire_at,
benchmark_price,
ask_price,
bid_price,
config_digest,
epoch_and_round,
extra_hash,
signatures,
recovery_ids,
)?;
Ok(())
}
}
#[derive(Accounts)]
pub struct FetchPrice<'info> {
#[account(mut)]
pub payer: Signer<'info>,
/// CHECK: This account is owned by the Oracle program
pub price_account: UncheckedAccount<'info>,
#[account(
init_if_needed,
payer = payer,
space = 8 + 16 + 1, // discriminator + price (u128) + is_valid
seeds = [b"price_result", price_account.key().as_ref()],
bump
)]
pub price_result: Account<'info, PriceResult>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct UpdateOraclePrice<'info> {
/// CHECK: This account is verified in the update_price function
pub oracle_state: UncheckedAccount<'info>,
/// CHECK: This account is verified in the update_price function
#[account(mut)]
pub price_feed: UncheckedAccount<'info>,
#[account(mut)]
pub payer: Signer<'info>,
/// CHECK: This account is verified in the update_price function
#[account(mut)]
pub admin: UncheckedAccount<'info>,
pub system_program: Program<'info, System>,
/// CHECK: This account is verified in the update_price function
pub oracle_program: UncheckedAccount<'info>,
}
#[account]
pub struct PriceResult {
pub price: u128,
pub is_valid: bool,
}
#[error_code]
pub enum ErrorCode {
#[msg("Invalid account data length")]
InvalidAccountDataLength,
}
```
<br>
