Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

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 patternencryptInputs, decryptForView, and decryptForTx use a chainable builder so you can set overrides (account, chain, callbacks) before calling .execute().
  • decryptForTx featurecofhejs does 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 encryptInputs call, not during initialization.
  • Better multichain support — configure multiple chains up front and override per-call.
  • Structured errors — typed CofheError objects with error codes replace the Result wrapper.

Requirements

  • Node.js 18+
  • TypeScript 5+
  • Viem 2+

Installation

Remove cofhejs and install @cofhe/sdk:

npm uninstall cofhejs && npm install @cofhe/sdk

1. 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:

Ethers
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.

Web (viem)
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); // bigint

After (@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

TypeScript
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
CoFheInItemEncryptedItemInput
CoFheInBoolEncryptedBoolInput
CoFheInUint8EncryptedUint8Input
CoFheInUint16EncryptedUint16Input
CoFheInUint32EncryptedUint32Input
CoFheInUint64EncryptedUint64Input
CoFheInUint128EncryptedUint128Input
CoFheInAddressEncryptedAddressInput

Quick-reference: full before & after

cofhejs (before)
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);