The fastest oracle on Solana
A 21 CU Solana Oracle Program
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 solanacomputebudget_interface::ComputeBudgetInstruction;
use solana_instruction::Instruction;
use solana_transaction::Transaction;
// Request exactly the CUs needed (21 + overhead for other instructions) let computebudgetix = ComputeBudgetInstruction::setcomputeunitlimit(200000);
// Add to your transaction let mut instructions = vec![computebudgetix];
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 priorityfeeix = ComputeBudgetInstruction::setcomputeunit_price(1000);
instructions.push(priorityfeeix);
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 datasizelimitix = ComputeBudgetInstruction::setloadedaccountsdatasizelimit(
32_768 // 32KB is usually sufficient for oracle operations
);
instructions.push(datasizelimit_ix);
4. Creating an Oracle Update
use dopplersdk::{Oracle, UpdateInstruction, ID as DOPPLERID};
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: 42000000, // $42.00 with 6 decimals }, };
// Create update instruction let update_ix: Instruction = UpdateInstruction { admin: admin_pubkey, oraclepubkey: oraclepubkey, oracle: oracle_update, }.into();
// Add to instructions instructions.push(update_ix);
5. Complete Transaction Example
use doppler_sdk::{Oracle, UpdateInstruction};
use solanaclient::rpcclient::RpcClient;
use solanacomputebudget_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::setcomputeunitlimit(200000),
// 2. Set priority fee (1000 micro-lamports per CU) ComputeBudgetInstruction::setcomputeunitprice(1000),
// 3. Set loaded accounts data size limit ComputeBudgetInstruction::setloadedaccountsdatasizelimit(32768), ];
// 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 recentblockhash = client.getlatest_blockhash()?; let tx = Transaction::newsignedwith_payer( &instructions, Some(&admin.pubkey()), &[admin], recent_blockhash, );
let signature = client.sendandconfirm_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::setcomputeunitlimit(200000), ComputeBudgetInstruction::setcomputeunitprice(1000), ComputeBudgetInstruction::setloadedaccountsdatasizelimit(65536), ];
for oracle in oracles { instructions.push(createupdateinstruction(oracle)); } // Single transaction with all updates
3. Network Optimization
// Use getRecentPrioritizationFees to determine optimal fee
let recentfees = client.getrecentprioritizationfees(&[oracle_pubkey])?;
let optimalfee = calculateoptimalfee(recentfees);
let priorityix = ComputeBudgetInstruction::setcomputeunitprice(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: fastRQJt3nLdY3QA7n8eZ8ETEVefy56ryfUGVkfZokm (5)
Account 0: admnz5UvRa93HM5nTrxXmsJ1rw2tvXMBFGauvCgzQhE (0)
Account 1: QUVF91dzXWYvE5FmFEc41JZxRDmNgx8S8P6sNDWYZiW (1)
Data: [2, 0, 0, 0, 0, 0, 0, 0, 180, 134, 1, 0, 0, 0, 0, 0]
Instruction 4
Program: fastRQJt3nLdY3QA7n8eZ8ETEVefy56ryfUGVkfZokm (5)
Account 0: admnz5UvRa93HM5nTrxXmsJ1rw2tvXMBFGauvCgzQhE (0)
Account 1: 9bA7GPqPpZ5aLbwb8E6cKvUPM8pcHXXTqLpf5zLAqHP5 (3)
Data: [1, 0, 0, 0, 0, 0, 0, 0, 170, 134, 1, 0, 0, 0, 0, 0]
Instruction 5
Program: fastRQJt3nLdY3QA7n8eZ8ETEVefy56ryfUGVkfZokm (5)
Account 0: admnz5UvRa93HM5nTrxXmsJ1rw2tvXMBFGauvCgzQhE (0)
Account 1: 6uQ848roY5vumz43QeQguE7xCyBSmgZbwNdJMTrs2Xhy (2)
Data: [1, 0, 0, 0, 0, 0, 0, 0, 170, 134, 1, 0, 0, 0, 0, 0]
Status: Ok
Fee: ◎0.000005001
Account 0 balance: ◎0.999994999 -> ◎0.999989998
Account 1 balance: ◎0.00100224
Account 2 balance: ◎0.00100224
Account 3 balance: ◎0.00100224
Account 4 balance: ◎0.000000001
Account 5 balance: ◎0.00114144
Compute Units Consumed: 513
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 63 compute units
Program fastRQJt3nLdY3QA7n8eZ8ETEVefy56ryfUGVkfZokm success
Program fastRQJt3nLdY3QA7n8eZ8ETEVefy56ryfUGVkfZokm invoke [1]
Program fastRQJt3nLdY3QA7n8eZ8ETEVefy56ryfUGVkfZokm consumed 21 of 42 compute units
Program fastRQJt3nLdY3QA7n8eZ8ETEVefy56ryfUGVkfZokm success
Program fastRQJt3nLdY3QA7n8eZ8ETEVefy56ryfUGVkfZokm invoke [1]
Program fastRQJt3nLdY3QA7n8eZ8ETEVefy56ryfUGVkfZokm consumed 21 of 21 compute units
Program fastRQJt3nLdY3QA7n8eZ8ETEVefy56ryfUGVkfZokm success
Finalized
Expected Priority Score
based on the Anza's blog post and the code from single price feed update example
let's assume we are going to update a single oracle:
- 1 signature
- 0 write locks
- Requested compute-budget-limit to 21 (with compute-budget instructions 321 and 471 respectively) CUs
- Paying priority fee: 1.00 lamports per CU
Building
Build the on-chain program:
# Build for Solana BPF
cargo build-sbf
Deploy
solana program deploy target/deploy/doppler.so
Security Considerations
- Admin Key: The admin key is hardcoded in the program for security
- Sequence Validation: Prevents replay attacks and ensures ordering
- No External Dependencies: Reduces attack surface
- Direct Memory Operations: Eliminates unnecessary abstraction layers
Benchmarks
| Operation | Compute Units | | ------------------ | ------------- | | Oracle Update | 21 | | Sequence Check | 5 | | Payload Write | 10 | | Admin Verification | 6 |
Example Payloads
Simple Price Feed
#[derive(Clone, Copy)]
pub struct PriceFeed {
pub price: u64,
}
AMM Oracle
#[derive(Clone, Copy)]
pub struct PropAMM {
pub bid: u64,
pub ask: u64,
}
Complex Market Data
#[derive(Clone, Copy)]
pub struct MarketData {
pub price: u64,
pub volume: u64,
pub confidence: u32,
}
FAQ
Q: Why only 21 CUs? A: Doppler uses direct memory operations, inline assembly optimizations, and zero-overhead abstractions to achieve minimal compute usage.
Q: Can I use custom payload types? A: Yes! Doppler is generic over any Copy type. Define your structure and use it with the SDK.
Q: How do I handle oracle account creation? A: However you like, but if you use Solana's createaccountwith_seed instruction with the admin as the base key it's cheaper!
Q: What's the maximum update frequency? A: Limited only by Solana's throughput. With 21 CUs, you can update as fast as you land.
Support
For issues, questions, or contributions:
- GitHub: @blueshift-gg
- X: @blueshift
- Discord: discord.gg/blueshift
License
Licensed under MIT.