Migrating from cofhejs
@cofhe/sdk is the successor to cofhejs, redesigned around an explicit, builder-pattern API that gives you full control over encryption, decryption, and permit management. Migration should be strightforward with a 1-to-1 mapping of functions and features.
Why migrate?
- Explicit API — no more implicit initialization or auto-generated permits. Every action is opt-in.
- Builder pattern —
encryptInputs,decryptForView, anddecryptForTxuse a chainable builder so you can set overrides (account, chain, callbacks) before calling.execute(). decryptForTxfeature —cofhejsdoes not provide an api for generating decryption signatures for on-chain usage.- Deferred key loading — FHE keys and TFHE WASM are fetched lazily on the first
encryptInputscall, not during initialization. - Better multichain support — configure multiple chains up front and override per-call.
- Structured errors — typed
CofheErrorobjects with error codes replace theResultwrapper.
Requirements
- Node.js 18+
- TypeScript 5+
- Viem 2+
Installation
Remove cofhejs and install @cofhe/sdk:
npm uninstall cofhejs && npm install @cofhe/sdk1. Initialization
The single cofhejs.initializeWithEthers(...) / cofhejs.initializeWithViem(...) call is replaced by a three-step flow: create a config, create a client, then connect. FHE keys and WASM are no longer fetched eagerly during init — they are deferred until the first encryptInputs call.
Before (cofhejs)
cofhejs uses a global singleton that is initialized eagerly — keys are fetched, the WASM module is loaded, and the provider/signer are stored all in one call:
import { cofhejs } from 'cofhejs/node';
await cofhejs.initializeWithEthers({
ethersProvider: provider,
ethersSigner: signer,
environment: 'TESTNET',
});After (@cofhe/sdk)
@cofhe/sdk splits initialization into three steps: config → client → connect. No keys are fetched until you actually encrypt.
import { createCofheConfig, createCofheClient } from '@cofhe/sdk/web';
import { chains } from '@cofhe/sdk/chains';
const config = createCofheConfig({
supportedChains: [chains.sepolia],
});
const client = createCofheClient(config);
await client.connect(publicClient, walletClient);2. Encrypting inputs
cofhejs.encrypt(...) is replaced by a builder: client.encryptInputs([...]).execute(). The builder lets you chain optional overrides (account, chain, worker, progress callback) before executing.
Before (cofhejs)
import { cofhejs, Encryptable } from 'cofhejs/node';
const result = await cofhejs.encrypt(
[Encryptable.uint64(42n), Encryptable.bool(true)],
(state) => console.log(state)
);
if (!result.success) {
console.error(result.error);
return;
}
const [eAmount, eFlag] = result.data;After (@cofhe/sdk)
import { Encryptable, EncryptStep } from '@cofhe/sdk';
const [eAmount, eFlag] = await client
.encryptInputs([Encryptable.uint64(42n), Encryptable.bool(true)])
.onStep((step, ctx) => {
if (ctx?.isStart) console.log(`Starting: ${step}`);
})
.execute();3. Decrypting / Unsealing
cofhejs has a single unseal function. @cofhe/sdk splits decryption into two purpose-built methods:
decryptForView— returns the plaintext for UI display (no on-chain signature).decryptForTx— returns the plaintext and a Threshold Network signature for on-chain verification.
Before (cofhejs)
import { cofhejs, FheTypes } from 'cofhejs/node';
const sealedBalance = await contract.getBalance();
const result = await cofhejs.unseal(sealedBalance, FheTypes.Uint64);
if (!result.success) {
console.error(result.error);
return;
}
console.log(result.data); // bigintAfter (@cofhe/sdk) — viewing in UI
import { FheTypes } from '@cofhe/sdk';
const ctHash = await contract.getBalance();
const balance = await client
.decryptForView(ctHash, FheTypes.Uint64)
.execute();After (@cofhe/sdk) — publishing on-chain
const ctHash = await myContract.getEncryptedAmount();
const { decryptedValue, signature } = await client
.decryptForTx(ctHash)
.withoutPermit()
.execute();
await myContract.publishDecryptResult(ctHash, decryptedValue, signature);4. Permits
Permits are no longer auto-generated during initialization. All permit operations are now explicit through client.permits, with separate methods for self permits, sharing permits, and importing shared permits.
Before (cofhejs)
Permits could be auto-generated during initialization (generatePermit: true, the default), or created manually:
// Auto-generated during init (default)
await cofhejs.initializeWithEthers({
ethersProvider: provider,
ethersSigner: signer,
environment: 'TESTNET',
// generatePermit: true ← default
});
// Manual creation
const result = await cofhejs.createPermit({
type: 'self',
issuer: wallet.address,
});
if (!result.success) {
console.error(result.error);
return;
}After (@cofhe/sdk)
Permits are never auto-generated. You explicitly create, retrieve, and select them through client.permits:
// Create a self permit (prompts for wallet signature)
const permit = await client.permits.createSelf({
issuer: account,
name: 'My dApp permit',
});
// Or use the convenience method that creates one only if needed
const permit2 = await client.permits.getOrCreateSelfPermit();
// Use with decryptForView (active permit is used automatically)
const value = await client
.decryptForView(ctHash, FheTypes.Uint32)
.execute();
// Or pass a specific permit
const value2 = await client
.decryptForView(ctHash, FheTypes.Uint32)
.withPermit(permit)
.execute();5. Error handling
Before (cofhejs)
All async operations return a Result<T> wrapper:
const result = await cofhejs.encrypt([Encryptable.uint32(42n)]);
if (!result.success) {
console.error('Failed:', result.error); // string
return;
}
const encrypted = result.data;After (@cofhe/sdk)
Operations return values directly and throw typed CofheError objects on failure:
import { isCofheError, CofheErrorCode } from '@cofhe/sdk';
try {
const encrypted = await client
.encryptInputs([Encryptable.uint32(42n)])
.execute();
} catch (err) {
if (isCofheError(err)) {
console.error(err.code); // CofheErrorCode enum
console.error(err.message); // human-readable message
}
}6. Import path changes
cofhejs | @cofhe/sdk |
|---|---|
cofhejs/node | @cofhe/sdk/node |
cofhejs/web | @cofhe/sdk/web |
| N/A | @cofhe/sdk (core types, Encryptable, FheTypes) |
| N/A | @cofhe/sdk/permits |
| N/A | @cofhe/sdk/adapters |
| N/A | @cofhe/sdk/chains |
7. Type renames
cofhejs | @cofhe/sdk |
|---|---|
CoFheInItem | EncryptedItemInput |
CoFheInBool | EncryptedBoolInput |
CoFheInUint8 | EncryptedUint8Input |
CoFheInUint16 | EncryptedUint16Input |
CoFheInUint32 | EncryptedUint32Input |
CoFheInUint64 | EncryptedUint64Input |
CoFheInUint128 | EncryptedUint128Input |
CoFheInAddress | EncryptedAddressInput |
Quick-reference: full before & after
import { cofhejs, Encryptable, FheTypes } from 'cofhejs/node';
import { ethers } from 'ethers';
// 1. Initialize
const provider = new ethers.JsonRpcProvider('https://rpc.sepolia.org');
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
await cofhejs.initializeWithEthers({
ethersProvider: provider,
ethersSigner: wallet,
environment: 'TESTNET',
});
// 2. Encrypt
const encrypted = await cofhejs.encrypt(
[Encryptable.uint64(100n)],
(state) => console.log(state)
);
if (!encrypted.success) throw new Error(encrypted.error);
// 3. Send transaction
await contract.deposit(encrypted.data[0]);
// 4. Create permit & unseal
const permit = await cofhejs.createPermit({
type: 'self',
issuer: wallet.address,
});
if (!permit.success) throw new Error(permit.error);
const sealed = await contract.getBalance();
const balance = await cofhejs.unseal(sealed, FheTypes.Uint64);
if (!balance.success) throw new Error(balance.error);
console.log(balance.data);