Doppler
The fastest oracle on Solana
Install / Use
/learn @blueshift-gg/DopplerREADME
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:
- Admin Account: Controls oracle updates (hardcoded for security)
- Oracle Account: Stores the sequence number and payload data
- 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
