Skip to main content
This guide covers integrating Privora into web and mobile applications using the @privora/react package.

React Integration

Setup

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

Using PrivoraProvider

The recommended approach is to use the @privora/react package which provides ready-to-use hooks and components:
import { PrivoraProvider, usePrivoraContext, useEncrypt } from '@privora/react';
import { useWallet } from '@solana/wallet-adapter-react';

function App() {
  const { signMessage, connected } = useWallet();

  return (
    <PrivoraProvider
      rpcUrl={process.env.NEXT_PUBLIC_SEQUENCER_URL!}
      autoInitialize={connected}
      signMessage={signMessage}
    >
      <MyApp />
    </PrivoraProvider>
  );
}

Using with Wallet Adapter

import { usePrivoraContext, useEncrypt } from '@privora/react';

function SubmitOrderForm() {
  const { privora, userCrypto, isInitialized } = usePrivoraContext();
  const { encryptAndSubmit, isEncrypting, isSubmitting, error } = useEncrypt(privora, userCrypto);
  const { publicKey, signTransaction } = useWallet();
  const [price, setPrice] = useState('');
  const [quantity, setQuantity] = useState('');

  if (!isInitialized) {
    return <div>Connecting to Privora...</div>;
  }

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

    // Encrypt and submit values
    const priceResult = await encryptAndSubmit(Number(price), 'u8');
    const qtyResult = await encryptAndSubmit(Number(quantity), 'u8');

    if (!priceResult || !qtyResult) return;

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

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

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

    // Store recovery data for later display
    saveOrderRecovery({
      priceHash: priceResult.hash,
      priceCiphertext: priceResult.userCiphertext,
      priceNonce: priceResult.userNonce,
      qtyHash: qtyResult.hash,
      qtyCiphertext: qtyResult.userCiphertext,
      qtyNonce: qtyResult.userNonce,
    });
  };

  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" disabled={isEncrypting || isSubmitting}>
        {isEncrypting ? 'Encrypting...' : isSubmitting ? 'Submitting...' : 'Submit Order'}
      </button>
      {error && <p className="error">{error}</p>}
    </form>
  );
}

Local Recovery Display

Show users their encrypted values using the useDecryptReveal hook:
import { useDecryptReveal } from '@privora/react';

function OrderDisplay({ order, userCrypto }) {
  const priceField = {
    ciphertext: order.priceCiphertext,
    nonce: order.priceNonce,
    dataType: 'u8' as const,
  };

  const qtyField = {
    ciphertext: order.qtyCiphertext,
    nonce: order.qtyNonce,
    dataType: 'u8' as const,
  };

  const [priceState, priceActions] = useDecryptReveal(priceField, userCrypto);
  const [qtyState, qtyActions] = useDecryptReveal(qtyField, userCrypto);

  return (
    <div>
      <div>
        <span>Price: {priceState.revealed ? priceState.value : '****'}</span>
        <button onClick={priceActions.toggle}>
          {priceState.revealed ? 'Hide' : 'Reveal'}
        </button>
      </div>
      <div>
        <span>Quantity: {qtyState.revealed ? qtyState.value : '****'}</span>
        <button onClick={qtyActions.toggle}>
          {qtyState.revealed ? 'Hide' : 'Reveal'}
        </button>
      </div>
    </div>
  );
}

Revealing Multiple Fields

For orders with multiple encrypted values, use useDecryptRevealMultiple:
import { useDecryptRevealMultiple } from '@privora/react';

function OrderCard({ order, userCrypto }) {
  const fields = {
    price: { ciphertext: order.priceCiphertext, nonce: order.priceNonce, dataType: 'u8' as const },
    quantity: { ciphertext: order.qtyCiphertext, nonce: order.qtyNonce, dataType: 'u8' as const },
  };

  const { price, quantity, revealAll, hideAll } = useDecryptRevealMultiple(fields, userCrypto);

  return (
    <div className="order-card">
      <p>Price: {price.revealed ? price.value : '****'}</p>
      <p>Quantity: {quantity.revealed ? quantity.value : '****'}</p>
      <button onClick={revealAll}>Reveal All</button>
      <button onClick={hideAll}>Hide All</button>
    </div>
  );
}

Error Handling

import { useEncrypt, usePrivoraContext } from '@privora/react';

function SubmitForm() {
  const { privora, userCrypto, error: initError } = usePrivoraContext();
  const { encryptAndSubmit, error: encryptError, clearError } = useEncrypt(privora, userCrypto);

  const handleSubmit = async (value: number) => {
    clearError(); // Clear previous errors

    const result = await encryptAndSubmit(value, 'u8');
    if (!result) {
      // Error is available in encryptError
      return;
    }

    // Success - continue with transaction
  };

  if (initError) {
    return <div className="error">Failed to initialize: {initError}</div>;
  }

  return (
    <div>
      {encryptError && <div className="error">{encryptError}</div>}
      <button onClick={() => handleSubmit(100)}>Submit</button>
    </div>
  );
}

TypeScript Types

import type {
  EncryptedField,
  SubmitResult,
  RevealState,
} from '@privora/react';

interface Order {
  owner: string;
  priceHash: string;
  qtyHash: string;
  // User recovery data (base64 encoded)
  priceCiphertext: string;
  priceNonce: string;
  qtyCiphertext: string;
  qtyNonce: string;
  side: 'buy' | 'sell';
  status: 'open' | 'matched' | 'cancelled';
}

Best Practices

  1. Use the React package: Prefer @privora/react over manual provider implementations
  2. Auto-initialize with wallet: Use autoInitialize prop with wallet adapter for seamless connection
  3. Store recovery data: Always save userCiphertext and userNonce for later decryption
  4. Handle loading states: Show appropriate UI during encryption/decryption operations
  5. Validate inputs: Check value ranges before encryption (e.g., 0-255 for u8)
  6. Secure storage: Consider encrypting recovery data at rest in local storage

Next Steps

React Integration Reference

Complete React SDK documentation

React Hooks API

Detailed hooks API reference