Skip to main content
The FHE orderbook implements four instructions for managing orders.

Instruction Enum

#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum OrderbookInstruction {
    Initialize = 0,
    SubmitOrder = 1,
    MatchOrders = 2,
    CancelOrder = 3,
}

Initialize

Creates a new orderbook with the signer as authority.

Accounts

#AccountWritableSignerDescription
0OrderbookYesNoOrderbook account to initialize
1AuthorityNoYesAuthority for the orderbook

Implementation

pub fn initialize_orderbook(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    _instruction_data: &[u8],
) -> ProgramResult {
    let account_info_iter = &mut accounts.iter();
    let orderbook_account = next_account_info(account_info_iter)?;
    let authority_account = next_account_info(account_info_iter)?;

    if !authority_account.is_signer {
        return Err(ProgramError::MissingRequiredSignature);
    }

    let orderbook = Orderbook {
        authority: *authority_account.key,
        next_order_id: 0,
        total_orders: 0,
        total_matches: 0,
    };

    orderbook.serialize(&mut *orderbook_account.data.borrow_mut())?;
    msg!("Orderbook initialized");
    Ok(())
}

SubmitOrder

Submits a new order with encrypted price and quantity references.

Accounts

#AccountWritableSignerDescription
0OrderbookYesNoOrderbook to update counters
1OrderYesNoOrder account to create
2OwnerNoYesOrder owner (signer)

Instruction Data

#[derive(BorshDeserialize)]
struct SubmitOrderData {
    price_ref: EncryptedRef<u8>,  // 32 bytes
    qty_ref: EncryptedRef<u8>,    // 32 bytes
    side: OrderSide,              // 1 byte
}

Implementation

pub fn submit_order(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let account_info_iter = &mut accounts.iter();
    let orderbook_account = next_account_info(account_info_iter)?;
    let order_account = next_account_info(account_info_iter)?;
    let owner_account = next_account_info(account_info_iter)?;

    if !owner_account.is_signer {
        return Err(ProgramError::MissingRequiredSignature);
    }

    // Deserialize instruction data with typed EncryptedRef
    let order_data = SubmitOrderData::try_from_slice(instruction_data)?;

    // Load and update orderbook
    let mut orderbook = Orderbook::try_from_slice(&orderbook_account.data.borrow())?;
    let order_id = orderbook.next_order_id;
    orderbook.next_order_id += 1;
    orderbook.total_orders += 1;
    orderbook.serialize(&mut *orderbook_account.data.borrow_mut())?;

    // Create order (only 106 bytes!)
    let order = Order {
        owner: *owner_account.key,
        price_ref: order_data.price_ref,
        qty_ref: order_data.qty_ref,
        side: order_data.side,
        status: OrderStatus::Open,
        order_id,
    };

    order.serialize(&mut *order_account.data.borrow_mut())?;
    msg!("Order {} submitted", order_id);
    Ok(())
}
The encrypted data is not passed in the instruction - only the 32-byte hash references. The actual ciphertexts must be submitted separately via submitFheData RPC.

MatchOrders

Matches a buy and sell order using FHE operations.

Accounts

#AccountWritableSignerDescription
0OrderbookYesNoUpdate match count
1Buy OrderYesNoBuy order to match
2Sell OrderYesNoSell order to match
3Match ResultYesNoStore match result
4-10Auth PDAsOptionalNoFor decryption authorization

Implementation

