SkillAgentSearch skills...

Doppler

The fastest oracle on Solana

Install / Use

/learn @blueshift-gg/Doppler
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

<h3 align="center"> A 21 CU Solana Oracle Program </h3>

Overview

Doppler is an ultra-optimized oracle program for Solana, achieving unparalleled performance at just 21 Compute Units (CUs) per update. Built with low-level optimizations and minimal overhead, Doppler sets the standard for high-frequency, low-latency price feeds on Solana.

Features

  • 21 CU Oracle Updates: The most efficient oracle implementation on Solana
  • Generic Payload Support: Flexible data structure supporting any payload type
  • Sequence-Based Updates: Built-in replay protection and ordering guarantees
  • Zero Dependencies: Pure no_std Rust implementation for minimal overhead
  • Direct Memory Operations: Optimized assembly-level exits for maximum efficiency

Installation

Add Doppler SDK and required Solana crates to your Cargo.toml:

[dependencies]
doppler-sdk = "0.1.0"
solana-instruction = "2.3.0"
solana-pubkey = "2.3.0"
solana-compute-budget-interface = "2.2.2"
solana-transaction = "2.3.0"
solana-keypair = "2.3.0"
solana-signer = "2.2.1"
# Add other Solana crates as needed

Program ID

fastRQJt3nLdY3QA7n8eZ8ETEVefy56ryfUGVkfZokm

Architecture

Doppler uses a simple yet powerful architecture:

  1. Admin Account: Controls oracle updates (hardcoded for security)
  2. Oracle Account: Stores the sequence number and payload data
  3. Sequence Validation: Ensures updates are monotonically increasing

Data Structure

pub struct Oracle<T> {
    pub sequence: u64,  // Timestamp, slot height, or auto-increment
    pub payload: T,     // Your custom data structure
}

Usage Guide

1. Setting Up Compute Budget

To achieve the 21 CU performance, configure your transaction with appropriate compute budget:

use solana_compute_budget_interface::ComputeBudgetInstruction;
use solana_instruction::Instruction;
use solana_transaction::Transaction;

// Request exactly the CUs needed (21 + overhead for other instructions)
let compute_budget_ix = ComputeBudgetInstruction::set_compute_unit_limit(200_000);

// Add to your transaction
let mut instructions = vec![compute_budget_ix];

2. Setting Priority Fees

For high-frequency oracle updates, use priority fees to ensure timely inclusion:

// Set priority fee (price per compute unit in micro-lamports)
let priority_fee_ix = ComputeBudgetInstruction::set_compute_unit_price(1000);

instructions.push(priority_fee_ix);

3. Optimizing Account Data Size

Use setLoadedAccountsDataSizeLimit to optimize memory allocation:

// Set the maximum loaded account data size
// Calculate based on your oracle data structure size
let data_size_limit_ix = ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(
    32_768  // 32KB is usually sufficient for oracle operations
);

instructions.push(data_size_limit_ix);

4. Creating an Oracle Update

use doppler_sdk::{Oracle, UpdateInstruction, ID as DOPPLER_ID};
use solana_instruction::Instruction;
use solana_pubkey::Pubkey;

// Define your payload structure
#[derive(Clone, Copy)]
pub struct PriceFeed {
    pub price: u64,
}

// Create oracle update
let oracle_update = Oracle {
    sequence: 1234567890,  // Must be > current sequence
    payload: PriceFeed {
        price: 42_000_000,  // $42.00 with 6 decimals
    },
};

// Create update instruction
let update_ix: Instruction = UpdateInstruction {
    admin: admin_pubkey,
    oracle_pubkey: oracle_pubkey,
    oracle: oracle_update,
}.into();

// Add to instructions
instructions.push(update_ix);

5. Complete Transaction Example

use doppler_sdk::{Oracle, UpdateInstruction};
use solana_client::rpc_client::RpcClient;
use solana_compute_budget_interface::ComputeBudgetInstruction;
use solana_instruction::Instruction;
use solana_keypair::Keypair;
use solana_signer::Signer;
use solana_transaction::Transaction;

