cofhejs Integration
Guide to using cofhejs for client-side FHE operations with Lunarys Protocol.
Overview
cofhejs is the JavaScript/TypeScript library for interacting with Fhenix CoFHE. It provides:
- Client-side encryption of values
- Decryption of permitted values
- Type-safe encrypted value handling
- Integration with ethers.js and viem
Installation
npm install cofhejs
Basic Setup
Create Client
import { createCofheClient } from 'cofhejs'
const cofhe = await createCofheClient({
rpcUrl: 'https://ethereum-sepolia-rpc.publicnode.com',
})
With Provider
import { createCofheClient } from 'cofhejs'
import { BrowserProvider } from 'ethers'
const provider = new BrowserProvider(window.ethereum)
const signer = await provider.getSigner()
const cofhe = await createCofheClient({
provider,
signer,
})
Encryption
Encrypt Values
import { FheType } from 'cofhejs'
// Encrypt different types
const uint8Value = await cofhe.encrypt(255, FheType.Uint8)
const uint16Value = await cofhe.encrypt(65535, FheType.Uint16)
const uint32Value = await cofhe.encrypt(4294967295n, FheType.Uint32)
const uint64Value = await cofhe.encrypt(BigInt(10) ** BigInt(18), FheType.Uint64)
const uint128Value = await cofhe.encrypt(BigInt(10) ** BigInt(30), FheType.Uint128)
FHE Types
| Type | Range | Use Case |
|---|---|---|
| Uint8 | 0 - 255 | Small values, flags |
| Uint16 | 0 - 65,535 | Medium values |
| Uint32 | 0 - 4.29B | Fees, timestamps |
| Uint64 | 0 - 18.4 quintillion | Token amounts |
| Uint128 | 0 - 3.4e38 | Large amounts, accumulators |
| Bool | true/false | Conditional logic |
Encryption Result
The encrypt function returns an InEuint* struct suitable for contract calls:
interface InEuint64 {
data: Uint8Array // Encrypted ciphertext
}
Working with Contracts
Swap Example
import { FheType } from 'cofhejs'
import { parseEther } from 'viem'
async function executeSwap(
poolContract: Contract,
amount: string,
recipient: string
) {
// Convert to wei
const amountWei = parseEther(amount)
// Encrypt for the pool
const encryptedAmount = await cofhe.encrypt(
amountWei,
FheType.Uint64
)
// Execute swap
const tx = await poolContract.swapExactAForB(
encryptedAmount,
recipient,
'0x' // hook context
)
return tx
}
Add Liquidity Example
async function addLiquidity(
poolContract: Contract,
amountA: string,
amountB: string
) {
const encAmountA = await cofhe.encrypt(
parseEther(amountA),
FheType.Uint64
)
const encAmountB = await cofhe.encrypt(
parseEther(amountB),
FheType.Uint64
)
return poolContract.contributeLiquidity(encAmountA, encAmountB)
}
Decryption
Request Decryption
Decryption is asynchronous and requires:
- Permission to decrypt
- A decryption request
- Waiting for the result
// Request decryption through contract
await tokenContract.requestBalanceDecryption()
// Poll for result
async function pollDecryption(tokenContract, address) {
while (true) {
const [balance, ready] = await tokenContract.getDecryptedBalance(address)
if (ready) {
return balance
}
await sleep(2000) // Wait 2 seconds
}
}
Using Decryption Results
async function getDecryptedBalance(
tokenContract: Contract,
address: string
): Promise<bigint | null> {
try {
// Request decryption
await tokenContract.requestBalanceDecryption()
// Wait for result (with timeout)
const timeout = 30000 // 30 seconds
const start = Date.now()
while (Date.now() - start < timeout) {
const [balance, ready] = await tokenContract.getDecryptedBalance(address)
if (ready) {
return balance
}
await new Promise(r => setTimeout(r, 2000))
}
throw new Error('Decryption timeout')
} catch (error) {
console.error('Decryption failed:', error)
return null
}
}
Type Conversion
BigInt to Encrypted
// From number
const fromNumber = await cofhe.encrypt(1000, FheType.Uint64)
// From BigInt
const fromBigInt = await cofhe.encrypt(BigInt('1000000000000000000'), FheType.Uint64)
// From string (via parseEther)
const fromString = await cofhe.encrypt(
parseEther('1.5'),
FheType.Uint64
)
Handling Large Values
// For amounts > Number.MAX_SAFE_INTEGER, use BigInt
const largeAmount = BigInt('100000000000000000000000') // 100,000 tokens
// Encrypt as Uint64 for token amounts
const encrypted = await cofhe.encrypt(largeAmount, FheType.Uint64)
Error Handling
Common Errors
async function safeEncrypt(value: bigint, type: FheType) {
try {
return await cofhe.encrypt(value, type)
} catch (error) {
if (error.message.includes('overflow')) {
throw new Error(`Value ${value} is too large for ${type}`)
}
if (error.message.includes('negative')) {
throw new Error('FHE types are unsigned, negative values not allowed')
}
throw error
}
}
Validation
function validateForEncryption(value: bigint, type: FheType): boolean {
const maxValues: Record<FheType, bigint> = {
[FheType.Uint8]: BigInt(255),
[FheType.Uint16]: BigInt(65535),
[FheType.Uint32]: BigInt(4294967295),
[FheType.Uint64]: BigInt('18446744073709551615'),
[FheType.Uint128]: BigInt('340282366920938463463374607431768211455'),
[FheType.Bool]: BigInt(1),
}
if (value < 0n) return false
if (value > maxValues[type]) return false
return true
}
React Hooks
useEncrypt Hook
import { useState, useCallback } from 'react'
import { FheType } from 'cofhejs'
function useEncrypt(cofheClient) {
const [isEncrypting, setIsEncrypting] = useState(false)
const [error, setError] = useState<Error | null>(null)
const encrypt = useCallback(async (value: bigint, type: FheType) => {
setIsEncrypting(true)
setError(null)
try {
const result = await cofheClient.encrypt(value, type)
return result
} catch (e) {
setError(e as Error)
throw e
} finally {
setIsEncrypting(false)
}
}, [cofheClient])
return { encrypt, isEncrypting, error }
}
useCofhe Provider
import { createContext, useContext, useState, useEffect } from 'react'
import { createCofheClient } from 'cofhejs'
const CofheContext = createContext(null)
export function CofheProvider({ children, rpcUrl }) {
const [client, setClient] = useState(null)
useEffect(() => {
createCofheClient({ rpcUrl }).then(setClient)
}, [rpcUrl])
return (
<CofheContext.Provider value={client}>
{children}
</CofheContext.Provider>
)
}
export function useCofhe() {
return useContext(CofheContext)
}
Best Practices
1. Reuse Client Instance
// Create once at app initialization
const cofhe = await createCofheClient({ rpcUrl })
// Reuse throughout the application
export { cofhe }
2. Pre-encrypt When Possible
// Encrypt before user clicks submit
const [pendingEncryption, setPendingEncryption] = useState(null)
useEffect(() => {
if (inputAmount && !isNaN(parseFloat(inputAmount))) {
cofhe.encrypt(parseEther(inputAmount), FheType.Uint64)
.then(setPendingEncryption)
.catch(console.error)
}
}, [inputAmount])
3. Handle Network Changes
useEffect(() => {
// Reinitialize client on network change
const handleChainChanged = async () => {
const newClient = await createCofheClient({
rpcUrl: getRpcUrl(chainId)
})
setClient(newClient)
}
window.ethereum?.on('chainChanged', handleChainChanged)
return () => {
window.ethereum?.removeListener('chainChanged', handleChainChanged)
}
}, [chainId])
4. Cache Decryption Results
const decryptionCache = new Map<string, bigint>()
async function getDecryptedValue(handle: string): Promise<bigint> {
if (decryptionCache.has(handle)) {
return decryptionCache.get(handle)!
}
const value = await requestAndWaitForDecryption(handle)
decryptionCache.set(handle, value)
return value
}