Observed since 20 Oct, 22:43 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Framework-agnostic FHEVM SDK core library
Framework-agnostic FHEVM SDK core library for building privacy-preserving applications with Fully Homomorphic Encryption.
@fhevm/mock-utilsnpm install @fhevm/core ethers
# or
pnpm add @fhevm/core ethers
# or
yarn add @fhevm/core ethers
For local development with Hardhat:
npm install -D @fhevm/mock-utils
import { FhevmClient } from '@fhevm/core';
import { BrowserProvider } from 'ethers';
// Create client
const client = new FhevmClient();
// Initialize with provider
const provider = new BrowserProvider(window.ethereum);
await client.init({ provider });
// Encrypt data
const encrypted = await client.encrypt({
value: 42,
type: 'euint32',
contractAddress: '0x...',
userAddress: '0x...',
});
// Decrypt data (requires signer)
const signer = await provider.getSigner();
const decrypted = await client.decrypt(
[{ handle: '0x...', contractAddress: '0x...' }],
signer
);
import { FhevmClient, IndexedDBStorage } from '@fhevm/core';
const client = new FhevmClient({
storage: new IndexedDBStorage(), // Persistent browser storage
});
import { FhevmClient } from '@fhevm/core';
const client = new FhevmClient({
mockChains: {
31337: 'http://localhost:8545', // Hardhat local network
},
});
await client.init({
provider: 'http://localhost:8545',
chainId: 31337,
});
new FhevmClient(config?: FhevmConfig)
Config Options:
storage?: StorageAdapter - Custom storage implementation (default: MemoryStorage)mockChains?: Record<number, string> - Mock chain configurations for local developmentinit(params: InitParams, signal?: AbortSignal): Promise<FhevmInstance>Initialize the FHEVM instance.
Parameters:
params.provider: Eip1193Provider | string - Ethereum provider or RPC URLparams.chainId?: number - Chain ID (auto-detected if not provided)signal?: AbortSignal - Optional abort signal for cancellationReturns: Promise
getStatus(): FhevmStatusGet current initialization status.
Returns: 'idle' | 'loading' | 'ready' | 'error'
onStatusChange(listener: (status: FhevmStatus) => void): () => voidSubscribe to status changes.
Returns: Unsubscribe function
getPublicKey(): stringGet the public key for encryption.
Returns: Public key string
encrypt(params: EncryptParams): Promise<EncryptResult>Encrypt data for on-chain computation.
Parameters:
value: number | bigint | boolean | string - Value to encrypttype: EncryptedType - Type of encrypted valuecontractAddress: 0x${string} - Contract addressuserAddress: 0x${string} - User addressSupported Types:
ebool - Encrypted booleaneuint8 - Encrypted 8-bit unsigned integereuint16 - Encrypted 16-bit unsigned integereuint32 - Encrypted 32-bit unsigned integereuint64 - Encrypted 64-bit unsigned integereuint128 - Encrypted 128-bit unsigned integereuint160 - Encrypted 160-bit unsigned integereuint256 - Encrypted 256-bit unsigned integereaddress - Encrypted addressReturns: { handles: Uint8Array[], inputProof: Uint8Array }
decrypt(requests: DecryptRequest[], signer: JsonRpcSigner): Promise<DecryptResult>Decrypt encrypted data (requires user signature).
Parameters:
requests - Array of decrypt requestssigner - ethers.JsonRpcSigner for EIP-712 signatureReturns: Decrypted values as Record<string, string | bigint | boolean>
In-memory storage, data persists only for the lifetime of the instance.
import { MemoryStorage } from '@fhevm/core';
const storage = new MemoryStorage();
Browser localStorage with prefix support.
import { LocalStorageStorage } from '@fhevm/core';
const storage = new LocalStorageStorage('my-app:'); // Optional prefix
Browser IndexedDB for larger storage capacity.
import { IndexedDBStorage } from '@fhevm/core';
const storage = new IndexedDBStorage();
Implement the StorageAdapter interface:
interface StorageAdapter {
getItem(key: string): Promise<string | null>;
setItem(key: string, value: string): Promise<void>;
removeItem(key: string): Promise<void>;
clear(): Promise<void>;
}
import { FhevmError, FhevmAbortError } from '@fhevm/core';
try {
await client.init({ provider });
} catch (error) {
if (error instanceof FhevmAbortError) {
console.log('Operation was cancelled');
} else if (error instanceof FhevmError) {
console.error('FHEVM Error:', error.code, error.message);
}
}
Full TypeScript support with comprehensive type definitions.
import type {
FhevmConfig,
FhevmStatus,
InitParams,
EncryptParams,
EncryptResult,
DecryptRequest,
DecryptResult,
StorageAdapter,
} from '@fhevm/core';
const controller = new AbortController();
// Start initialization
const initPromise = client.init({ provider }, controller.signal);
// Cancel if needed
controller.abort();
try {
await initPromise;
} catch (error) {
if (error instanceof FhevmAbortError) {
console.log('Initialization cancelled');
}
}
const unsubscribe = client.onStatusChange((status) => {
console.log('Status:', status);
// idle -> loading -> ready
});
await client.init({ provider });
unsubscribe();
const results = await client.decrypt(
[
{ handle: '0x...', contractAddress: '0xContract1...' },
{ handle: '0x...', contractAddress: '0xContract2...' },
],
signer
);
// Encrypt multiple values for the same contract
const client = new FhevmClient();
await client.init({ provider });
const contractAddress = '0x...';
const userAddress = '0x...';
// Encrypt multiple values
const age = await client.encrypt({
value: 25,
type: 'euint8',
contractAddress,
userAddress,
});
const balance = await client.encrypt({
value: 1000n,
type: 'euint64',
contractAddress,
userAddress,
});
const isActive = await client.encrypt({
value: true,
type: 'ebool',
contractAddress,
userAddress,
});
// Use in contract call
await contract.setUserData(
age.handles[0],
balance.handles[0],
isActive.handles[0],
age.inputProof // All values share the same proof
);
import { FhevmClient, FhevmError, FhevmAbortError } from '@fhevm/core';
try {
await client.encrypt({ value: 42, type: 'euint32', contractAddress, userAddress });
} catch (error) {
if (error instanceof FhevmAbortError) {
console.log('Operation cancelled by user');
} else if (error instanceof FhevmError) {
switch (error.code) {
case 'NOT_INITIALIZED':
console.error('Client not initialized - call init() first');
break;
case 'INVALID_TYPE':
console.error('Invalid encrypted type provided');
break;
case 'BROWSER_REQUIRED':
console.error('Browser environment required for production chains');
break;
default:
console.error(`FHEVM Error (${error.code}):`, error.message);
}
} else {
console.error('Unexpected error:', error);
}
}
import { FhevmClient, IndexedDBStorage } from '@fhevm/core';
import { BrowserProvider } from 'ethers';
// Production configuration
const client = new FhevmClient({
storage: new IndexedDBStorage(), // Persistent storage
mockChains: {}, // Disable mock chains in production
});
// Initialize with MetaMask/WalletConnect
const provider = new BrowserProvider(window.ethereum);
await client.init({ provider });
// Subscribe to status changes for UI feedback
client.onStatusChange((status) => {
switch (status) {
case 'loading':
showLoadingSpinner();
break;
case 'ready':
hideLoadingSpinner();
enableEncryptButton();
break;
case 'error':
hideLoadingSpinner();
showErrorMessage();
break;
}
});
Choose the smallest encrypted type that fits your data to minimize gas costs:
// ❌ BAD: Using oversized type
const age = await client.encrypt({
value: 25, // 0-255 range
type: 'euint256', // Wastes gas
contractAddress,
userAddress,
});
// ✅ GOOD: Using appropriate type
const age = await client.encrypt({
value: 25,
type: 'euint8', // Perfect fit for 0-255 range
contractAddress,
userAddress,
});
| Storage Type | Use Case | Persistence | Capacity | Performance |
|---|---|---|---|---|
| MemoryStorage | Testing, ephemeral sessions | Session only | Low (~5MB) | Fastest |
| LocalStorageStorage | Simple apps, small data | Browser restart | ~10MB | Fast |
| IndexedDBStorage | Production apps | Browser restart | ~50MB+ | Good |
Recommendation: Use IndexedDBStorage for production to cache public keys and signatures persistently.
Decryption signatures are valid for 365 days and automatically cached:
// First decrypt - prompts user to sign
const result1 = await client.decrypt([{ handle: '0x...', contractAddress }], signer);
// Subsequent decrypts within 365 days - no signature prompt!
const result2 = await client.decrypt([{ handle: '0x...', contractAddress }], signer);
const result3 = await client.decrypt([{ handle: '0x...', contractAddress }], signer);
Impact: Reduces user friction by 99% after initial signature.
Public keys are automatically cached using PublicKeyStorage:
Recommendation: Always use persistent storage (IndexedDB) to maximize cache hit rate across sessions.
Use AbortController to cancel initialization if user navigates away:
const controller = new AbortController();
// Cancel on component unmount
useEffect(() => {
const initPromise = client.init({ provider }, controller.signal);
return () => controller.abort();
}, []);
Symptom: Transactions fail with "nonce too high" or "nonce too low" after restarting local Hardhat node.
Solution:
http://localhost:8545pnpm contract:deploySymptom: FhevmError: Public key not found during decryption.
Causes & Solutions:
await client.init({ provider }); // Refreshes public key
// User will be prompted to sign again
await client.decrypt([...], signer);
// Generate new signature for new contract
await client.decrypt(
[{ handle: '0x...', contractAddress: '0xNEW_CONTRACT' }],
signer
);
Symptom: Cannot find module '@fhevm/mock-utils' when using Hardhat (chainId 31337).
Solution: Install as dev dependency:
npm install -D @fhevm/mock-utils
# or
pnpm add -D @fhevm/mock-utils
Symptom: FhevmError: BROWSER_REQUIRED when running on server.
Explanation: Production chains (Sepolia, mainnet) require browser environment to load Relayer SDK.
Solutions:
'use client') for FHEVM operationsuseEffect (client-side only)'use client'; // Next.js client component
import { useEffect } from 'react';
import { FhevmClient } from '@fhevm/core';
export function EncryptForm() {
useEffect(() => {
// Client-side only
const client = new FhevmClient();
client.init({ provider: window.ethereum });
}, []);
}
Symptom: Client initializes but encryption fails.
Cause: Provider and config chainId don't match.
Solution: Let the SDK auto-detect chainId:
// ❌ BAD: Manual chainId may mismatch
await client.init({ provider: window.ethereum, chainId: 11155111 });
// ✅ GOOD: Auto-detect from provider
await client.init({ provider: window.ethereum });
Symptom: init() takes >5 seconds on subsequent loads.
Causes:
MemoryStorage (default)
IndexedDBStorage// Enable persistent caching
const client = new FhevmClient({
storage: new IndexedDBStorage(),
});
// Verify caching works
console.time('First init');
await client.init({ provider });
console.timeEnd('First init'); // ~2000ms
console.time('Second init');
await client.init({ provider });
console.timeEnd('Second init'); // ~50ms (cached!)
Before (fhevmjs):
import { createInstance } from 'fhevmjs';
const instance = await createInstance({
network: window.ethereum,
aclContractAddress: '0x...',
kmsVerifierContractAddress: '0x...',
inputVerifierContractAddress: '0x...',
});
const input = instance.createEncryptedInput('0xContract', '0xUser');
input.add32(42);
const encrypted = await input.encrypt();
After (@fhevm/core):
import { FhevmClient } from '@fhevm/core';
const client = new FhevmClient();
await client.init({ provider: window.ethereum });
// ACL addresses auto-detected from Relayer SDK config
const encrypted = await client.encrypt({
value: 42,
type: 'euint32',
contractAddress: '0xContract',
userAddress: '0xUser',
});
Key Changes:
init(), getStatus())EncryptedType enumFhevmError, FhevmAbortError)See full working examples in our monorepo:
Live Demos:
BSD-3-Clause-Clear
Download counts and popularity will appear here as the collector receives them.
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 22:43 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:aca664fcf626d4ee1dfd10729d0396c2e43b62fe75a4af5e9e8d92267334ec09