async fn update_oracle(
    client: &RpcClient,
    admin: &Keypair,
    oracle_pubkey: Pubkey,
    new_price: u64,
    sequence: u64,
) -> Result<(), Box<dyn std::error::Error>> {
    // Build all instructions
    let mut instructions = vec![
        // 1. Set compute budget
        ComputeBudgetInstruction::set_compute_unit_limit(200_000),

        // 2. Set priority fee (1000 micro-lamports per CU)
        ComputeBudgetInstruction::set_compute_unit_price(1_000),

        // 3. Set loaded accounts data size limit
        ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(32_768),
    ];

    // 4. Add oracle update
    let oracle_update = Oracle {
        sequence,
        payload: PriceFeed { price: new_price },
    };

    let update_ix: Instruction = UpdateInstruction {
        admin: admin.pubkey(),
        oracle_pubkey,
        oracle: oracle_update,
    }.into();

    instructions.push(update_ix);

    // Create and send transaction
    let recent_blockhash = client.get_latest_blockhash()?;
    let tx = Transaction::new_signed_with_payer(
        &instructions,
        Some(&admin.pubkey()),
        &[admin],
        recent_blockhash,
    );

    let signature = client.send_and_confirm_transaction(&tx)?;
    println!("Oracle updated: {}", signature);

    Ok(())
}

Performance Optimization Tips

1. Compute Budget Configuration

  • Exact CU Request: Request only what you need (21 CUs + overhead)
  • Priority Fees: Use dynamic priority fees based on network congestion
  • Account Data Size: Minimize loaded data to reduce memory overhead

2. Batching Updates

For multiple oracle updates, batch them efficiently:

// DON'T: Multiple transactions
for oracle in oracles {
    send_update(oracle)?;  // 21 CU each, but multiple transactions
}

// DO: Single transaction with multiple updates
let mut instructions = vec![
    ComputeBudgetInstruction::set_compute_unit_limit(200_000),
    ComputeBudgetInstruction::set_compute_unit_price(1_000),
    ComputeBudgetInstruction::set_loaded_accounts_data_size_limit(65_536),
];

for oracle in oracles {
    instructions.push(create_update_instruction(oracle));
}
// Single transaction with all updates

3. Network Optimization

// Use getRecentPrioritizationFees to determine optimal fee
let recent_fees = client.get_recent_prioritization_fees(&[oracle_pubkey])?;
let optimal_fee = calculate_optimal_fee(recent_fees);

let priority_ix = ComputeBudgetInstruction::set_compute_unit_price(optimal_fee);

Testing

Build

# Within root
cargo build-sbf --manifest-path program/Cargo.toml

Unit

Run the test suite:

# Run all tests
cargo test

E2E

chmod +X ./surfpool.sh
./surfpool.sh

cargo run --bin single-price-feed
cargo run --bin multiple-price-feed

example of single price feed update response

