Skip to main content
This guide covers integrating Privora into web and mobile applications.

React Integration

Setup

npm install @privora/sdk @solana/web3.js @solana/wallet-adapter-react

Privora Provider

import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { Privora } from '@privora/sdk';

const PrivoraContext = createContext<Privora | null>(null);

export function PrivoraProvider({ children }: { children: ReactNode }) {
  const [privora, setPrivora] = useState<Privora | null>(null);

  useEffect(() => {
    Privora.connect(process.env.NEXT_PUBLIC_SEQUENCER_URL!)
      .then(setPrivora)
      .catch(console.error);
  }, []);

  return (
    <PrivoraContext.Provider value={privora}>
      {children}
    </PrivoraContext.Provider>
  );
}

export function usePrivora() {
  const privora = useContext(PrivoraContext);
  if (!privora) throw new Error('Privora not initialized');
  return privora;
}

Using with Wallet Adapter

import { useWallet } from '@solana/wallet-adapter-react';
import { usePrivora } from './PrivoraProvider';

function SubmitOrderForm() {
  const privora = usePrivora();
  const { publicKey, signTransaction } = useWallet();
  const [price, setPrice] = useState('');
  const [quantity, setQuantity] = useState('');

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    if (!publicKey) return;

    // Encrypt values
    const encPrice = privora.encrypt(Number(price), 'u8');
    const encQty = privora.encrypt(Number(quantity), 'u8');

    // Submit to sequencer
    const priceHash = await privora.submit(encPrice);
    const qtyHash = await privora.submit(encQty);

    // Build and send transaction
    const tx = await privora
      .transaction()
      .add(buildSubmitOrderInstruction(priceHash, qtyHash))
      .withFheData([priceHash, qtyHash])
      .build();

    const signed = await signTransaction!(tx);
    const signature = await privora.sendTransaction(signed);

    console.log('Order submitted:', signature);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input value={price} onChange={e => setPrice(e.target.value)} placeholder="Price" />
      <input value={quantity} onChange={e => setQuantity(e.target.value)} placeholder="Quantity" />
      <button type="submit">Submit Order</button>
    </form>
  );
}

Local Recovery Display

Show users their encrypted values:
import { UserCrypto } from '@privora/sdk';

function OrderDisplay({ order, keypair }) {
  const [price, setPrice] = useState<number | null>(null);
  const [quantity, setQuantity] = useState<number | null>(null);

  useEffect(() => {
    if (order.priceRecovery && order.qtyRecovery) {
      const userCrypto = new UserCrypto(keypair);

      const priceBytes = userCrypto.decryptRecovery(order.priceRecovery);
      setPrice(new DataView(priceBytes.buffer).getUint8(0));

      const qtyBytes = userCrypto.decryptRecovery(order.qtyRecovery);
      setQuantity(new DataView(qtyBytes.buffer).getUint8(0));
    }
  }, [order, keypair]);

  return (
    <div>
      <p>Price: {price ?? 'Encrypted'}</p>
      <p>Quantity: {quantity ?? 'Encrypted'}</p>
    </div>
  );
}

Error Handling

async function submitWithRetry(privora: Privora, encrypted: any, retries = 3) {
  for (let i = 0; i < retries; i++) {
    try {
      return await privora.submit(encrypted);
    } catch (error) {
      if (i === retries - 1) throw error;
      await new Promise(r => setTimeout(r, 1000 * (i + 1)));
    }
  }
}

TypeScript Types

interface Order {
  owner: string;
  priceHash: string;
  qtyHash: string;
  priceRecovery?: {
    ciphertext: Uint8Array;
    nonce: Uint8Array;
  };
  qtyRecovery?: {
    ciphertext: Uint8Array;
    nonce: Uint8Array;
  };
  side: 'buy' | 'sell';
  status: 'open' | 'matched' | 'cancelled';
}

interface EncryptedValue {
  hash: string;
  recovery?: {
    ciphertext: Uint8Array;
    nonce: Uint8Array;
  };
}

Best Practices

  1. Initialize once: Create Privora instance at app startup
  2. Store recovery data: Save user recovery data for later viewing
  3. Handle loading states: FHE operations can take time
  4. Validate inputs: Check value ranges before encryption
  5. Secure storage: Encrypt recovery data at rest