Skip to main content
This guide shows how to interact with the FHE orderbook from both test environments and production clients.

Test Environment

The Privora testing SDK provides helpers for FHE orderbook testing.

Setup

use privora_sdk_testing::prelude::*;

const PROGRAM_ID: Pubkey = pubkey!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

#[test]
fn test_order_matching() {
    let mut env = FheTestEnv::new();
    env.sync_data_store_to_syscalls();

    // Deploy program
    let program_bytes = include_bytes!("../target/deploy/fhe_orderbook.so");
    env.deploy_program(PROGRAM_ID, program_bytes);

    // Create accounts
    let authority = env.create_funded_keypair();
    let buyer = env.create_funded_keypair();
    let seller = env.create_funded_keypair();
}

Encrypting Order Data

// Encrypt values
let buy_price: u8 = 100;
let buy_qty: u8 = 50;

// Encrypt and submit to data store
let buy_price_hash = env.encrypt_and_submit_u8(buy_price);
let buy_price_ref = env.encrypted_ref_u8(buy_price_hash);

let buy_qty_hash = env.encrypt_and_submit_u8(buy_qty);
let buy_qty_ref = env.encrypted_ref_u8(buy_qty_hash);

Submitting Orders

#[derive(BorshSerialize)]
struct SubmitOrderData {
    price_ref: EncryptedRef<u8>,
    qty_ref: EncryptedRef<u8>,
    side: OrderSide,
}

fn submit_order(
    env: &mut FheTestEnv,
    owner: &Keypair,
    orderbook: &Keypair,
    order: &Keypair,
    price_ref: EncryptedRef<u8>,
    qty_ref: EncryptedRef<u8>,
    side: OrderSide,
) {
    let data = SubmitOrderData { price_ref, qty_ref, side };
    let mut instruction_data = vec![1];  // SubmitOrder discriminator
    instruction_data.extend_from_slice(&borsh::to_vec(&data).unwrap());

    let ix = Instruction {
        program_id: PROGRAM_ID,
        accounts: vec![
            AccountMeta::new(orderbook.pubkey(), false),
            AccountMeta::new(order.pubkey(), false),
            AccountMeta::new_readonly(owner.pubkey(), true),
        ],
        data: instruction_data,
    };

    let mut tx = Transaction::new_with_payer(&[ix], Some(&owner.pubkey()));
    tx.sign(&[owner], env.latest_blockhash());
    env.svm.send_transaction(tx).expect("Failed to submit order");
}

Matching Orders

fn match_orders(
    env: &mut FheTestEnv,
    authority: &Keypair,
    orderbook: &Keypair,
    buy_order: &Keypair,
    sell_order: &Keypair,
    match_result: &Keypair,
) -> ([u8; 32], [u8; 32]) {
    let ix = Instruction {
        program_id: PROGRAM_ID,
        accounts: vec![
            AccountMeta::new(orderbook.pubkey(), false),
            AccountMeta::new(buy_order.pubkey(), false),
            AccountMeta::new(sell_order.pubkey(), false),
            AccountMeta::new(match_result.pubkey(), false),
        ],
        data: vec![2],  // MatchOrders discriminator
    };

    // Sync data store before matching
    env.sync_data_store_to_syscalls();

    let mut tx = Transaction::new_with_payer(&[ix], Some(&authority.pubkey()));
    tx.sign(&[authority], env.latest_blockhash());
    let result = env.svm.send_transaction(tx).expect("Failed to match");

    // Sync data store after matching
    env.sync_data_store_from_syscalls();

    // Extract fill hashes from return data
    let return_data = result.return_data;
    let fill_price_hash: [u8; 32] = return_data.data[0..32].try_into().unwrap();
    let fill_qty_hash: [u8; 32] = return_data.data[32..64].try_into().unwrap();

    (fill_price_hash, fill_qty_hash)
}

Verifying Results

// Verify fill values
env.assert_encrypted_eq_u8(&fill_price_hash, sell_price);
env.assert_encrypted_eq_u8(&fill_qty_hash, expected_qty);  // min(buy_qty, sell_qty)

println!("Match verified: fill_price={}, fill_qty={}", sell_price, expected_qty);

Complete Test Example

