Skip to main content
The @privora/react package provides React hooks and components for building FHE-enabled applications with Privora.

Installation

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

Quick Start

import { PrivoraProvider, usePrivoraContext, useEncrypt } from '@privora/react';
import { useWallet } from '@solana/wallet-adapter-react';

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

  return (
    <PrivoraProvider
      rpcUrl="http://localhost:8899"
      autoInitialize
      signMessage={signMessage}
    >
      <MyComponent />
    </PrivoraProvider>
  );
}

function MyComponent() {
  const { privora, userCrypto, isInitialized } = usePrivoraContext();
  const { encryptAndSubmit, isEncrypting } = useEncrypt(privora, userCrypto);

  const handleSubmit = async (value: number) => {
    const result = await encryptAndSubmit(value, 'u8');
    if (result) {
      console.log('Submitted hash:', result.hash);
    }
  };

  if (!isInitialized) return <div>Initializing...</div>;

  return <button onClick={() => handleSubmit(100)}>Submit</button>;
}

PrivoraProvider

The PrivoraProvider component wraps your application to provide Privora state to all child components.

Props

PropTypeRequiredDescription
rpcUrlstringYesThe Privora sequencer RPC URL
signatureMessagestringNoCustom message for deriving encryption keys
autoInitializebooleanNoAuto-initialize when signMessage is available
signMessageSignMessageFn | nullNoWallet adapter signMessage function
childrenReactNodeYesChild components

Basic Usage

// Manual initialization
function App() {
  return (
    <PrivoraProvider rpcUrl="http://localhost:8899">
      <MyApp />
    </PrivoraProvider>
  );
}

Auto-initialization

