Solana Programs Specification¶
Detailed implementation specifications for all on-chain programs
Program Overview¶
Core Programs¶
- Controller Program - System orchestration and governance
- OTC Trading Program - Peer-to-peer trading and escrow
- Algorithm Execution Program - TWAP/VWAP/Intent execution
- Privacy Program - Zero-knowledge proofs and privacy pools
- Oracle Program - Price feed aggregation and validation
Program Addresses (Mainnet)¶
moby-controller: MobyCtrl1111111111111111111111111111111111
moby-otc: MobyOTC11111111111111111111111111111111111
moby-executor: MobyExec1111111111111111111111111111111111
moby-privacy: MobyPriv1111111111111111111111111111111111
moby-oracle: MobyOrcl1111111111111111111111111111111111
1. Controller Program (moby-controller)¶
Purpose¶
Central coordination and governance for the entire Moby Market ecosystem.
Instructions¶
Initialize System¶
#[derive(Accounts)]
pub struct InitializeSystem<'info> {
#[account(
init,
payer = authority,
space = 8 + GlobalState::INIT_SPACE,
seeds = [b"global-state"],
bump
)]
pub global_state: Account<'info, GlobalState>,
#[account(mut)]
pub authority: Signer<'info>,
pub system_program: Program<'info, System>,
}
pub fn initialize_system(
ctx: Context<InitializeSystem>,
initial_config: InitialConfig,
) -> Result<()> {
let global_state = &mut ctx.accounts.global_state;
global_state.authority = ctx.accounts.authority.key();
global_state.paused = false;
global_state.emergency_mode = false;
global_state.total_volume_traded = 0;
global_state.total_fees_collected = 0;
global_state.supported_tokens = initial_config.supported_tokens;
global_state.fee_collector = initial_config.fee_collector;
global_state.upgrade_authority = initial_config.upgrade_authority;
global_state.created_at = Clock::get()?.unix_timestamp;
global_state.last_updated = Clock::get()?.unix_timestamp;
emit!(SystemInitialized {
authority: ctx.accounts.authority.key(),
timestamp: Clock::get()?.unix_timestamp,
});
Ok(())
}
Update System Configuration¶
#[derive(Accounts)]
pub struct UpdateSystemConfig<'info> {
#[account(
mut,
seeds = [b"global-state"],
bump,
has_one = authority
)]
pub global_state: Account<'info, GlobalState>,
pub authority: Signer<'info>,
}
pub fn update_system_config(
ctx: Context<UpdateSystemConfig>,
updates: SystemConfigUpdates,
) -> Result<()> {
let global_state = &mut ctx.accounts.global_state;
// Apply updates with validation
if let Some(new_tokens) = updates.supported_tokens {
global_state.supported_tokens = new_tokens;
}
if let Some(new_fee_collector) = updates.fee_collector {
global_state.fee_collector = new_fee_collector;
}
global_state.last_updated = Clock::get()?.unix_timestamp;
emit!(SystemConfigUpdated {
authority: ctx.accounts.authority.key(),
updates,
timestamp: Clock::get()?.unix_timestamp,
});
Ok(())
}
Emergency Pause¶
pub fn emergency_pause(ctx: Context<EmergencyPause>) -> Result<()> {
let global_state = &mut ctx.accounts.global_state;
require!(!global_state.paused, ErrorCode::AlreadyPaused);
global_state.paused = true;
global_state.emergency_mode = true;
global_state.last_updated = Clock::get()?.unix_timestamp;
emit!(EmergencyPauseActivated {
triggered_by: ctx.accounts.authority.key(),
timestamp: Clock::get()?.unix_timestamp,
});
Ok(())
}
State Management¶
Global State Structure¶
#[account]
#[derive(InitSpace)]
pub struct GlobalState {
pub authority: Pubkey,
pub paused: bool,
pub emergency_mode: bool,
pub total_volume_traded: u64,
pub total_fees_collected: u64,
#[max_len(50)]
pub supported_tokens: Vec<Pubkey>,
pub fee_collector: Pubkey,
pub upgrade_authority: Pubkey,
pub created_at: i64,
pub last_updated: i64,
pub program_versions: ProgramVersions,
pub risk_parameters: GlobalRiskParameters,
}
#[derive(AnchorSerialize, AnchorDeserialize, InitSpace, Clone)]
pub struct GlobalRiskParameters {
pub max_order_size: u64,
pub max_daily_volume: u64,
pub emergency_stop_threshold: u64,
pub min_collateral_ratio: u16,
pub liquidation_threshold: u16,
}
2. OTC Trading Program (moby-otc)¶
Purpose¶
Handles all over-the-counter trading operations including fixed-price orders, RFQ system, and dark pools.
Instructions¶
Create Fixed Price Order¶
#[derive(Accounts)]
#[instruction(order_params: CreateOrderParams)]
pub struct CreateFixedPriceOrder<'info> {
#[account(
init,
payer = maker,
space = 8 + OTCOrder::INIT_SPACE,
seeds = [
b"otc-order",
maker.key().as_ref(),
&order_params.order_id
],
bump
)]
pub order: Account<'info, OTCOrder>,
#[account(
init,
payer = maker,
space = 8 + OTCEscrow::INIT_SPACE,
seeds = [
b"otc-escrow",
order.key().as_ref()
],
bump
)]
pub escrow: Account<'info, OTCEscrow>,
/// CHECK: Will be validated by token program
#[account(
init,
payer = maker,
token::mint = token_mint_a,
token::authority = escrow,
seeds = [
b"escrow-token-account",
escrow.key().as_ref(),
token_mint_a.key().as_ref()
],
bump
)]
pub escrow_token_account_a: Account<'info, TokenAccount>,
#[account(
mut,
token::mint = token_mint_a,
token::authority = maker
)]
pub maker_token_account_a: Account<'info, TokenAccount>,
pub token_mint_a: Account<'info, Mint>,
pub token_mint_b: Account<'info, Mint>,
#[account(mut)]
pub maker: Signer<'info>,
pub token_program: Program<'info, Token>,
pub system_program: Program<'info, System>,
pub rent: Sysvar<'info, Rent>,
}
pub fn create_fixed_price_order(
ctx: Context<CreateFixedPriceOrder>,
params: CreateOrderParams,
) -> Result<()> {
let order = &mut ctx.accounts.order;
let escrow = &mut ctx.accounts.escrow;
// Validate order parameters
require!(params.amount_a > 0, ErrorCode::InvalidAmount);
require!(params.amount_b > 0, ErrorCode::InvalidAmount);
require!(params.expiry > Clock::get()?.unix_timestamp, ErrorCode::InvalidExpiry);
require!(
ctx.accounts.token_mint_a.key() != ctx.accounts.token_mint_b.key(),
ErrorCode::SameTokenSwap
);
// Initialize order
order.order_id = params.order_id;
order.maker = ctx.accounts.maker.key();
order.token_mint_a = ctx.accounts.token_mint_a.key();
order.token_mint_b = ctx.accounts.token_mint_b.key();
order.amount_a = params.amount_a;
order.amount_b = params.amount_b;
order.min_fill_amount = params.min_fill_amount.unwrap_or(params.amount_a);
order.expiry = params.expiry;
order.order_type = OTCOrderType::FixedPrice;
order.privacy_level = params.privacy_level;
order.status = OrderStatus::Created;
order.created_at = Clock::get()?.unix_timestamp;
order.slippage_tolerance = params.slippage_tolerance;
// Initialize escrow
escrow.order_id = params.order_id;
escrow.escrow_authority = escrow.key();
escrow.token_account_a = ctx.accounts.escrow_token_account_a.key();
// Transfer tokens to escrow
let transfer_instruction = Transfer {
from: ctx.accounts.maker_token_account_a.to_account_info(),
to: ctx.accounts.escrow_token_account_a.to_account_info(),
authority: ctx.accounts.maker.to_account_info(),
};
token::transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
transfer_instruction,
),
params.amount_a,
)?;
escrow.deposited_amount_a = params.amount_a;
emit!(OTCOrderCreated {
order_id: params.order_id,
maker: ctx.accounts.maker.key(),
token_a: ctx.accounts.token_mint_a.key(),
token_b: ctx.accounts.token_mint_b.key(),
amount_a: params.amount_a,
amount_b: params.amount_b,
order_type: OTCOrderType::FixedPrice,
timestamp: Clock::get()?.unix_timestamp,
});
Ok(())
}
Accept OTC Order¶
#[derive(Accounts)]
#[instruction(fill_amount: u64)]
pub struct AcceptOTCOrder<'info> {
#[account(
mut,
seeds = [
b"otc-order",
order.maker.as_ref(),
&order.order_id
],
bump,
constraint = order.status == OrderStatus::Created || order.status == OrderStatus::Partial
)]
pub order: Account<'info, OTCOrder>,
#[account(
mut,
seeds = [
b"otc-escrow",
order.key().as_ref()
],
bump
)]
pub escrow: Account<'info, OTCEscrow>,
#[account(
mut,
token::mint = order.token_mint_a,
token::authority = escrow
)]
pub escrow_token_account_a: Account<'info, TokenAccount>,
#[account(
mut,
token::mint = order.token_mint_b,
token::authority = taker
)]
pub taker_token_account_b: Account<'info, TokenAccount>,
#[account(
mut,
token::mint = order.token_mint_a,
token::authority = taker
)]
pub taker_token_account_a: Account<'info, TokenAccount>,
#[account(
mut,
token::mint = order.token_mint_b,
token::authority = order.maker
)]
pub maker_token_account_b: Account<'info, TokenAccount>,
#[account(mut)]
pub taker: Signer<'info>,
pub token_program: Program<'info, Token>,
}
pub fn accept_otc_order(
ctx: Context<AcceptOTCOrder>,
fill_amount: u64,
) -> Result<()> {
let order = &mut ctx.accounts.order;
let escrow = &mut ctx.accounts.escrow;
// Validate fill amount
let remaining_amount = order.amount_a - order.filled_amount_a;
require!(fill_amount <= remaining_amount, ErrorCode::ExcessiveFillAmount);
require!(fill_amount >= order.min_fill_amount, ErrorCode::BelowMinFillAmount);
// Calculate proportional amount B
let fill_amount_b = (fill_amount as u128 * order.amount_b as u128 / order.amount_a as u128) as u64;
// Transfer token B from taker to maker
let transfer_b_to_maker = Transfer {
from: ctx.accounts.taker_token_account_b.to_account_info(),
to: ctx.accounts.maker_token_account_b.to_account_info(),
authority: ctx.accounts.taker.to_account_info(),
};
token::transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
transfer_b_to_maker,
),
fill_amount_b,
)?;
// Transfer token A from escrow to taker
let escrow_key = escrow.key();
let seeds = &[
b"otc-escrow",
order.to_account_info().key.as_ref(),
&[ctx.bumps.escrow],
];
let signer = &[&seeds[..]];
let transfer_a_to_taker = Transfer {
from: ctx.accounts.escrow_token_account_a.to_account_info(),
to: ctx.accounts.taker_token_account_a.to_account_info(),
authority: escrow.to_account_info(),
};
token::transfer(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
transfer_a_to_taker,
signer,
),
fill_amount,
)?;
// Update order state
order.filled_amount_a += fill_amount;
order.filled_amount_b += fill_amount_b;
order.taker = Some(ctx.accounts.taker.key());
order.updated_at = Clock::get()?.unix_timestamp;
if order.filled_amount_a == order.amount_a {
order.status = OrderStatus::Filled;
} else {
order.status = OrderStatus::Partial;
}
emit!(OTCOrderFilled {
order_id: order.order_id,
taker: ctx.accounts.taker.key(),
fill_amount_a: fill_amount,
fill_amount_b: fill_amount_b,
total_filled_a: order.filled_amount_a,
remaining_amount_a: order.amount_a - order.filled_amount_a,
timestamp: Clock::get()?.unix_timestamp,
});
Ok(())
}
Create RFQ (Request for Quote)¶
pub fn create_rfq(
ctx: Context<CreateRFQ>,
params: CreateRFQParams,
) -> Result<()> {
let rfq = &mut ctx.accounts.rfq;
// Validate RFQ parameters
require!(params.amount_desired > 0, ErrorCode::InvalidAmount);
require!(params.amount_offered > 0, ErrorCode::InvalidAmount);
require!(
params.quote_deadline > Clock::get()?.unix_timestamp,
ErrorCode::InvalidDeadline
);
require!(
params.settlement_deadline > params.quote_deadline,
ErrorCode::InvalidSettlementDeadline
);
rfq.rfq_id = params.rfq_id;
rfq.requester = ctx.accounts.requester.key();
rfq.token_desired = params.token_desired;
rfq.token_offered = params.token_offered;
rfq.amount_desired = params.amount_desired;
rfq.amount_offered = params.amount_offered;
rfq.quote_deadline = params.quote_deadline;
rfq.settlement_deadline = params.settlement_deadline;
rfq.min_responder_reputation = params.min_responder_reputation;
rfq.max_responders = params.max_responders;
rfq.status = RFQStatus::Active;
rfq.created_at = Clock::get()?.unix_timestamp;
emit!(RFQCreated {
rfq_id: params.rfq_id,
requester: ctx.accounts.requester.key(),
token_desired: params.token_desired,
token_offered: params.token_offered,
amount_desired: params.amount_desired,
amount_offered: params.amount_offered,
quote_deadline: params.quote_deadline,
timestamp: Clock::get()?.unix_timestamp,
});
Ok(())
}
Account Structures¶
#[account]
#[derive(InitSpace)]
pub struct OTCOrder {
pub order_id: [u8; 32],
pub maker: Pubkey,
pub taker: Option<Pubkey>,
pub token_mint_a: Pubkey,
pub token_mint_b: Pubkey,
pub amount_a: u64,
pub amount_b: u64,
pub filled_amount_a: u64,
pub filled_amount_b: u64,
pub min_fill_amount: u64,
pub expiry: i64,
pub order_type: OTCOrderType,
pub privacy_level: PrivacyLevel,
pub status: OrderStatus,
pub created_at: i64,
pub updated_at: i64,
pub fees_paid: u64,
pub slippage_tolerance: u16,
pub maker_fee_bps: u16,
pub taker_fee_bps: u16,
}
#[derive(AnchorSerialize, AnchorDeserialize, InitSpace, Clone)]
pub enum OTCOrderType {
FixedPrice,
RFQ,
DarkPool,
}
#[derive(AnchorSerialize, AnchorDeserialize, InitSpace, Clone)]
pub enum OrderStatus {
Created,
Partial,
Filled,
Cancelled,
Expired,
}
#[derive(AnchorSerialize, AnchorDeserialize, InitSpace, Clone)]
pub enum PrivacyLevel {
Public,
Stealth,
Anonymous,
}
#[account]
#[derive(InitSpace)]
pub struct RFQOrder {
pub rfq_id: [u8; 32],
pub requester: Pubkey,
pub token_desired: Pubkey,
pub token_offered: Pubkey,
pub amount_desired: u64,
pub amount_offered: u64,
pub quote_deadline: i64,
pub settlement_deadline: i64,
pub min_responder_reputation: u32,
pub max_responders: u8,
pub status: RFQStatus,
pub created_at: i64,
#[max_len(10)]
pub quotes: Vec<Quote>,
pub selected_quote: Option<u8>, // Index into quotes array
}
#[derive(AnchorSerialize, AnchorDeserialize, InitSpace, Clone)]
pub struct Quote {
pub responder: Pubkey,
pub price: u64, // Price per unit in quote token
pub amount: u64, // Amount willing to trade
pub valid_until: i64,
pub partial_fill_allowed: bool,
pub submitted_at: i64,
}
3. Algorithm Execution Program (moby-executor)¶
Purpose¶
Implements sophisticated execution algorithms including TWAP, VWAP, and intent-based execution.
Instructions¶
Create TWAP Order¶
pub fn create_twap_order(
ctx: Context<CreateTWAPOrder>,
params: CreateTWAPParams,
) -> Result<()> {
let twap_order = &mut ctx.accounts.twap_order;
// Validate TWAP parameters
require!(params.total_amount > 0, ErrorCode::InvalidAmount);
require!(params.time_window > 0, ErrorCode::InvalidTimeWindow);
require!(params.num_intervals > 0, ErrorCode::InvalidIntervals);
require!(params.num_intervals <= 1000, ErrorCode::TooManyIntervals);
// Calculate interval amounts with randomization
let base_amount = params.total_amount / params.num_intervals as u64;
let mut interval_amounts = Vec::new();
let mut remaining_amount = params.total_amount;
for i in 0..params.num_intervals {
let is_last = i == params.num_intervals - 1;
let amount = if is_last {
remaining_amount
} else {
// Apply randomization factor
let randomness = generate_randomness(params.randomness_factor);
let adjusted_amount = apply_randomness(base_amount, randomness);
std::cmp::min(adjusted_amount, remaining_amount)
};
interval_amounts.push(amount);
remaining_amount -= amount;
}
twap_order.order_id = params.order_id;
twap_order.trader = ctx.accounts.trader.key();
twap_order.token_in = params.token_in;
twap_order.token_out = params.token_out;
twap_order.total_amount_in = params.total_amount;
twap_order.min_amount_out = params.min_amount_out;
twap_order.time_window_seconds = params.time_window;
twap_order.num_intervals = params.num_intervals;
twap_order.randomness_factor = params.randomness_factor;
twap_order.max_slippage_bps = params.max_slippage_bps;
twap_order.interval_amounts = interval_amounts;
twap_order.venue_preferences = params.venue_preferences;
twap_order.status = AlgorithmStatus::Active;
twap_order.privacy_enabled = params.privacy_enabled;
twap_order.created_at = Clock::get()?.unix_timestamp;
// Calculate next execution time
let interval_duration = params.time_window / params.num_intervals as i64;
twap_order.next_execution_time = Clock::get()?.unix_timestamp + interval_duration;
emit!(TWAPOrderCreated {
order_id: params.order_id,
trader: ctx.accounts.trader.key(),
total_amount: params.total_amount,
num_intervals: params.num_intervals,
time_window: params.time_window,
next_execution_time: twap_order.next_execution_time,
timestamp: Clock::get()?.unix_timestamp,
});
Ok(())
}
Execute TWAP Interval¶
pub fn execute_twap_interval(
ctx: Context<ExecuteTWAPInterval>,
) -> Result<()> {
let twap_order = &mut ctx.accounts.twap_order;
// Verify execution timing
let current_time = Clock::get()?.unix_timestamp;
require!(
current_time >= twap_order.next_execution_time,
ErrorCode::ExecutionTooEarly
);
require!(
twap_order.status == AlgorithmStatus::Active,
ErrorCode::OrderNotActive
);
require!(
twap_order.current_interval < twap_order.num_intervals,
ErrorCode::AllIntervalsExecuted
);
// Get current interval amount
let interval_index = twap_order.current_interval as usize;
let execution_amount = twap_order.interval_amounts[interval_index];
// Execute trade through optimal routing
let route_result = execute_optimal_route(
&ctx.accounts,
execution_amount,
twap_order.max_slippage_bps,
&twap_order.venue_preferences,
)?;
// Record execution
let execution_record = ExecutionRecord {
interval_index: twap_order.current_interval,
amount_in: execution_amount,
amount_out: route_result.amount_out,
execution_price: route_result.execution_price,
slippage_bps: route_result.slippage_bps,
venue_used: route_result.venue_used,
gas_used: route_result.gas_used,
timestamp: current_time,
};
twap_order.execution_history.push(execution_record);
twap_order.executed_amount_in += execution_amount;
twap_order.received_amount_out += route_result.amount_out;
twap_order.current_interval += 1;
// Update next execution time with randomization
if twap_order.current_interval < twap_order.num_intervals {
let base_interval = twap_order.time_window_seconds / twap_order.num_intervals as i64;
let randomness_offset = generate_execution_delay(twap_order.randomness_factor);
twap_order.next_execution_time = current_time + base_interval + randomness_offset;
} else {
twap_order.status = AlgorithmStatus::Completed;
twap_order.next_execution_time = 0;
}
twap_order.updated_at = current_time;
emit!(TWAPIntervalExecuted {
order_id: twap_order.order_id,
interval_index: interval_index as u32,
amount_in: execution_amount,
amount_out: route_result.amount_out,
execution_price: route_result.execution_price,
slippage_bps: route_result.slippage_bps,
next_execution_time: twap_order.next_execution_time,
timestamp: current_time,
});
Ok(())
}
Create VWAP Order¶
pub fn create_vwap_order(
ctx: Context<CreateVWAPOrder>,
params: CreateVWAPParams,
) -> Result<()> {
let vwap_order = &mut ctx.accounts.vwap_order;
// Validate VWAP parameters
require!(params.total_amount > 0, ErrorCode::InvalidAmount);
require!(params.target_participation_bps <= 2000, ErrorCode::ExcessiveParticipation); // Max 20%
require!(params.time_horizon_hours > 0, ErrorCode::InvalidTimeHorizon);
require!(params.time_horizon_hours <= 168, ErrorCode::TimeHorizonTooLong); // Max 1 week
// Generate volume forecast based on historical data
let volume_forecast = generate_volume_forecast(
¶ms.token_pair,
params.time_horizon_hours,
&ctx.accounts.oracle_data,
)?;
// Create execution schedule based on volume forecast
let execution_schedule = create_vwap_schedule(
params.total_amount,
params.target_participation_bps,
&volume_forecast,
params.rebalance_frequency_minutes,
)?;
vwap_order.order_id = params.order_id;
vwap_order.trader = ctx.accounts.trader.key();
vwap_order.token_pair = params.token_pair;
vwap_order.total_amount = params.total_amount;
vwap_order.target_participation_bps = params.target_participation_bps;
vwap_order.volume_curve_type = params.volume_curve_type;
vwap_order.time_horizon_hours = params.time_horizon_hours;
vwap_order.rebalance_frequency_minutes = params.rebalance_frequency_minutes;
vwap_order.volume_forecast = volume_forecast;
vwap_order.execution_schedule = execution_schedule;
vwap_order.adaptive_parameters = params.adaptive_parameters;
emit!(VWAPOrderCreated {
order_id: params.order_id,
trader: ctx.accounts.trader.key(),
total_amount: params.total_amount,
target_participation_bps: params.target_participation_bps,
time_horizon_hours: params.time_horizon_hours,
timestamp: Clock::get()?.unix_timestamp,
});
Ok(())
}
Helper Functions¶
fn execute_optimal_route(
accounts: &ExecuteTWAPInterval,
amount_in: u64,
max_slippage_bps: u16,
venue_preferences: &[VenueWeight],
) -> Result<RouteResult> {
// Implementation of smart routing logic
// 1. Query liquidity across all venues
// 2. Calculate optimal route considering fees and slippage
// 3. Execute through best venue or split across multiple venues
// 4. Return execution results
// Placeholder implementation
Ok(RouteResult {
amount_out: amount_in * 150, // Assuming SOL -> USDC conversion
execution_price: 150_000_000, // Price in micro-units
slippage_bps: 5, // 0.05%
venue_used: Pubkey::default(),
gas_used: 50_000,
})
}
fn generate_randomness(factor: u8) -> i64 {
// Generate cryptographically secure randomness
// Uses Solana's built-in randomness when available
// Falls back to deterministic pseudo-randomness based on slot/timestamp
if factor == 0 {
return 0;
}
let slot = Clock::get().unwrap().slot;
let timestamp = Clock::get().unwrap().unix_timestamp;
// Simple PRNG based on slot and timestamp
let seed = (slot as i64).wrapping_mul(timestamp);
let rand_val = seed % (factor as i64 * 2) - (factor as i64);
rand_val
}
fn generate_volume_forecast(
token_pair: &TokenPair,
time_horizon_hours: u8,
oracle_data: &Account<OracleData>,
) -> Result<Vec<VolumeDataPoint>> {
// Generate volume forecast based on:
// 1. Historical volume patterns
// 2. Time-of-day effects
// 3. Day-of-week effects
// 4. Market volatility
// 5. External events
let mut forecast = Vec::new();
let intervals = time_horizon_hours as usize * 4; // 15-minute intervals
for i in 0..intervals {
let hour_of_day = (i / 4) % 24;
let base_volume = get_historical_average_volume(token_pair, hour_of_day)?;
let volatility_adjustment = get_volatility_adjustment(oracle_data, i)?;
let predicted_volume = (base_volume as f64 * volatility_adjustment) as u64;
forecast.push(VolumeDataPoint {
timestamp: Clock::get()?.unix_timestamp + (i as i64 * 900), // 15 min intervals
predicted_volume,
confidence: 0.8, // 80% confidence
});
}
Ok(forecast)
}
4. Privacy Program (moby-privacy)¶
Purpose¶
Implements zero-knowledge privacy features including privacy pools, commitment schemes, and proof verification.
Instructions¶
Initialize Privacy Pool¶
pub fn initialize_privacy_pool(
ctx: Context<InitializePrivacyPool>,
params: InitializePoolParams,
) -> Result<()> {
let privacy_pool = &mut ctx.accounts.privacy_pool;
let commitment_tree = &mut ctx.accounts.commitment_tree;
// Validate pool parameters
require!(params.min_deposit_amount > 0, ErrorCode::InvalidMinDeposit);
require!(params.merkle_tree_height >= 10, ErrorCode::TreeTooShallow);
require!(params.merkle_tree_height <= 32, ErrorCode::TreeTooDeep);
privacy_pool.pool_id = params.pool_id;
privacy_pool.merkle_tree_height = params.merkle_tree_height;
privacy_pool.commitment_scheme = params.commitment_scheme;
privacy_pool.zk_circuit_id = params.zk_circuit_id;
privacy_pool.verification_key_hash = params.verification_key_hash;
privacy_pool.min_deposit_amount = params.min_deposit_amount;
privacy_pool.deposit_fee_bps = params.deposit_fee_bps;
privacy_pool.withdrawal_delay_seconds = params.withdrawal_delay_seconds;
// Initialize commitment tree
commitment_tree.tree_height = params.merkle_tree_height;
commitment_tree.next_index = 0;
commitment_tree.current_root_index = 0;
// Initialize zero hashes for each level
let mut current_zero = [0u8; 32];
commitment_tree.zeros[0] = current_zero;
for i in 1..params.merkle_tree_height as usize {
current_zero = hash_pair(¤t_zero, ¤t_zero);
commitment_tree.zeros[i] = current_zero;
}
// Set initial root
commitment_tree.roots[0] = commitment_tree.zeros[(params.merkle_tree_height - 1) as usize];
emit!(PrivacyPoolInitialized {
pool_id: params.pool_id,
tree_height: params.merkle_tree_height,
min_deposit: params.min_deposit_amount,
timestamp: Clock::get()?.unix_timestamp,
});
Ok(())
}
Deposit to Privacy Pool¶
pub fn deposit_to_privacy_pool(
ctx: Context<DepositToPrivacyPool>,
commitment: [u8; 32],
amount: u64,
) -> Result<()> {
let privacy_pool = &mut ctx.accounts.privacy_pool;
let commitment_tree = &mut ctx.accounts.commitment_tree;
// Validate deposit
require!(amount >= privacy_pool.min_deposit_amount, ErrorCode::DepositTooSmall);
require!(
commitment_tree.next_index < (1u32 << privacy_pool.merkle_tree_height),
ErrorCode::PoolFull
);
// Transfer tokens to pool
let transfer_instruction = Transfer {
from: ctx.accounts.depositor_token_account.to_account_info(),
to: ctx.accounts.pool_token_account.to_account_info(),
authority: ctx.accounts.depositor.to_account_info(),
};
token::transfer(
CpiContext::new(
ctx.accounts.token_program.to_account_info(),
transfer_instruction,
),
amount,
)?;
// Add commitment to tree
let leaf_index = commitment_tree.next_index;
insert_commitment(commitment_tree, commitment, leaf_index)?;
// Update pool statistics
privacy_pool.total_deposited += amount;
privacy_pool.anonymity_set_size += 1;
privacy_pool.next_leaf_index = leaf_index + 1;
// Update merkle tree root
let new_root = commitment_tree.roots[commitment_tree.current_root_index as usize];
privacy_pool.merkle_tree_root = new_root;
emit!(PrivacyPoolDeposit {
pool_id: privacy_pool.pool_id,
commitment,
leaf_index,
amount,
new_root,
timestamp: Clock::get()?.unix_timestamp,
});
Ok(())
}
Withdraw from Privacy Pool¶
pub fn withdraw_from_privacy_pool(
ctx: Context<WithdrawFromPrivacyPool>,
proof_data: WithdrawalProofData,
) -> Result<()> {
let privacy_pool = &mut ctx.accounts.privacy_pool;
// Verify the proof hasn't been used (check nullifier)
require!(
!privacy_pool.nullifier_hashes.contains(&proof_data.nullifier),
ErrorCode::NullifierAlreadyUsed
);
// Verify the merkle root is in our recent roots
let root_index = find_root_index(&privacy_pool, proof_data.root)?;
require!(root_index.is_some(), ErrorCode::InvalidMerkleRoot);
// Verify ZK proof
verify_withdrawal_proof(
&proof_data,
&ctx.accounts.verification_key,
privacy_pool.zk_circuit_id,
)?;
// Add nullifier to prevent double spending
privacy_pool.nullifier_hashes.push(proof_data.nullifier);
// Transfer tokens to recipient
let pool_seed = &privacy_pool.pool_id;
let seeds = &[
b"privacy-pool-authority",
pool_seed.as_ref(),
&[ctx.bumps.pool_authority],
];
let signer = &[&seeds[..]];
let transfer_instruction = Transfer {
from: ctx.accounts.pool_token_account.to_account_info(),
to: ctx.accounts.recipient_token_account.to_account_info(),
authority: ctx.accounts.pool_authority.to_account_info(),
};
token::transfer(
CpiContext::new_with_signer(
ctx.accounts.token_program.to_account_info(),
transfer_instruction,
signer,
),
proof_data.amount,
)?;
// Update pool statistics
privacy_pool.total_withdrawn += proof_data.amount;
privacy_pool.anonymity_set_size = privacy_pool.anonymity_set_size.saturating_sub(1);
emit!(PrivacyPoolWithdrawal {
pool_id: privacy_pool.pool_id,
nullifier: proof_data.nullifier,
amount: proof_data.amount,
recipient: proof_data.recipient,
timestamp: Clock::get()?.unix_timestamp,
});
Ok(())
}
ZK Proof Verification¶
fn verify_withdrawal_proof(
proof_data: &WithdrawalProofData,
verification_key: &Account<VerificationKey>,
circuit_id: [u8; 32],
) -> Result<()> {
// Verify the circuit ID matches
require!(
verification_key.circuit_id == circuit_id,
ErrorCode::CircuitMismatch
);
// Prepare public inputs for proof verification
let public_inputs = vec![
proof_data.nullifier.to_vec(),
proof_data.root.to_vec(),
proof_data.recipient.to_bytes().to_vec(),
proof_data.amount.to_le_bytes().to_vec(),
];
// Verify the proof using the appropriate proof system
match verification_key.proof_system {
ProofSystem::Groth16 => {
verify_groth16_proof(&proof_data.proof, &verification_key.vk_data, &public_inputs)?;
}
ProofSystem::PLONK => {
verify_plonk_proof(&proof_data.proof, &verification_key.vk_data, &public_inputs)?;
}
ProofSystem::STARKs => {
verify_stark_proof(&proof_data.proof, &verification_key.vk_data, &public_inputs)?;
}
}
Ok(())
}
fn insert_commitment(
commitment_tree: &mut Account<CommitmentTree>,
commitment: [u8; 32],
leaf_index: u32,
) -> Result<()> {
let tree_height = commitment_tree.tree_height as usize;
let mut current_index = leaf_index;
let mut current_hash = commitment;
// Update path from leaf to root
for level in 0..tree_height {
let is_right_node = current_index % 2 == 1;
if is_right_node {
let left_hash = commitment_tree.filled_subtrees[level];
current_hash = hash_pair(&left_hash, ¤t_hash);
current_index /= 2;
} else {
commitment_tree.filled_subtrees[level] = current_hash;
let right_hash = commitment_tree.zeros[level];
current_hash = hash_pair(¤t_hash, &right_hash);
current_index /= 2;
}
}
// Update root
commitment_tree.current_root_index = (commitment_tree.current_root_index + 1) % 100;
commitment_tree.roots[commitment_tree.current_root_index as usize] = current_hash;
Ok(())
}
fn hash_pair(left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] {
use solana_program::keccak;
let mut input = [0u8; 64];
input[..32].copy_from_slice(left);
input[32..].copy_from_slice(right);
keccak::hash(&input).to_bytes()
}
5. Oracle Program (moby-oracle)¶
Purpose¶
Aggregates price feeds from multiple oracle sources with manipulation detection and emergency fallbacks.
Instructions¶
Initialize Oracle Aggregator¶
pub fn initialize_oracle_aggregator(
ctx: Context<InitializeOracleAggregator>,
params: InitializeOracleParams,
) -> Result<()> {
let oracle_aggregator = &mut ctx.accounts.oracle_aggregator;
// Validate oracle configuration
require!(params.oracle_sources.len() >= 2, ErrorCode::InsufficientOracles);
require!(params.deviation_threshold_bps <= 1000, ErrorCode::DeviationTooHigh); // Max 10%
require!(params.staleness_threshold > 0, ErrorCode::InvalidStaleness);
oracle_aggregator.authority = ctx.accounts.authority.key();
oracle_aggregator.token_mint = params.token_mint;
oracle_aggregator.oracle_sources = params.oracle_sources;
oracle_aggregator.aggregation_method = params.aggregation_method;
oracle_aggregator.deviation_threshold_bps = params.deviation_threshold_bps;
oracle_aggregator.staleness_threshold = params.staleness_threshold;
oracle_aggregator.min_sources_required = params.min_sources_required;
oracle_aggregator.created_at = Clock::get()?.unix_timestamp;
emit!(OracleAggregatorInitialized {
token_mint: params.token_mint,
num_sources: params.oracle_sources.len() as u8,
aggregation_method: params.aggregation_method,
timestamp: Clock::get()?.unix_timestamp,
});
Ok(())
}
Update Price Feed¶
pub fn update_price_feed(
ctx: Context<UpdatePriceFeed>,
) -> Result<()> {
let oracle_aggregator = &mut ctx.accounts.oracle_aggregator;
let current_time = Clock::get()?.unix_timestamp;
let mut prices = Vec::new();
let mut total_valid_sources = 0;
// Collect prices from all oracle sources
for oracle_source in &oracle_aggregator.oracle_sources {
match oracle_source.oracle_type {
OracleType::Pyth => {
if let Ok(price_data) = get_pyth_price(&ctx.accounts.pyth_price_account) {
if is_price_fresh(&price_data, current_time, oracle_aggregator.staleness_threshold) {
prices.push(PricePoint {
price: price_data.price,
confidence: price_data.confidence,
timestamp: price_data.timestamp,
source: oracle_source.clone(),
});
total_valid_sources += 1;
}
}
}
OracleType::Switchboard => {
if let Ok(price_data) = get_switchboard_price(&ctx.accounts.switchboard_feed) {
if is_price_fresh(&price_data, current_time, oracle_aggregator.staleness_threshold) {
prices.push(PricePoint {
price: price_data.price,
confidence: price_data.confidence,
timestamp: price_data.timestamp,
source: oracle_source.clone(),
});
total_valid_sources += 1;
}
}
}
OracleType::Chainlink => {
// Handle Chainlink feeds via bridge
if let Ok(price_data) = get_chainlink_price(&ctx.accounts.chainlink_feed) {
if is_price_fresh(&price_data, current_time, oracle_aggregator.staleness_threshold) {
prices.push(PricePoint {
price: price_data.price,
confidence: price_data.confidence,
timestamp: price_data.timestamp,
source: oracle_source.clone(),
});
total_valid_sources += 1;
}
}
}
}
}
// Verify we have enough valid sources
require!(
total_valid_sources >= oracle_aggregator.min_sources_required,
ErrorCode::InsufficientValidSources
);
// Filter outliers
let filtered_prices = filter_price_outliers(&prices, oracle_aggregator.deviation_threshold_bps)?;
require!(!filtered_prices.is_empty(), ErrorCode::AllPricesFiltered);
// Calculate aggregated price
let aggregated_price = match oracle_aggregator.aggregation_method {
AggregationMethod::Median => calculate_median_price(&filtered_prices)?,
AggregationMethod::Mean => calculate_mean_price(&filtered_prices)?,
AggregationMethod::VolumeWeighted => calculate_vwap(&filtered_prices)?,
AggregationMethod::ConfidenceWeighted => calculate_confidence_weighted_price(&filtered_prices)?,
};
// Detect potential manipulation
let manipulation_score = detect_price_manipulation(
&oracle_aggregator.price_history,
&aggregated_price,
)?;
// Update aggregator state
oracle_aggregator.current_price = aggregated_price.clone();
oracle_aggregator.last_update_slot = Clock::get()?.slot;
oracle_aggregator.last_update_timestamp = current_time;
oracle_aggregator.valid_sources_count = total_valid_sources;
oracle_aggregator.manipulation_score = manipulation_score;
// Add to price history (keep last 100 prices)
oracle_aggregator.price_history.push(aggregated_price.clone());
if oracle_aggregator.price_history.len() > 100 {
oracle_aggregator.price_history.remove(0);
}
// Emit price update event
emit!(PriceUpdated {
token_mint: oracle_aggregator.token_mint,
price: aggregated_price.price,
confidence: aggregated_price.confidence,
sources_used: total_valid_sources,
manipulation_score,
timestamp: current_time,
});
// Trigger alerts if manipulation detected
if manipulation_score > 80 {
emit!(ManipulationAlert {
token_mint: oracle_aggregator.token_mint,
manipulation_score,
current_price: aggregated_price.price,
timestamp: current_time,
});
}
Ok(())
}
Account Structures¶
#[account]
#[derive(InitSpace)]
pub struct OracleAggregator {
pub authority: Pubkey,
pub token_mint: Pubkey,
#[max_len(10)]
pub oracle_sources: Vec<OracleSource>,
pub aggregation_method: AggregationMethod,
pub deviation_threshold_bps: u16,
pub staleness_threshold: i64,
pub min_sources_required: u8,
pub current_price: AggregatedPrice,
pub last_update_slot: u64,
pub last_update_timestamp: i64,
pub valid_sources_count: u8,
pub manipulation_score: u8,
#[max_len(100)]
pub price_history: Vec<AggregatedPrice>,
pub created_at: i64,
}
#[derive(AnchorSerialize, AnchorDeserialize, InitSpace, Clone)]
pub struct OracleSource {
pub oracle_type: OracleType,
pub feed_account: Pubkey,
pub weight: u8, // 1-100, weight in aggregation
pub max_confidence: u64, // Maximum acceptable confidence interval
pub enabled: bool,
}
#[derive(AnchorSerialize, AnchorDeserialize, InitSpace, Clone)]
pub enum OracleType {
Pyth,
Switchboard,
Chainlink,
Custom,
}
#[derive(AnchorSerialize, AnchorDeserialize, InitSpace, Clone)]
pub enum AggregationMethod {
Median,
Mean,
VolumeWeighted,
ConfidenceWeighted,
}
#[derive(AnchorSerialize, AnchorDeserialize, InitSpace, Clone)]
pub struct AggregatedPrice {
pub price: i64,
pub confidence: u64,
pub timestamp: i64,
pub sources_used: u8,
pub price_status: PriceStatus,
}
#[derive(AnchorSerialize, AnchorDeserialize, InitSpace, Clone)]
pub enum PriceStatus {
Trading, // Normal trading price
Unknown, // Price unknown or unavailable
Halted, // Trading halted due to manipulation
Auction, // In auction mode
}
Error Codes¶
#[error_code]
pub enum ErrorCode {
#[msg("Invalid amount provided")]
InvalidAmount,
#[msg("Invalid expiry time")]
InvalidExpiry,
#[msg("Same token swap not allowed")]
SameTokenSwap,
#[msg("Order already filled")]
OrderAlreadyFilled,
#[msg("Excessive fill amount")]
ExcessiveFillAmount,
#[msg("Below minimum fill amount")]
BelowMinFillAmount,
#[msg("System is currently paused")]
SystemPaused,
#[msg("Already paused")]
AlreadyPaused,
#[msg("Execution too early")]
ExecutionTooEarly,
#[msg("Order not active")]
OrderNotActive,
#[msg("All intervals executed")]
AllIntervalsExecuted,
#[msg("Invalid time window")]
InvalidTimeWindow,
#[msg("Invalid number of intervals")]
InvalidIntervals,
#[msg("Too many intervals")]
TooManyIntervals,
#[msg("Deposit amount too small")]
DepositTooSmall,
#[msg("Privacy pool is full")]
PoolFull,
#[msg("Nullifier already used")]
NullifierAlreadyUsed,
#[msg("Invalid merkle root")]
InvalidMerkleRoot,
#[msg("Invalid ZK proof")]
InvalidZKProof,
#[msg("Circuit ID mismatch")]
CircuitMismatch,
#[msg("Insufficient oracle sources")]
InsufficientOracles,
#[msg("Deviation threshold too high")]
DeviationTooHigh,
#[msg("Invalid staleness threshold")]
InvalidStaleness,
#[msg("Insufficient valid price sources")]
InsufficientValidSources,
#[msg("All prices were filtered as outliers")]
AllPricesFiltered,
}
This comprehensive Solana program specification provides the foundation for implementing the whale trading infrastructure. Each program is designed with security, efficiency, and composability in mind, following Solana best practices and Anchor framework conventions.