Transaction executed in slot 131:
  Block Time: 2025-09-03T04:23:08+03:00
  Version: legacy
  Recent Blockhash: 89ZvpNezGugkfm9LnN99rhb6aTNaW1cLKkS2DDbr7NPA
  Signature 0: m14zQFvt1jU9YYM2QAmVSnMZUa5P2eKdtP21Shu9w9kEhxKLAfJoUyqZwiTt43hGwewhsahQJi5eLJ71NptUWDu
  Account 0: srw- admnz5UvRa93HM5nTrxXmsJ1rw2tvXMBFGauvCgzQhE (fee payer)
  Account 1: -rw- QUVF91dzXWYvE5FmFEc41JZxRDmNgx8S8P6sNDWYZiW
  Account 2: -r-x ComputeBudget111111111111111111111111111111
  Account 3: -r-x fastRQJt3nLdY3QA7n8eZ8ETEVefy56ryfUGVkfZokm
  Instruction 0
    Program:   ComputeBudget111111111111111111111111111111 (2)
    Data: [3, 232, 3, 0, 0, 0, 0, 0, 0]
  Instruction 1
    Program:   ComputeBudget111111111111111111111111111111 (2)
    Data: [2, 215, 1, 0, 0]
  Instruction 2
    Program:   ComputeBudget111111111111111111111111111111 (2)
    Data: [4, 127, 0, 0, 0]
  Instruction 3
    Program:   fastRQJt3nLdY3QA7n8eZ8ETEVefy56ryfUGVkfZokm (3)
    Account 0: admnz5UvRa93HM5nTrxXmsJ1rw2tvXMBFGauvCgzQhE (0)
    Account 1: QUVF91dzXWYvE5FmFEc41JZxRDmNgx8S8P6sNDWYZiW (1)
    Data: [159, 136, 1, 0, 0, 0, 0, 0, 64, 226, 1, 0, 0, 0, 0, 0, 160, 213, 119, 107, 1, 0, 0, 0]
  Status: Ok
    Fee: ◎0.000005001
    Account 0 balance: ◎9.999969996 -> ◎9.999964995
    Account 1 balance: ◎0.00100224
    Account 2 balance: ◎0.000000001
    Account 3 balance: ◎0.00114144
  Compute Units Consumed: 471
  Log Messages:
    Program ComputeBudget111111111111111111111111111111 invoke [1]
    Program ComputeBudget111111111111111111111111111111 success
    Program ComputeBudget111111111111111111111111111111 invoke [1]
    Program ComputeBudget111111111111111111111111111111 success
    Program ComputeBudget111111111111111111111111111111 invoke [1]
    Program ComputeBudget111111111111111111111111111111 success
    Program fastRQJt3nLdY3QA7n8eZ8ETEVefy56ryfUGVkfZokm invoke [1]
    Program fastRQJt3nLdY3QA7n8eZ8ETEVefy56ryfUGVkfZokm consumed 21 of 21 compute units
    Program fastRQJt3nLdY3QA7n8eZ8ETEVefy56ryfUGVkfZokm success

Finalized

Fully fledged tx requires: 471 CU + 111 bytes

example of multiple price feed update response

Transaction executed in slot 218:
  Block Time: 2025-09-06T13:06:05+03:00
  Version: legacy
  Recent Blockhash: AeCvWYJjrx6Yxjknh6ndTTaTYsHkPQgr9iMURRN8Ah4S
  Signature 0: 3MLXk7YCsqEoMiYiGT4RYKa3Js2QJ6acM1BQstKGNbXsUJ6rNaySmUzzqNRDnFd7St1XTpPngAbcnf3ZxD2Lj9Jr
  Account 0: srw- admnz5UvRa93HM5nTrxXmsJ1rw2tvXMBFGauvCgzQhE (fee payer)
  Account 1: -rw- QUVF91dzXWYvE5FmFEc41JZxRDmNgx8S8P6sNDWYZiW
  Account 2: -rw- 6uQ848roY5vumz43QeQguE7xCyBSmgZbwNdJMTrs2Xhy
  Account 3: -rw- 9bA7GPqPpZ5aLbwb8E6cKvUPM8pcHXXTqLpf5zLAqHP5
  Account 4: -r-x ComputeBudget111111111111111111111111111111
  Account 5: -r-x fastRQJt3nLdY3QA7n8eZ8ETEVefy56ryfUGVkfZokm
  Instruction 0
    Program:   ComputeBudget111111111111111111111111111111 (4)
    Data: [3, 232, 3, 0, 0, 0, 0, 0, 0]
  Instruction 1
    Program:   ComputeBudget111111111111111111111111111111 (4)
    Data: [4, 175, 0, 0, 0]
  Instruction 2
    Program:   ComputeBudget111111111111111111111111111111 (4)
    Data: [2, 1, 2, 0, 0]
  Instruction 3
    Program:   fastRQJt3nLdY3QA7n8eZ8ETEVefy56ryfUG
View on GitHub
GitHub Stars157
CategoryDevelopment
Updated1d ago
Forks29

Languages

Rust

Security Score

100/100

Audited on Apr 3, 2026

No findings