React Integration
Setup
Copy
npm install @privora/sdk @solana/web3.js @solana/wallet-adapter-react
Privora Provider
Copy
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
Copy
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:Copy
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
Copy
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
Copy
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
- Initialize once: Create Privora instance at app startup
- Store recovery data: Save user recovery data for later viewing
- Handle loading states: FHE operations can take time
- Validate inputs: Check value ranges before encryption
- Secure storage: Encrypt recovery data at rest