pub fn match_orders(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    _instruction_data: &[u8],
) -> ProgramResult {
    let account_info_iter = &mut accounts.iter();
    let orderbook_account = next_account_info(account_info_iter)?;
    let buy_order_account = next_account_info(account_info_iter)?;
    let sell_order_account = next_account_info(account_info_iter)?;
    let match_result_account = next_account_info(account_info_iter)?;

    // Load orders (small - only 106 bytes each)
    let mut buy_order = Order::try_from_slice(&buy_order_account.data.borrow())?;
    let mut sell_order = Order::try_from_slice(&sell_order_account.data.borrow())?;

    // Verify order sides and status
    if buy_order.side != OrderSide::Buy || sell_order.side != OrderSide::Sell {
        return Err(ProgramError::InvalidArgument);
    }
    if buy_order.status != OrderStatus::Open || sell_order.status != OrderStatus::Open {
        return Err(ProgramError::InvalidArgument);
    }

    // Load encrypted data using type-safe EncryptedRef.load()
    let buy_price = buy_order.price_ref.load()?;
    let sell_price = sell_order.price_ref.load()?;
    let buy_qty = buy_order.qty_ref.load()?;
    let sell_qty = sell_order.qty_ref.load()?;

    // FHE comparison: buy_price >= sell_price
    let _can_match = buy_price.ge(&sell_price)?;

    // Calculate fill quantity: min(buy_qty, sell_qty)
    let fill_qty = buy_qty.min(&sell_qty)?;

    // Use sell price as fill price
    let fill_price_ref = sell_order.price_ref;

    // Store computed fill quantity
    let fill_qty_ref = fill_qty.store()?;

    // Update order statuses
    buy_order.status = OrderStatus::Matched;
    sell_order.status = OrderStatus::Matched;
    buy_order.serialize(&mut *buy_order_account.data.borrow_mut())?;
    sell_order.serialize(&mut *sell_order_account.data.borrow_mut())?;

    // Update orderbook
    let mut orderbook = Orderbook::try_from_slice(&orderbook_account.data.borrow())?;
    let match_id = orderbook.total_matches;
    orderbook.total_matches += 1;
    orderbook.serialize(&mut *orderbook_account.data.borrow_mut())?;

    // Create match result
    let match_result = MatchResult {
        buy_order_id: buy_order.order_id,
        sell_order_id: sell_order.order_id,
        fill_price_ref,
        fill_qty_ref,
        match_id,
    };
    match_result.serialize(&mut *match_result_account.data.borrow_mut())?;

    Ok(())
}

FHE Operations Summary

OperationSDK MethodDescription
Load priceprice_ref.load()Fetch from store
Comparebuy_price.ge(&sell_price)Encrypted comparison
Minbuy_qty.min(&sell_qty)Encrypted minimum
Storefill_qty.store()Save to store

CancelOrder

Cancels an open order (owner only).

Accounts

#AccountWritableSignerDescription
0OrderYesNoOrder to cancel
1OwnerNoYesOrder owner (signer)

Implementation

pub fn cancel_order(
    _program_id: &Pubkey,
    accounts: &[AccountInfo],
    _instruction_data: &[u8],
) -> ProgramResult {
    let account_info_iter = &mut accounts.iter();
    let order_account = next_account_info(account_info_iter)?;
    let owner_account = next_account_info(account_info_iter)?;

    if !owner_account.is_signer {
        return Err(ProgramError::MissingRequiredSignature);
    }

    let mut order = Order::try_from_slice(&order_account.data.borrow())?;

    if order.owner != *owner_account.key {
        msg!("Only order owner can cancel");
        return Err(ProgramError::InvalidArgument);
    }

    if order.status != OrderStatus::Open {
        msg!("Can only cancel open orders");
        return Err(ProgramError::InvalidArgument);
    }

    order.status = OrderStatus::Cancelled;
    order.serialize(&mut *order_account.data.borrow_mut())?;
    msg!("Order {} cancelled", order.order_id);

    Ok(())
}

Entry Point

privora_sdk_program::setup_fhe_allocator!();

#[cfg(not(feature = "no-entrypoint"))]
solana_program_entrypoint::entrypoint_no_alloc!(process_instruction);

pub fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let (discriminant, data) = instruction_data.split_at(1);
    let instruction = OrderbookInstruction::from_u8(discriminant[0])
        .ok_or(ProgramError::InvalidInstructionData)?;

    match instruction {
        OrderbookInstruction::Initialize => {
            instructions::initialize_orderbook(program_id, accounts, data)
        }
        OrderbookInstruction::SubmitOrder => {
            instructions::submit_order(program_id, accounts, data)
        }
        OrderbookInstruction::MatchOrders => {
            instructions::match_orders(program_id, accounts, data)
        }
        OrderbookInstruction::CancelOrder => {
            instructions::cancel_order(program_id, accounts, data)
        }
    }
}

Next Steps

Matching Logic

Deep dive into the FHE comparison and min operations