#[test]
fn test_basic_order_match() {
    let mut env = FheTestEnv::new();
    env.sync_data_store_to_syscalls();

    // Deploy program
    let program_bytes = include_bytes!("../target/deploy/fhe_orderbook.so");
    env.deploy_program(PROGRAM_ID, program_bytes);

    // Create participants
    let authority = env.create_funded_keypair();
    let buyer = env.create_funded_keypair();
    let seller = env.create_funded_keypair();

    // Create accounts
    let orderbook = Keypair::new();
    let buy_order = Keypair::new();
    let sell_order = Keypair::new();
    let match_result = Keypair::new();

    // Initialize orderbook
    create_account(&mut env, &authority, &orderbook, 56, &PROGRAM_ID);
    initialize_orderbook(&mut env, &authority, &orderbook);

    // Order data
    let buy_price: u8 = 100;
    let buy_qty: u8 = 50;
    let sell_price: u8 = 95;
    let sell_qty: u8 = 60;

    // Encrypt and create refs
    let buy_price_ref = env.encrypted_ref_u8(env.encrypt_and_submit_u8(buy_price));
    let buy_qty_ref = env.encrypted_ref_u8(env.encrypt_and_submit_u8(buy_qty));
    let sell_price_ref = env.encrypted_ref_u8(env.encrypt_and_submit_u8(sell_price));
    let sell_qty_ref = env.encrypted_ref_u8(env.encrypt_and_submit_u8(sell_qty));

    // Submit orders
    create_account(&mut env, &buyer, &buy_order, 106, &PROGRAM_ID);
    submit_order(&mut env, &buyer, &orderbook, &buy_order,
                 buy_price_ref, buy_qty_ref, OrderSide::Buy);

    create_account(&mut env, &seller, &sell_order, 106, &PROGRAM_ID);
    submit_order(&mut env, &seller, &orderbook, &sell_order,
                 sell_price_ref, sell_qty_ref, OrderSide::Sell);

    // Match orders
    create_account(&mut env, &authority, &match_result, 88, &PROGRAM_ID);
    let (fill_price_hash, fill_qty_hash) = match_orders(
        &mut env, &authority, &orderbook, &buy_order, &sell_order, &match_result
    );

    // Verify: fill_price = sell_price, fill_qty = min(50, 60) = 50
    env.assert_encrypted_eq_u8(&fill_price_hash, 95);
    env.assert_encrypted_eq_u8(&fill_qty_hash, 50);
}

TypeScript Client

For production use with the TypeScript SDK:
import { Privora, FheType } from '@privora/sdk';
import { Connection, Keypair, Transaction, PublicKey } from '@solana/web3.js';

const PROGRAM_ID = new PublicKey('Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS');

async function submitOrder(
  privora: Privora,
  connection: Connection,
  owner: Keypair,
  orderbook: PublicKey,
  orderAccount: PublicKey,
  price: number,
  quantity: number,
  side: 'buy' | 'sell'
) {
  // Encrypt order data
  const priceHash = await privora.encrypt(price, FheType.U8);
  const qtyHash = await privora.encrypt(quantity, FheType.U8);

  // Build instruction data
  const sideValue = side === 'buy' ? 0 : 1;
  const data = Buffer.concat([
    Buffer.from([1]),  // SubmitOrder discriminator
    Buffer.from(priceHash),
    Buffer.from(qtyHash),
    Buffer.from([sideValue])
  ]);

  // Create instruction
  const instruction = {
    programId: PROGRAM_ID,
    keys: [
      { pubkey: orderbook, isSigner: false, isWritable: true },
      { pubkey: orderAccount, isSigner: false, isWritable: true },
      { pubkey: owner.publicKey, isSigner: true, isWritable: false },
    ],
    data,
  };

  // Send transaction with FHE data
  const tx = new Transaction().add(instruction);
  const signature = await privora.sendTransaction(tx, connection, [owner], {
    fheDataHashes: [priceHash, qtyHash],
  });

  return signature;
}

Rust Client

For Rust clients:
use privora_sdk_client::prelude::*;

async fn submit_order(
    client: &PrivoraClient,
    owner: &Keypair,
    orderbook: Pubkey,
    order_account: Pubkey,
    price: u8,
    quantity: u8,
    side: OrderSide,
) -> Result<Signature> {
    // Encrypt order data
    let price_hash = client.encryptor().encrypt_u8(price).await?;
    let qty_hash = client.encryptor().encrypt_u8(quantity).await?;

    // Build transaction
    let tx = TransactionBuilder::new()
        .add_instruction(
            PROGRAM_ID,
            vec![
                AccountMeta::new(orderbook, false),
                AccountMeta::new(order_account, false),
                AccountMeta::new_readonly(owner.pubkey(), true),
            ],
            build_submit_order_data(price_hash, qty_hash, side),
        )
        .with_fhe_data(vec![price_hash, qty_hash])
        .build()?;

    client.send_transaction(tx, &[owner]).await
}

Key Integration Points

Encrypt Client-Side

Always encrypt prices and quantities before submission

Include FHE Hashes

Pass fheDataHashes when sending transactions

Sync Data Store

Call sync_data_store_to_syscalls before FHE operations in tests

Verify with Decryption

Use assert helpers to verify encrypted results

Summary

The FHE orderbook demonstrates a complete privacy-preserving trading system:
  1. Client: Encrypts prices and quantities
  2. Program: Matches orders using FHE operations
  3. Result: Fill values remain encrypted until authorized decryption
This pattern can be extended to more complex financial applications requiring privacy guarantees.