// Auto-initialize with wallet adapter
function App() {
  const { signMessage, connected } = useWallet();

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

Hooks

usePrivora

Standalone hook for initializing and managing Privora state without using the context provider.
import { usePrivora } from '@privora/react';

function MyComponent() {
  const { publicKey, signMessage } = useWallet();
  const {
    privora,
    userCrypto,
    userKeypair,
    isInitialized,
    isInitializing,
    error,
    initialize,
    reset
  } = usePrivora({
    rpcUrl: 'http://localhost:8899',
  });

  useEffect(() => {
    if (publicKey && signMessage && !isInitialized && !isInitializing) {
      initialize(signMessage);
    }
  }, [publicKey, signMessage, isInitialized, isInitializing]);

  // Use privora and userCrypto...
}

usePrivoraContext

Access the Privora context when using PrivoraProvider.
import { usePrivoraContext } from '@privora/react';

function MyComponent() {
  const {
    privora,
    userCrypto,
    isInitialized,
    initialize,
    reset
  } = usePrivoraContext();

  if (!isInitialized) {
    return <button onClick={() => initialize(signMessage)}>Connect</button>;
  }

  return <div>Connected!</div>;
}

useEncrypt

Hook for encrypting values with FHE and user recovery.
import { useEncrypt } from '@privora/react';

function OrderForm({ privora, userCrypto }) {
  const {
    encrypt,
    encryptAndSubmit,
    submit,
    isEncrypting,
    isSubmitting,
    error,
    clearError
  } = useEncrypt(privora, userCrypto);

  const handleSubmit = async (price: number) => {
    // Option 1: Encrypt and submit in one step
    const result = await encryptAndSubmit(price, 'u8');
    if (result) {
      console.log('Hash:', result.hash);
      // Store result.userCiphertext and result.userNonce for later decryption
    }

    // Option 2: Encrypt separately, then submit
    const encrypted = await encrypt(price, 'u8');
    if (encrypted) {
      const hash = await submit(encrypted.encrypted);
    }
  };

  return (
    <div>
      {error && <div className="error">{error}</div>}
      <button onClick={() => handleSubmit(100)} disabled={isEncrypting || isSubmitting}>
        {isEncrypting ? 'Encrypting...' : isSubmitting ? 'Submitting...' : 'Submit'}
      </button>
    </div>
  );
}

useDecryptReveal

Hook for decrypting and revealing user-encrypted values with a show/hide toggle.
import { useDecryptReveal } from '@privora/react';

function OrderCard({ order, userCrypto }) {
  const priceField = {
    ciphertext: order.userPriceCiphertext,
    nonce: order.userPriceNonce,
    dataType: 'u8' as const,
  };

  const [priceState, { reveal, hide, toggle }] = useDecryptReveal(priceField, userCrypto);

  return (
    <div>
      <span>
        Price: {priceState.revealed ? priceState.value : '****'}
      </span>
      <button onClick={toggle} disabled={priceState.isDecrypting}>
        {priceState.isDecrypting ? 'Decrypting...' : priceState.revealed ? 'Hide' : 'Reveal'}
      </button>
      {priceState.error && <span className="error">{priceState.error}</span>}
    </div>
  );
}

useDecryptRevealMultiple

Hook for managing multiple encrypted fields at once.
import { useDecryptRevealMultiple } from '@privora/react';

function OrderDetails({ 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>
      <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>
  );
}

Context Utilities

Additional hooks for accessing specific parts of the context:
import {
  usePrivoraInstance,
  useUserCrypto,
  useUserKeypair
} from '@privora/react';

function MyComponent() {
  // Get just the Privora instance (or null if not initialized)
  const privora = usePrivoraInstance();

  // Get just the UserCrypto instance (or null if not initialized)
  const userCrypto = useUserCrypto();

  // Get the derived user keypair (or null if not initialized)
  const userKeypair = useUserKeypair();
}

Complete Example

import {
  PrivoraProvider,
  usePrivoraContext,
  useEncrypt,
  useDecryptReveal
} from '@privora/react';
import {
  ConnectionProvider,
  WalletProvider,
  useWallet
} from '@solana/wallet-adapter-react';

// Wallet setup wrapper
function AppWithWallet() {
  const { signMessage, connected } = useWallet();

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

// Main orderbook component
function OrderBook() {
  const { privora, userCrypto, isInitialized, isInitializing, error } = usePrivoraContext();

  if (error) return <div>Error: {error}</div>;
  if (isInitializing) return <div>Connecting to Privora...</div>;
  if (!isInitialized) return <div>Please connect your wallet</div>;

  return (
    <div>
      <SubmitOrderForm privora={privora} userCrypto={userCrypto} />
      <OrderList userCrypto={userCrypto} />
    </div>
  );
}

// Order submission form
function SubmitOrderForm({ privora, userCrypto }) {
  const [price, setPrice] = useState('');
  const { encryptAndSubmit, isEncrypting, isSubmitting, error } = useEncrypt(privora, userCrypto);

  const handleSubmit = async (e: FormEvent) => {
    e.preventDefault();
    const result = await encryptAndSubmit(Number(price), 'u8');
    if (result) {
      // Save result for later display
      saveOrder({
        hash: result.hash,
        userCiphertext: result.userCiphertext,
        userNonce: result.userNonce,
      });
      setPrice('');
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        value={price}
        onChange={e => setPrice(e.target.value)}
        placeholder="Price (0-255)"
      />
      <button disabled={isEncrypting || isSubmitting}>
        {isEncrypting ? 'Encrypting...' : isSubmitting ? 'Submitting...' : 'Submit'}
      </button>
      {error && <p className="error">{error}</p>}
    </form>
  );
}

// Display order with reveal functionality
function OrderCard({ order, userCrypto }) {
  const priceField = {
    ciphertext: order.userCiphertext,
    nonce: order.userNonce,
    dataType: 'u8' as const,
  };

  const [state, { toggle }] = useDecryptReveal(priceField, userCrypto);

  return (
    <div className="order-card">
      <span>Price: {state.revealed ? state.value : '****'}</span>
      <button onClick={toggle}>
        {state.revealed ? 'Hide' : 'Reveal'}
      </button>
    </div>
  );
}

TypeScript Types

All types are exported from @privora/react:
import type {
  // Hook options and results
  UsePrivoraOptions,
  UsePrivoraResult,
  PrivoraState,
  SignMessageFn,

  // Encryption types
  EncryptedWithRecovery,
  SubmitResult,
  UseEncryptState,
  UseEncryptResult,

  // Decryption types
  EncryptedField,
  RevealState,
  UseDecryptRevealResult,

  // Context types
  PrivoraContextValue,
  PrivoraProviderProps,
} from '@privora/react';

Notes

  • The @privora/react package requires @privora/sdk as a peer dependency
  • For core types like Privora, UserCrypto, Encrypted, and EncryptedType, import directly from @privora/sdk
  • The package uses 'use client' directives for Next.js App Router compatibility