# End-to-End Example Source: https://cofhe-docs.fhenix.zone/client-sdk/examples/end-to-end A complete encrypt → store → decrypt flow using @cofhe/sdk This example demonstrates the full lifecycle of working with encrypted data: initialize the SDK, encrypt a value, send it to a contract, and decrypt the result — both for UI display and for on-chain verification. ## The contract A simple contract that stores an encrypted `uint64` balance and allows the owner to set and read it. ```solidity contracts/ConfidentialVault.sol theme={null} // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.28; import '@fhenixprotocol/cofhe-contracts/FHE.sol'; contract ConfidentialVault { mapping(address => euint64) private _balances; function deposit(InEuint64 calldata encryptedAmount) external { euint64 amount = FHE.asEuint64(encryptedAmount); _balances[msg.sender] = FHE.add(_balances[msg.sender], amount); FHE.allowThis(_balances[msg.sender]); FHE.allowSender(_balances[msg.sender]); } function getBalance() public view returns (euint64) { return _balances[msg.sender]; } function publishBalance( euint64 ctHash, uint64 plaintext, bytes calldata signature ) external { FHE.publishDecryptResult(ctHash, plaintext, signature); } } ``` ## SDK: full flow ```typescript Web (viem) theme={null} import { createCofheConfig, createCofheClient } from '@cofhe/sdk/web'; import { Encryptable, FheTypes } from '@cofhe/sdk'; import { chains } from '@cofhe/sdk/chains'; import { createPublicClient, createWalletClient, http, custom } from 'viem'; import { sepolia } from 'viem/chains'; // 1. Initialize const config = createCofheConfig({ supportedChains: [chains.sepolia], }); const client = createCofheClient(config); const publicClient = createPublicClient({ chain: sepolia, transport: http(), }); const walletClient = createWalletClient({ chain: sepolia, transport: custom(window.ethereum), }); await client.connect(publicClient, walletClient); // 2. Create a permit await client.permits.getOrCreateSelfPermit(); // 3. Encrypt and deposit const [encryptedAmount] = await client .encryptInputs([Encryptable.uint64(100n)]) .onStep((step, ctx) => { if (ctx?.isStart) console.log(`Encrypting: ${step}...`); }) .execute(); await contract.deposit(encryptedAmount); // 4. Decrypt for UI display const ctHash = await contract.getBalance(); const balance = await client .decryptForView(ctHash, FheTypes.Uint64) .execute(); console.log('Balance:', balance); // 100n // 5. Decrypt for on-chain verification const { decryptedValue, signature } = await client .decryptForTx(ctHash) .withPermit() .execute(); await contract.publishBalance(ctHash, decryptedValue, signature); ``` ```typescript Node (ethers v6) theme={null} import { createCofheConfig, createCofheClient } from '@cofhe/sdk/node'; import { Encryptable, FheTypes } from '@cofhe/sdk'; import { Ethers6Adapter } from '@cofhe/sdk/adapters'; import { chains } from '@cofhe/sdk/chains'; import { ethers } from 'ethers'; // 1. Initialize const provider = new ethers.JsonRpcProvider('https://rpc.sepolia.org'); const wallet = new ethers.Wallet(PRIVATE_KEY, provider); const config = createCofheConfig({ supportedChains: [chains.sepolia], }); const client = createCofheClient(config); const { publicClient, walletClient } = await Ethers6Adapter(provider, wallet); await client.connect(publicClient, walletClient); // 2. Create a permit await client.permits.getOrCreateSelfPermit(); // 3. Encrypt and deposit const [encryptedAmount] = await client .encryptInputs([Encryptable.uint64(100n)]) .execute(); await contract.deposit(encryptedAmount); // 4. Decrypt for UI display const ctHash = await contract.getBalance(); const balance = await client .decryptForView(ctHash, FheTypes.Uint64) .execute(); console.log('Balance:', balance); // 100n // 5. Decrypt for on-chain verification const { decryptedValue, signature } = await client .decryptForTx(ctHash) .withPermit() .execute(); await contract.publishBalance(ctHash, decryptedValue, signature); ``` ```typescript Hardhat test theme={null} import hre from 'hardhat'; import { CofheClient, Encryptable, FheTypes } from '@cofhe/sdk'; import { expect } from 'chai'; describe('ConfidentialVault', () => { let cofheClient: CofheClient; before(async () => { const [signer] = await hre.ethers.getSigners(); cofheClient = await hre.cofhe.createClientWithBatteries(signer); }); it('deposits and reads an encrypted balance', async () => { const Factory = await hre.ethers.getContractFactory('ConfidentialVault'); const vault = await Factory.deploy(); // Encrypt and deposit const [encrypted] = await cofheClient .encryptInputs([Encryptable.uint64(100n)]) .execute(); await (await vault.deposit(encrypted)).wait(); // Decrypt and verify const ctHash = await vault.getBalance(); const balance = await cofheClient .decryptForView(ctHash, FheTypes.Uint64) .execute(); expect(balance).to.equal(100n); }); it('publishes a decrypt result on-chain', async () => { const Factory = await hre.ethers.getContractFactory('ConfidentialVault'); const vault = await Factory.deploy(); // Encrypt and deposit const [encrypted] = await cofheClient .encryptInputs([Encryptable.uint64(42n)]) .execute(); await (await vault.deposit(encrypted)).wait(); // Decrypt for on-chain verification const ctHash = await vault.getBalance(); const { decryptedValue, signature } = await cofheClient .decryptForTx(ctHash) .withPermit() .execute(); // Publish on-chain with Threshold Network proof await (await vault.publishBalance(ctHash, decryptedValue, signature)).wait(); }); }); ``` # Templates & Starters Source: https://cofhe-docs.fhenix.zone/client-sdk/examples/templates Ready-to-use project templates for building with @cofhe/sdk Get started quickly with pre-configured project templates. ## Hardhat Starter A minimal Hardhat project with `@cofhe/hardhat-plugin` pre-configured, including a sample contract and test. Clone this template to start building FHE contracts with Hardhat immediately. ```bash theme={null} git clone https://github.com/FhenixProtocol/cofhe-hardhat-starter.git cd cofhe-hardhat-starter npm install npx hardhat test ``` ### What's included * `@cofhe/hardhat-plugin` and `@cofhe/sdk` pre-installed * `hardhat.config.ts` with `evmVersion: 'cancun'` and the plugin imported * A sample FHE contract * A test demonstrating the encrypt → store → decrypt flow * Pre-configured network settings for local development and testnets ## Foundry Starter A minimal Foundry project with `@cofhe/foundry-plugin` pre-configured, including a sample `Counter` contract and a comprehensive test suite covering plaintext assertions, public-decrypt flows, ACL deny paths, and fuzzing. Clone this template to start building FHE contracts with Foundry immediately. ```bash theme={null} git clone https://github.com/FhenixProtocol/cofhe-foundry-starter.git cd cofhe-foundry-starter npm install forge build forge test -vvv ``` ### What's included * `@cofhe/foundry-plugin`, `@cofhe/mock-contracts`, and `@fhenixprotocol/cofhe-contracts` pre-installed * `foundry.toml` with `evm_version = "cancun"`, `solc_version = "0.8.25"`, and `code_size_limit = 100000` * `remappings.txt` configured for the plugin and mocks * A sample `Counter` contract using FHE * Tests demonstrating `expectPlaintext`, `decryptForTx_withoutPermit`, permit-based unseal, ACL deny assertions, and fuzz tests * Deploy scripts for `eth-sepolia`, `arb-sepolia`, and `base-sepolia` # CofheClient Source: https://cofhe-docs.fhenix.zone/client-sdk/foundry-plugin/cofhe-client The in-Solidity SDK shim — one client per user, produces encrypted inputs and signed permits `CofheClient` is the Foundry plugin's in-Solidity SDK shim. One client per "user" in your scenario. The client carries a private key and produces encrypted inputs / signed permits **as if it were that user's frontend SDK** — no JS bridge required. ## Creating and connecting Spin up a client from inside [`CofheTest`](/client-sdk/foundry-plugin/cofhe-test) with `createCofheClient()`, then bind it to an address with `connect(pkey)`: ```solidity theme={null} CofheClient bob = createCofheClient(); bob.connect(0xB0B); // bob.account() == vm.addr(0xB0B) ``` After `connect`, the client knows which address to sign as. All `createInEuintN` and `permit_*` calls use that account automatically — there's no `account` argument to pass. To act on-chain as that user, prank with `client.account()`: ```solidity theme={null} vm.prank(bob.account()); counter.reset(bob.createInEuint32(2000)); ``` A mismatch between the prank address and the client that produced the input will fail the ZK-verifier signature check — the input was signed for `bob.account()`, not whoever you pranked. Always match the client to the prank. ## Encrypting inputs The client mirrors the JS SDK's `encryptInputs` API — one method per encrypted Solidity type: | Method | Returns | | --------------------------- | ------------ | | `createInEbool(bool)` | `InEbool` | | `createInEuint8(uint8)` | `InEuint8` | | `createInEuint16(uint16)` | `InEuint16` | | `createInEuint32(uint32)` | `InEuint32` | | `createInEuint64(uint64)` | `InEuint64` | | `createInEuint128(uint128)` | `InEuint128` | | `createInEaddress(address)` | `InEaddress` | All produce signed `EncryptedInput` shapes — drop straight into the `InEuintN` parameter on the contract under test. ```solidity theme={null} InEuint32 memory encrypted = bob.createInEuint32(42); vm.prank(bob.account()); counter.reset(encrypted); ``` ## Decrypting The plugin exposes both decryption flows the SDK supports: | Method | Returns | Use for | | ----------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------- | | `decryptForTx_withoutPermit(ctHash)` | `(bytes32 ctHash, uint256 plaintext, bytes signature)` | Globally-allowed (`FHE.allowPublic`) ciphertexts. Pass `signature` to `FHE.publishDecryptResult`. | | `decryptForTx_withPermit(ctHash, permit)` | `(bytes32, uint256, bytes)` | ACL-gated `decryptForTx` flow. | | `decryptForView(ctHash, permit)` | `uint256 plaintext` | Off-chain seal/unseal flow. **Reverts on deny** — use the mock directly to assert deny. | ### `decryptForTx_withoutPermit` — public-decrypt 3-step flow Mirrors the production flow when a contract calls `FHE.publishDecryptResult`: ```solidity theme={null} // Step 1: contract grants public decrypt permission vm.prank(bob.account()); counter.allowCounterPublicly(); // calls FHE.allowPublic(handle) // Step 2: SDK fetches plaintext + threshold-network signature bytes32 ctHash = euint32.unwrap(counter.count()); (, uint256 plaintext, bytes memory sig) = bob.decryptForTx_withoutPermit(ctHash); // Step 3: contract verifies signature and stores plaintext counter.revealCounter(uint32(plaintext), sig); ``` The same shape runs unmodified against real CoFHE on testnet — the mock signature is produced by the same `MockThresholdNetworkSigner` that `FHE.verifyDecryptResult` accepts. ### `decryptForView` — permit-based unseal ```solidity theme={null} Permission memory bobPermit = bob.permit_createSelf(); uint256 value = bob.decryptForView(ctHash, bobPermit); assertEq(value, 42); ``` `decryptForView` reverts when the caller isn't on the ACL. To **assert** the deny path (e.g. "Alice should NOT be able to decrypt Bob's value"), drop down to the mock directly — see [Testing: Deny path](/client-sdk/foundry-plugin/testing#deny-path-asserting-alice-cannot-decrypt-bob-s-value). ## Permits The client signs EIP-712 permits against the ACL's domain. Two flavors: | Method | Purpose | | -------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | `permit_createSelf()` | Self-permit for the connected account; sealing key is auto-derived (`keccak(address)`). | | `permit_createShared(recipient)` | Issuer half of a shared permit (no sealing key — the recipient adds it on import). | | `permit_exportShared(perm)` | Strip sensitive fields → `SharedPermitExport` (safe to transmit out-of-band). | | `permit_importShared(export)` | Recipient-side completion: adds sealing key + recipient signature. Reverts unless `export.recipient == account()`. | | `createSealingKey(seed)` | Custom sealing key. Rarely needed — `permit_createSelf` derives one for you. | ### Self-permit (most common) ```solidity theme={null} Permission memory bobPermit = bob.permit_createSelf(); uint256 plaintext = bob.decryptForView(ctHash, bobPermit); ``` `permit_createSelf` builds the EIP-712 typed-data, derives a sealing key from the connected account, and signs — all in one call. ### Shared permits (issuer → recipient) ```solidity theme={null} // Bob (issuer) creates a permit shared to Alice (recipient) Permission memory shared = bob.permit_createShared(alice.account()); // Bob exports it (strips bob's sealing key) for transmission SharedPermitExport memory exported = bob.permit_exportShared(shared); // Alice imports it — adds her sealing key and recipient signature Permission memory aliceImported = alice.permit_importShared(exported); ``` `permit_importShared` reverts unless the calling client's `account()` matches `export.recipient` — preventing Alice from importing a permit shared to someone else. ## Common pitfalls `vm.prank(bob.account())` while the input came from `alice.createInEuintN(...)` fails ZK verification — the input was signed for Alice's address, not Bob's. Match the client to the prank. `euint32.unwrap(counter.count())` returns the *current* handle. Storing it in a local then asserting after a write reads the old handle. ```solidity theme={null} bytes32 oldHandle = euint32.unwrap(counter.count()); counter.increment(); // ❌ oldHandle still references the pre-increment handle expectPlaintext(oldHandle, uint32(0)); // passes by accident expectPlaintext(counter.count(), uint32(1)); // ✅ fetch the new handle ``` Re-fetch after each state change. The `pkey` passed to `connect` must derive the address used as `permit.issuer`. If you call `bob.permit_createSelf()` after `bob.connect(0xB0B)`, the issuer is `vm.addr(0xB0B)`. Trying to forge an issuer mismatch will fail signature verification. Useful default — most tests want a hard failure when the caller isn't permitted. To assert "Alice cannot decrypt", call the mock's `querySealOutput` directly: ```solidity theme={null} (bool allowed, string memory err, ) = mockThresholdNetwork.querySealOutput( uint256(ctHash), block.chainid, alicePermit ); assertFalse(allowed); assertEq(err, "NotAllowed"); ``` ## Next steps * [Testing](/client-sdk/foundry-plugin/testing) — full test-writing patterns. * [CofheTest](/client-sdk/foundry-plugin/cofhe-test) — the test base contract that creates clients. # CofheTest Source: https://cofhe-docs.fhenix.zone/client-sdk/foundry-plugin/cofhe-test The CofheTest abstract base contract — deploys CoFHE mocks and exposes plaintext assertions `CofheTest` is the abstract test base shipped by `@cofhe/foundry-plugin`. It already inherits `forge-std/Test`, so inherit `CofheTest` directly — inheriting both is a redeclaration error. ```solidity theme={null} import { CofheTest } from "@cofhe/foundry-plugin/contracts/CofheTest.sol"; contract MyTest is CofheTest { function setUp() public { deployMocks(); // ... your contract deploys } } ``` ## `deployMocks()` Call once in `setUp()`. Deploys the full CoFHE mock stack and wires the contracts together: | Mock | Role | Address | | ---------------------------- | -------------------------------------------------------- | ------------------------------ | | `MockTaskManager` | Manages FHE operations; stores plaintext values on-chain | `TASK_MANAGER_ADDRESS` (fixed) | | `MockACL` | Access control list for encrypted handles | (deployed) | | `MockZkVerifier` | Verifies encrypted-input ZK proofs | `0x…5001` (fixed) | | `MockZkVerifierSigner` | Signs encrypted inputs (for the mock verifier) | (funded with 10 ether) | | `MockThresholdNetwork` | Handles decryption requests | `0x…5002` (fixed) | | `MockThresholdNetworkSigner` | Signs decrypt-result outputs | (deployed) | After `deployMocks()` runs, the following public state vars are available on `CofheTest`: ```solidity theme={null} mockTaskManager mockAcl mockZkVerifier mockZkVerifierSigner mockThresholdNetwork mockThresholdNetworkSigner ``` Reach into them when you need behavior `expectPlaintext` doesn't cover (e.g. permission-denied assertions — see [Testing](/client-sdk/foundry-plugin/testing#deny-path-asserting-alice-cannot-decrypt-bob-s-value)). ## `createCofheClient()` Returns a fresh [`CofheClient`](/client-sdk/foundry-plugin/cofhe-client). You typically follow it with `connect(pkey)`: ```solidity theme={null} CofheClient bob = createCofheClient(); bob.connect(0xB0B); ``` One client per scenario address. Connecting with a deterministic plaintext private key keeps test output debuggable. ## Reading plaintext values Because `MockTaskManager` stores plaintext values on-chain, you can read the underlying plaintext of any encrypted handle directly — no permit needed. ### `getPlaintext(handle)` Returns the on-chain plaintext from `MockTaskManager.mockStorage`. Typed overloads exist for every encrypted Solidity type: ```solidity theme={null} bytes32 plaintext = getPlaintext(handle); // raw bool flag = getPlaintext(eboolHandle); uint8 v8 = getPlaintext(euint8Handle); uint16 v16 = getPlaintext(euint16Handle); uint32 v32 = getPlaintext(euint32Handle); uint64 v64 = getPlaintext(euint64Handle); uint128 v128 = getPlaintext(euint128Handle); address addr = getPlaintext(eaddressHandle); ``` Reverts if the handle isn't in mock storage. ### `expectPlaintext(handle, value)` and `(handle, value, "msg")` Assertion variant — typed overloads for the same type set. Faster than `decryptForView` (no SDK round-trip, no permit needed). Use it whenever you only care about the value, not the SDK code path. ```solidity theme={null} expectPlaintext(counter.count(), uint32(42)); expectPlaintext(counter.count(), uint32(42), "after increment"); ``` Prefer `expectPlaintext` over `decryptForView` for state assertions. Reserve the SDK path for tests where the SDK behavior itself is under test. ## Logging The mocks log every FHE operation to the console when log mode is on. Toggle it locally with: | Function | Effect | | --------------- | ---------------------------------------- | | `enableLogs()` | Calls `mockTaskManager.setLogOps(true)` | | `disableLogs()` | Calls `mockTaskManager.setLogOps(false)` | Logging is verbose — enable it only around the operation you're investigating. ```solidity theme={null} function test_DebuggingIncrement() public { enableLogs(); vm.prank(bob.account()); counter.increment(); disableLogs(); } ``` Each operation prints in a formatted block: ``` ├ FHE.add | euint32(4473..3424)[0] + euint32(1157..3648)[1] => euint32(1106..1872)[1] ├ FHE.allowThis | euint32(1106..1872)[1] -> 0x663f..6602 ``` ## Common pitfalls Solidity LSPs (Wake, Cursor, etc.) often resolve imports from the monorepo root rather than the package's `node_modules`. The plugin's remappings are package-local, so the LSP may flag valid imports. **`forge` is authoritative** — if `forge build` and `forge test` succeed, the test is correct. That import path is the **old** API from `@cofhe/mock-contracts@0.4.x`. Migrate to: ```solidity theme={null} import { CofheTest } from "@cofhe/foundry-plugin/contracts/CofheTest.sol"; ``` See the [migration mapping](/client-sdk/foundry-plugin/testing#migration-from-the-old-mock-contracts-api) for the full rename table. The remappings are relative to the foundry package's root. Running `forge test` from a parent monorepo directory won't resolve them — `forge` resolves from the working directory. ## Next steps * [CofheClient](/client-sdk/foundry-plugin/cofhe-client) — per-user encrypt / decrypt / permit shim. * [Testing](/client-sdk/foundry-plugin/testing) — canonical test-writing patterns and migration mapping. # Getting Started Source: https://cofhe-docs.fhenix.zone/client-sdk/foundry-plugin/getting-started Set up @cofhe/foundry-plugin for local FHE contract development and testing under Forge `@cofhe/foundry-plugin` is the Foundry counterpart to [`@cofhe/hardhat-plugin`](/client-sdk/hardhat-plugin/getting-started). It provides two abstract Solidity contracts — `CofheTest` (test base, deploys all CoFHE mocks) and `CofheClient` (per-account encrypt/decrypt/permit shim) — that let you exercise FHE contracts under `forge test` with **no JS SDK required**. Want to skip the setup? Clone the [cofhe-foundry-starter](https://github.com/FhenixProtocol/cofhe-foundry-starter) template to get a pre-configured project ready to go. ## What the plugin provides * **`CofheTest`** — abstract test base that inherits `forge-std/Test` and deploys the full CoFHE mock stack (`MockTaskManager`, `MockACL`, `MockZkVerifier`, `MockThresholdNetwork`). * **`CofheClient`** — in-Solidity SDK shim. One client per "user" in your scenario; each client carries a private key and produces encrypted inputs and signed permits as if it were that user's frontend SDK. * **Plaintext assertions** — `expectPlaintext(handle, value)` reads the on-chain plaintext from the mock task manager. Faster than `decryptForView` and needs no permit. ## Prerequisites * [Foundry](https://book.getfoundry.sh/getting-started/installation) (`forge`, `cast`, `anvil`) * Node.js 18+ with npm/pnpm/yarn (for installing the plugin's npm dependencies) ## Installation The plugin and its dependencies are distributed via npm. Install them as dev dependencies in your Foundry project: ```bash npm theme={null} npm install -D @cofhe/foundry-plugin@^0.5.2 @cofhe/mock-contracts@^0.5.2 @fhenixprotocol/cofhe-contracts@^0.1.3 @openzeppelin/contracts ``` ```bash pnpm theme={null} pnpm add -D @cofhe/foundry-plugin@^0.5.2 @cofhe/mock-contracts@^0.5.2 @fhenixprotocol/cofhe-contracts@^0.1.3 @openzeppelin/contracts ``` ```bash yarn theme={null} yarn add -D @cofhe/foundry-plugin@^0.5.2 @cofhe/mock-contracts@^0.5.2 @fhenixprotocol/cofhe-contracts@^0.1.3 @openzeppelin/contracts ``` ```bash theme={null} forge install foundry-rs/forge-std ``` The CoFHE contracts and mocks have specific Solidity / EVM requirements. Set them in `foundry.toml`: ```toml foundry.toml theme={null} [profile.default] src = "src" out = "out" test = "test" script = "script" libs = ["node_modules"] solc_version = "0.8.25" # cofhe-contracts target auto_detect_remappings = false code_size_limit = 100000 # mocks exceed 24 KB ``` * `code_size_limit = 100000` is required — the mock contracts exceed the EIP-170 24 KB ceiling. * `solc_version = "0.8.25"` matches the compiler used by `@fhenixprotocol/cofhe-contracts`. `evm_version = "cancun"` is no longer required as of `@cofhe/mock-contracts@0.5.0` — `MockACL` was migrated off transient storage (`tstore`/`tload`) to block-number-based storage, and the pragma was lowered to `>=0.8.19`. Set it only if your own contracts need cancun-specific opcodes. ```text remappings.txt theme={null} forge-std/=node_modules/forge-std/src/ hardhat/=node_modules/forge-std/src/ @openzeppelin/contracts/=node_modules/@openzeppelin/contracts/ @fhenixprotocol/cofhe-contracts/=node_modules/@fhenixprotocol/cofhe-contracts/ @cofhe/mock-contracts/=node_modules/@cofhe/mock-contracts/ @cofhe/foundry-plugin/=node_modules/@cofhe/foundry-plugin/ ``` Two non-obvious shapes worth calling out: * **`@cofhe/mock-contracts/` points at the package root**, not `…/contracts/`. The plugin's own imports already include the `contracts/` segment (e.g. `@cofhe/mock-contracts/contracts/MockTaskManager.sol`). Pointing at `…/contracts/` will break those imports. * **`hardhat/=node_modules/forge-std/src/` is load-bearing.** `MockCoFHE.sol` inside the mock contracts imports `hardhat/console.sol`; this alias resolves it to forge-std's compatible `console.sol`. Drop it and `forge build` errors with `Source "hardhat/console.sol" not found`. ## Verify the setup ```bash theme={null} forge build forge test ``` If `forge build` succeeds, your remappings and `foundry.toml` are correct. From here, write a test that inherits `CofheTest`: ```solidity test/MyTest.t.sol theme={null} import { CofheTest } from "@cofhe/foundry-plugin/contracts/CofheTest.sol"; contract MyTest is CofheTest { function setUp() public { deployMocks(); // ... your contract deploys } } ``` ## Version pinning The plugin and `@cofhe/mock-contracts` pin `@fhenixprotocol/cofhe-contracts` *exactly* (no caret). Keep the three CoFHE packages aligned — otherwise `npm install` may resolve `cofhe-contracts` to a newer version that the mocks don't implement, producing `MockTaskManager should be marked as abstract` at compile time. Known-aligned tuple as of writing: | Package | Version | | --------------------------------- | ------- | | `@cofhe/foundry-plugin` | `0.5.2` | | `@cofhe/mock-contracts` | `0.5.2` | | `@fhenixprotocol/cofhe-contracts` | `0.1.3` | See the [Compatibility](/get-started/introduction/compatibility) page for the canonical table. ## Mocks vs production The mocks are the same `@cofhe/mock-contracts` package the [Hardhat plugin](/client-sdk/hardhat-plugin/mock-contracts) uses. Behaviorally: * Plaintext lives on-chain in `MockTaskManager.mockStorage` (so `expectPlaintext` and `getPlaintext` work). * No real ZK proving; encrypted inputs are signed by `MockZkVerifierSigner`. * Decryption is synchronous — `decryptForTx_withoutPermit` returns the result immediately. * Mock signatures are accepted by the same `FHE.verifyDecryptResult` your contract uses on testnet. The same test code runs unchanged against real CoFHE on a deployed network. ## Next steps * [CofheTest](/client-sdk/foundry-plugin/cofhe-test) — the test base contract: `deployMocks`, `expectPlaintext`, `getPlaintext`, log toggles. * [CofheClient](/client-sdk/foundry-plugin/cofhe-client) — per-user shim: `createInEuintN`, `decryptForTx_withoutPermit`, `decryptForView`, permits. * [Testing](/client-sdk/foundry-plugin/testing) — canonical test patterns and the migration mapping from the old `@cofhe/mock-contracts/foundry/CoFheTest.sol` API. # Testing Source: https://cofhe-docs.fhenix.zone/client-sdk/foundry-plugin/testing Canonical test-writing patterns for FHE contracts under Foundry This page shows the load-bearing patterns for writing FHE contract tests under Foundry with `@cofhe/foundry-plugin`. Hardhat counterpart: [Hardhat Plugin → Testing](/client-sdk/hardhat-plugin/testing). ## Skeleton ```solidity test/Counter.t.sol theme={null} import { CofheTest } from "@cofhe/foundry-plugin/contracts/CofheTest.sol"; import { CofheClient } from "@cofhe/foundry-plugin/contracts/CofheClient.sol"; import { InEuint32 } from "@fhenixprotocol/cofhe-contracts/FHE.sol"; import { Counter } from "../src/Counter.sol"; contract CounterTest is CofheTest { Counter public counter; CofheClient bob; CofheClient alice; uint256 constant BOB_PKEY = 0xB0B; uint256 constant ALICE_PKEY = 0xA11CE; function setUp() public { deployMocks(); bob = createCofheClient(); bob.connect(BOB_PKEY); alice = createCofheClient(); alice.connect(ALICE_PKEY); vm.prank(bob.account()); counter = new Counter(); } function test_Increments() public { vm.prank(bob.account()); counter.increment(); expectPlaintext(counter.count(), uint32(1)); } } ``` That's the load-bearing shape. Everything below is what to add when the contract gets non-trivial. ## Rules ### 1. Inherit `CofheTest`, not `Test` `CofheTest` already inherits `forge-std/Test` and exposes the mock state vars (`mockTaskManager`, `mockAcl`, `mockThresholdNetwork`, …) you'll occasionally reach into. Inheriting both is a redeclaration error. ### 2. One `CofheClient` per scenario address Each user with their own permit/encrypted inputs gets their own client. Connect with a deterministic plaintext private key — don't recycle real keys; these are visible in test output. ```solidity theme={null} CofheClient bob = createCofheClient(); bob.connect(0xB0B); // bob.account() == vm.addr(0xB0B) ``` You don't pass `account` to `createInEuintN` — the client is bound at `connect`. ### 3. `vm.prank(client.account())` to act on-chain as that user ```solidity theme={null} vm.prank(bob.account()); counter.reset(bob.createInEuint32(2000)); ``` Mismatching the prank address and the client that produced the input fails the ZK-verifier signature check. ### 4. Assert with `expectPlaintext` whenever possible ```solidity theme={null} expectPlaintext(counter.count(), uint32(2000)); // typed overload ``` Faster than `decryptForView` and needs no permit. Reserve the SDK path for tests where the SDK behavior itself is under test. ### 5. Test the public-decrypt 3-step flow with `decryptForTx_withoutPermit` When the contract calls `FHE.publishDecryptResult`: ```solidity theme={null} // Step 1: contract grants public decrypt permission vm.prank(bob.account()); counter.allowCounterPublicly(); // FHE.allowPublic(handle) // Step 2: SDK fetches plaintext + threshold-network signature bytes32 ctHash = euint32.unwrap(counter.count()); (, uint256 plaintext, bytes memory sig) = bob.decryptForTx_withoutPermit(ctHash); // Step 3: contract verifies signature and stores plaintext counter.revealCounter(uint32(plaintext), sig); assertEq(counter.getDecryptedValue(), plaintext); ``` Same shape runs unmodified against real CoFHE on testnet — the mock signature is produced by the same `MockThresholdNetworkSigner` that `FHE.verifyDecryptResult` accepts. ### 6. Permit-based unseal: `decryptForView` for the success path ```solidity theme={null} import { Permission } from "@cofhe/mock-contracts/contracts/Permissioned.sol"; Permission memory bobPermit = bob.permit_createSelf(); uint256 value = bob.decryptForView(ctHash, bobPermit); assertEq(value, expected); ``` `permit_createSelf` builds the EIP-712 typed-data, derives a sealing key from the connected account, and signs — no manual `signPermissionSelf` boilerplate. ### 7. Deny path: asserting "Alice cannot decrypt Bob's value" `decryptForView` reverts when the caller isn't on the ACL. To assert the deny path, drop down to the mock directly: ```solidity theme={null} Permission memory alicePermit = alice.permit_createSelf(); (bool allowed, string memory err, ) = mockThresholdNetwork.querySealOutput( uint256(ctHash), block.chainid, alicePermit ); assertFalse(allowed, "Alice should NOT be allowed"); assertEq(err, "NotAllowed"); ``` `mockThresholdNetwork` is a public field on `CofheTest`. ### 8. Fuzz tests inherit normally ```solidity theme={null} function testFuzz_Reset(uint32 v) public { InEuint32 memory enc = bob.createInEuint32(v); vm.prank(bob.account()); counter.reset(enc); expectPlaintext(counter.count(), v); } ``` `createInEuintN` accepts the full `uintN` range — no shaping needed. ## Migration from the old `mock-contracts` API If you're upgrading from `@cofhe/mock-contracts@0.4.x` (where `CoFheTest` lived inside the mocks package), here's the rename table: | Old API (mock-contracts ≤ 0.4) | New API (`@cofhe/foundry-plugin`) | | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | `import { CoFheTest } from "@cofhe/mock-contracts/foundry/CoFheTest.sol"` | `import { CofheTest } from "@cofhe/foundry-plugin/contracts/CofheTest.sol"` | | `is Test, CoFheTest` | `is CofheTest` (Test already inherited) | | `assertHashValue(handle, value)` | `expectPlaintext(handle, value)` | | `mockStorage(ctHash)` | `getPlaintext(ctHash)` | | `createInEuint32(v, bob)` | `bob.createInEuint32(v)` | | `createPermissionSelf(bob)` + `signPermissionSelf(perm, bobKey)` | `bob.permit_createSelf()` (auto-signs) | | `createSealingKey(seed)` | `bob.createSealingKey(seed)` (rarely needed — `permit_createSelf` derives one) | | `queryDecrypt(hash, chainId, permit)` | `bob.decryptForView(hash, permit)` (reverts on deny) | | Same, asserting deny | `mockThresholdNetwork.querySealOutput(hash, block.chainid, permit)` | | `querySealOutput` + `unseal` | `bob.decryptForView` (does both) | | `decryptForTxWithoutPermit(ct)` returns `(allowed, error, plaintext)` | `bob.decryptForTx_withoutPermit(ct)` returns `(ctHash, plaintext, signature)` | ## Common pitfalls LSPs like Wake/Cursor often resolve from the monorepo root; the plugin's remappings are package-local. **`forge` is the truth** — if `forge build` and `forge test` succeed, the test is correct. Tests pass on the first op, then a second op reverts with `ACLNotAllowed` because the contract itself isn't on the ACL. Toggle `enableLogs()` to see the missing grant — every op prints a line showing whether `allowThis`/`allow` was called. `vm.prank(bob.account())` while the input came from `alice.createInEuintN(...)` fails ZK verification. Always match the client to the prank. `euint32.unwrap(counter.count())` returns the *current* handle. Storing it in a local then asserting after a write reads the old handle: ```solidity theme={null} bytes32 oldHandle = euint32.unwrap(counter.count()); counter.increment(); expectPlaintext(oldHandle, uint32(0)); // passes by accident expectPlaintext(counter.count(), uint32(1)); // ✅ re-fetch ``` Re-fetch after each state change. The `pkey` passed to `connect` must derive the address used as `permit.issuer`. `bob.permit_createSelf()` after `bob.connect(0xB0B)` produces `issuer == vm.addr(0xB0B)`. Trying to forge a mismatch fails signature verification. ## Related * [CofheTest](/client-sdk/foundry-plugin/cofhe-test) — the test base contract API. * [CofheClient](/client-sdk/foundry-plugin/cofhe-client) — the per-user shim API. * [Hardhat → Testing](/client-sdk/hardhat-plugin/testing) — the same patterns under Hardhat. # Client Setup Source: https://cofhe-docs.fhenix.zone/client-sdk/guides/client-setup Configure and connect the @cofhe/sdk client with viem, Ethers, or wagmi This page covers the core SDK lifecycle: 1. Create config (`createCofheConfig`) 2. Create client (`createCofheClient`) 3. Connect (`client.connect`) 4. Manage connection (change account / disconnect) ## 1. Create config Import `createCofheConfig` from the entrypoint that matches your runtime: * Browser apps: `@cofhe/sdk/web` * Node.js scripts/backends: `@cofhe/sdk/node` The only required field is `supportedChains`. ```typescript theme={null} import { createCofheConfig } from '@cofhe/sdk/web'; import { chains } from '@cofhe/sdk/chains'; const config = createCofheConfig({ supportedChains: [chains.sepolia], // Optional knobs // defaultPermitExpiration: 60 * 60 * 24 * 30, // useWorkers: true, }); ``` ## 2. Create the client ```typescript theme={null} import { createCofheConfig, createCofheClient } from '@cofhe/sdk/web'; import { chains } from '@cofhe/sdk/chains'; const config = createCofheConfig({ supportedChains: [chains.sepolia] }); const cofheClient = createCofheClient(config); cofheClient.connected; // false cofheClient.connecting; // false ``` ## 3. Connect The SDK connects to CoFHE using viem clients: * `PublicClient`: read-only chain access * `WalletClient`: signing + sending transactions If you already have viem clients, pass them directly. ```typescript theme={null} import { createPublicClient, createWalletClient, http } from 'viem'; import { sepolia } from 'viem/chains'; const publicClient = createPublicClient({ chain: sepolia, transport: http(), }); const walletClient = createWalletClient({ chain: sepolia, transport: http(), }); await cofheClient.connect(publicClient, walletClient); cofheClient.connected; // true ``` ### Using adapters If you use a different wallet/provider stack, `@cofhe/sdk/adapters` provides adapters that convert into viem-shaped clients. ```typescript Ethers v6 theme={null} import { Ethers6Adapter } from '@cofhe/sdk/adapters'; import { ethers } from 'ethers'; const provider = new ethers.JsonRpcProvider('https://rpc.sepolia.org'); const signer = new ethers.Wallet('0xYOUR_PRIVATE_KEY', provider); const { publicClient, walletClient } = await Ethers6Adapter( provider, signer ); await cofheClient.connect(publicClient, walletClient); ``` ```typescript Ethers v5 theme={null} import { Ethers5Adapter } from '@cofhe/sdk/adapters'; import { ethers } from 'ethers5'; const provider = new ethers.providers.JsonRpcProvider( 'https://rpc.sepolia.org' ); const signer = new ethers.Wallet('0xYOUR_PRIVATE_KEY').connect(provider); const { publicClient, walletClient } = await Ethers5Adapter( provider, signer ); await cofheClient.connect(publicClient, walletClient); ``` ```typescript Wagmi theme={null} import { WagmiAdapter } from '@cofhe/sdk/adapters'; import { createPublicClient, createWalletClient, http } from 'viem'; import { sepolia } from 'viem/chains'; const wagmiPublicClient = createPublicClient({ chain: sepolia, transport: http(), }); const wagmiWalletClient = createWalletClient({ chain: sepolia, transport: http(), }); const { publicClient, walletClient } = await WagmiAdapter( wagmiWalletClient, wagmiPublicClient ); await cofheClient.connect(publicClient, walletClient); ``` ## 4. Managing connections ### Reconnect behavior Calling `connect` again with the same clients is a no-op. Calling it with new clients replaces the connection state. ### Changing connected account To switch the client's connected account, call `cofheClient.connect()` with updated viem clients. ```typescript theme={null} const bobWalletClient = createWalletClient({ chain: sepolia, transport: http(), account: bobAddress, }); const aliceWalletClient = createWalletClient({ chain: sepolia, transport: http(), account: aliceAddress, }); // Connect as Bob await cofheClient.connect(publicClient, bobWalletClient); cofheClient.connection.account; // Bob's address // Switch to Alice await cofheClient.connect(publicClient, aliceWalletClient); cofheClient.connection.account; // Alice's address ``` ### Disconnecting To manually disconnect, call `cofheClient.disconnect()`. This clears the in-memory connection state (clients/account/chainId) and marks the client as disconnected. It does **not** delete persisted permits or stored FHE keys. ```typescript theme={null} cofheClient.disconnect(); cofheClient.connected; // false ``` # Decrypt to Transact Source: https://cofhe-docs.fhenix.zone/client-sdk/guides/decrypt-to-tx Decrypt with a verifiable Threshold Network signature for on-chain use Use `decryptForTx` to reveal a confidential (encrypted) value on-chain: it returns the plaintext together with a Threshold Network signature, so a contract can verify the reveal when you publish it in a transaction. Common use cases: * **Unshield a confidential token**: reveal the encrypted amount you're unshielding so the contract can finalize the public transfer. * **Finalize a private auction / game move**: bids or moves are submitted encrypted, and the winner is revealed later in a verifiable way. If you only need to show plaintext in your UI (and you do **not** need an on-chain-verifiable signature), use [`decryptForView`](/client-sdk/guides/decrypt-to-view) instead. ## Prerequisites 1. [Create and connect a client](/client-sdk/guides/client-setup). 2. Know the on-chain encrypted handle (`ctHash`) you want to decrypt. 3. Determine whether the contract's ACL policy for this `ctHash` requires a [permit](/client-sdk/guides/permits). `decryptForTx` does not take a `utype`. It always returns the plaintext as a `bigint` because the result is intended to be passed into a transaction. If you need UI-friendly decoding, use [`decryptForView`](/client-sdk/guides/decrypt-to-view). ## Permit: when is it needed? Often, `decryptForTx` is used to reveal a value that the protocol already considers OK to make public. In those cases, the contract's ACL policy can allow anyone to decrypt, and you can use `.withoutPermit()`. Examples where a permit is **not** needed: * **Unshielding**: the amount being unshielded is no longer meant to stay secret. * **Auction/game reveal**: it doesn't matter who submits the reveal — only that the result is verified. If the ACL policy restricts decryption, you must use `.withPermit(...)`. ## What `decryptForTx` returns `.execute()` resolves to an object with: * `ctHash: bigint | string` — the ciphertext handle you decrypted * `decryptedValue: bigint` — the plaintext value (always a `bigint`) * `signature: 0x${string}` — the Threshold Network signature as a hex string ## Decrypt (choose permit mode) ```typescript No permit theme={null} const decryptResult = await client .decryptForTx(ctHash) .withoutPermit() .execute(); decryptResult.decryptedValue; decryptResult.signature; ``` ```typescript Active permit theme={null} const decryptResult = await client .decryptForTx(ctHash) .withPermit() .execute(); ``` ```typescript Explicit permit theme={null} const permit = await client.permits.getOrCreateSelfPermit(); const decryptResult = await client .decryptForTx(ctHash) .withPermit(permit) .execute(); ``` After decrypting, see [Writing Decrypt Result to Contract](/client-sdk/guides/writing-decrypt-result) for how to publish or verify the result on-chain. ## Builder API ### `.execute()` — required, call last Runs the decryption and returns `{ ctHash, decryptedValue, signature }`. ### `.withPermit(...)` — required unless using `.withoutPermit()` * `.withPermit()` — uses the active permit * `.withPermit(permitHash)` — fetches a stored permit by hash * `.withPermit(permit)` — uses the provided permit object ### `.withoutPermit()` — required unless using `.withPermit(...)` Decrypt via global allowance (no permit). Only works if the contract's ACL policy allows anyone to decrypt that `ctHash`. ### `.setAccount(address)` — optional Overrides the account used to resolve the active/stored permit. ### `.setChainId(chainId)` — optional Overrides the chain used to resolve the Threshold Network URL and permits. ### `.onPoll(callback)` — optional Register a callback that fires once per poll attempt while `decryptForTx` waits for the Threshold Network to return the plaintext. Useful for surfacing progress in a UI. ```typescript theme={null} const decryptResult = await client .decryptForTx(ctHash) .withoutPermit() .onPoll(({ operation, requestId, attemptIndex, elapsedMs, intervalMs, timeoutMs }) => { console.log(`[${operation}] attempt ${attemptIndex} after ${elapsedMs}ms (next in ${intervalMs}ms, budget ${timeoutMs}ms)`); }) .execute(); ``` The callback receives: | Field | Type | Description | | -------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `operation` | `'decrypt' \| 'sealoutput'` | Which Threshold Network flow is polling. For `decryptForTx` this is always `'decrypt'`. | | `requestId` | `string` | The Threshold Network request id. **May be the empty string** during submit-time retries — see `.set404RetryTimeout(...)` below. | | `attemptIndex` | `number` | Zero-based poll attempt counter. | | `elapsedMs` | `number` | Time since the first submit attempt. | | `intervalMs` | `number` | Delay until the next poll. | | `timeoutMs` | `number` | Overall budget shared by submit-retries and status-polling. | ### `.set404RetryTimeout(timeoutMs)` — optional Configures how long `decryptForTx` keeps retrying when the Threshold Network's submit endpoint responds with `404 Not Found` before a `requestId` is available. This typically happens on slower backends where the ciphertext isn't visible yet at submit time. Defaults to `10_000` ms. ```typescript theme={null} const decryptResult = await client .decryptForTx(ctHash) .withoutPermit() .set404RetryTimeout(20_000) // give a slower backend more time .execute(); ``` Pass `0` to disable submit-time retries (`404` becomes a hard failure). Submit retries share the same overall timeout budget as status polling, so a larger value here trades poll time for submit-recovery time. ## Common pitfalls * **Permit mode must be selected**: you must call exactly one of `.withPermit(...)` or `.withoutPermit()` before `.execute()`. * **Wrong chain/account**: permits are scoped to `chainId + account`. If you get an ACL/permit error, double-check the connected chain and account. # Decrypt to View Source: https://cofhe-docs.fhenix.zone/client-sdk/guides/decrypt-to-view Reveal encrypted values locally for UI display using permits Use `decryptForView` to reveal a confidential (encrypted) value locally in your app so you can display it in the UI. Unlike [`decryptForTx`](/client-sdk/guides/decrypt-to-tx), this flow does **not** return an on-chain-verifiable signature, and it is **not** meant to be published on-chain. ## Flow 1. Read the encrypted handle (`ctHash`) from your contract. 2. Ensure you have a permit that authorizes decryption of that value. 3. Call `decryptForView(ctHash, utype).execute()` to get the plaintext. `decryptForView` always decrypts using a permit (there is no `.withoutPermit()` mode). If your protocol intends for the plaintext to become publicly visible on-chain, use [`decryptForTx`](/client-sdk/guides/decrypt-to-tx) instead. ## Prerequisites 1. [Create and connect a client](/client-sdk/guides/client-setup). 2. Know the encrypted handle (`ctHash`) and the encrypted type (`utype`). 3. Have a [permit](/client-sdk/guides/permits) available for the connected `chainId + account`. **Getting `ctHash`**: In most apps, `ctHash` comes from reading a stored encrypted value, an event arg, or a return value from a `view` call. **Providing `utype`**: `utype` must match the ciphertext's underlying FHE type. The SDK uses it to convert the decrypted `bigint` into a convenient JS type. Supported `utype`s: * `FheTypes.Bool` → returns a `boolean` * `FheTypes.Uint160` (address) → returns a checksummed `0x...` string * `FheTypes.Uint8 | Uint16 | Uint32 | Uint64 | Uint128` → returns a `bigint` ## Permit setup If you don't have a permit yet, create one once after connecting: ```typescript theme={null} await client.connect(publicClient, walletClient); // Creates a permit if needed, stores it, and selects it as the active permit. await client.permits.getOrCreateSelfPermit(); ``` ## Decrypt for UI Choose the pattern that matches how your app manages permits: ```typescript Active permit theme={null} await client.connect(publicClient, walletClient); await client.permits.getOrCreateSelfPermit(); const plaintext = await client .decryptForView(ctHash, FheTypes.Uint32) .execute(); ``` ```typescript Permit object theme={null} const permit = await client.permits.getOrCreateSelfPermit(); const plaintext = await client .decryptForView(ctHash, FheTypes.Uint64) .withPermit(permit) .execute(); ``` ```typescript Permit hash theme={null} const plaintext = await client .decryptForView(ctHash, FheTypes.Uint8) .withPermit(permitHash) .execute(); ``` ## What `decryptForView` returns Running `.execute()` resolves to a scalar JS value: * Integer utypes (`Uint8`, `Uint16`, `Uint32`, `Uint64`, `Uint128`): a `bigint` * `FheTypes.Bool`: a `boolean` * `FheTypes.Uint160` (address): a checksummed `0x...` address string ## Builder API ### `.execute()` — required, call last Runs the decryption and returns a UI-friendly scalar value. ### `.withPermit(...)` — optional Select which permit to use: * `.withPermit()` — uses the active permit * `.withPermit(permitHash)` — fetches a stored permit by hash * `.withPermit(permit)` — uses the provided permit object If you don't call `.withPermit(...)`, the active permit is used by default. ### `.setAccount(address)` — optional Overrides the account used to resolve the active/stored permit. ### `.setChainId(chainId)` — optional Overrides the chain used to resolve the Threshold Network URL and permits. ### `.onPoll(callback)` — optional Register a callback that fires once per poll attempt while `decryptForView` waits for the Threshold Network to return the sealed plaintext. Useful for surfacing decrypt progress in a UI. ```typescript theme={null} const plaintext = await client .decryptForView(ctHash, FheTypes.Uint64) .onPoll(({ operation, requestId, attemptIndex, elapsedMs, intervalMs, timeoutMs }) => { console.log(`[${operation}] attempt ${attemptIndex} after ${elapsedMs}ms (next in ${intervalMs}ms, budget ${timeoutMs}ms)`); }) .execute(); ``` The callback receives: | Field | Type | Description | | -------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | | `operation` | `'decrypt' \| 'sealoutput'` | Which Threshold Network flow is polling. For `decryptForView` this is `'sealoutput'`. | | `requestId` | `string` | The Threshold Network request id. **May be the empty string** during submit-time retries — see `.set404RetryTimeout(...)` below. | | `attemptIndex` | `number` | Zero-based poll attempt counter. | | `elapsedMs` | `number` | Time since the first submit attempt. | | `intervalMs` | `number` | Delay until the next poll. | | `timeoutMs` | `number` | Overall budget shared by submit-retries and status-polling. | ### `.set404RetryTimeout(timeoutMs)` — optional Configures how long `decryptForView` keeps retrying when the Threshold Network's submit endpoint responds with `404 Not Found` before a `requestId` is available. This typically happens on slower backends where the ciphertext isn't visible yet at submit time. Defaults to `10_000` ms. ```typescript theme={null} const plaintext = await client .decryptForView(ctHash, FheTypes.Uint32) .set404RetryTimeout(20_000) // give a slower backend more time .execute(); ``` Pass `0` to disable submit-time retries (`404` becomes a hard failure). Submit retries share the same overall timeout budget as status polling. ## Common UI patterns ```typescript Format bigint for display theme={null} import { formatUnits } from 'viem'; const decimals = 6; const display = formatUnits(amount, decimals); ``` ```typescript Bigint → number (range-check) theme={null} const MAX_SAFE = BigInt(Number.MAX_SAFE_INTEGER); const asNumber = amount <= MAX_SAFE ? Number(amount) : undefined; ``` ```typescript Booleans & addresses theme={null} const statusLabel = decryptedIsAllowed ? 'Allowed' : 'Not allowed'; const shortOwner = `${decryptedOwner.slice(0, 6)}…${decryptedOwner.slice(-4)}`; ``` ## Common pitfalls * **Missing permit**: `decryptForView` will fail if there is no active permit for the current `chainId + account`. * **Wrong `utype`**: you must pass the correct FHE type for the ciphertext. * **Wrong chain/account**: permits are scoped to `chainId + account`. If the user switches wallets or networks, create/select the correct permit. # Encrypting Inputs Source: https://cofhe-docs.fhenix.zone/client-sdk/guides/encrypting-inputs Encrypt plaintext values with ZK proofs for use in FHE-enabled smart contracts `encryptInputs` encrypts plaintext values into FHE ciphertexts that can be passed as inputs to a confidential smart contract transaction. Values must be encrypted before being passed on-chain to preserve confidentiality. ## Prerequisites 1. [Create and connect a client](/client-sdk/guides/client-setup). 2. Know which encrypted type you want to encode each value as — the type must match the Solidity parameter type your contract expects (e.g. `InEuint32` vs `InEuint64`). ## Basic usage ```typescript theme={null} import { Encryptable } from '@cofhe/sdk'; await cofheClient.connect(publicClient, walletClient); const encrypted = await cofheClient .encryptInputs([ Encryptable.uint32(42n), Encryptable.bool(true), Encryptable.address('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'), ]) .execute(); const [eUint32, eBool, eAddress] = encrypted; ``` The return type is a typed tuple that mirrors the array you pass in — each element is the corresponding `EncryptedItemInput` type. ## Using encrypted inputs in a transaction Pass the returned `EncryptedItemInput` objects directly into your contract call. The on-chain CoFHE library verifies the signature before using the ciphertext. ```solidity Solidity theme={null} function confidentialTransfer(address to, InEuint64 amount) external { // ... } ``` ```typescript TypeScript theme={null} const [encryptedAmount] = await cofheClient .encryptInputs([Encryptable.uint64(amount)]) .execute(); await contract.confidentialTransfer(recipient, encryptedAmount); ``` ## Builder API ### `.execute()` — required, call last Runs the encryption pipeline and returns the `EncryptedItemInput[]` tuple. ```typescript theme={null} const [encryptedAge, encryptedFlag] = await cofheClient .encryptInputs([Encryptable.uint8(25n), Encryptable.bool(true)]) .execute(); ``` ### `.setAccount(address)` — optional Override the address that "owns" the encrypted input. Only that address will be allowed to use the encrypted inputs on-chain. Defaults to the connected wallet account. ```typescript theme={null} const encrypted = await cofheClient .encryptInputs([Encryptable.uint64(10n)]) .setAccount('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045') .execute(); ``` ### `.setChainId(chainId)` — optional Override the chain the encrypted input will be used on. Defaults to the connected chain. ```typescript theme={null} const encrypted = await cofheClient .encryptInputs([Encryptable.uint64(10n)]) .setChainId(11155111) .execute(); ``` ### `.setUseWorker(boolean)` — optional Override the `useWorkers` flag for this specific call. When `true` (the default), ZK proof generation runs in a Web Worker to avoid blocking the main thread. No-op in Node.js. ```typescript theme={null} const encrypted = await cofheClient .encryptInputs([Encryptable.uint32(7n)]) .setUseWorker(false) .execute(); ``` ### `.onStep(callback)` — optional Register a callback that fires at the start and end of each encryption step. Useful for building progress indicators. ```typescript theme={null} import { EncryptStep } from '@cofhe/sdk'; const encrypted = await cofheClient .encryptInputs([Encryptable.uint64(10n)]) .onStep((step, ctx) => { if (ctx?.isStart) console.log(`Starting: ${step}`); if (ctx?.isEnd) console.log(`Done: ${step} (${ctx.duration}ms)`); }) .execute(); ``` #### The encryption flow Calling `.execute()` runs five sequential steps: | Step | Description | | ----------- | ----------------------------------------------------------------------------------- | | `InitTfhe` | Lazy-initializes the TFHE WASM module. A no-op after the first call. | | `FetchKeys` | Fetches (or loads from cache) the FHE public key and CRS for the target chain. | | `Pack` | Packs the plaintext values into a ZK list ready for proving. | | `Prove` | Generates the ZK proof of knowledge (ZKPoK). Uses a Web Worker when available. | | `Verify` | Sends the proof to the CoFHE verifier. Returns signed `EncryptedItemInput` objects. | ## Encryptable — creating inputs Use the `Encryptable` factory to create the items you want to encrypt. Each factory function accepts the plaintext value and an optional `securityZone`. | Factory | Data type | Solidity input param | | ---------------------------- | ------------------ | -------------------- | | `Encryptable.bool(value)` | `boolean` | `InEbool` | | `Encryptable.uint8(value)` | `bigint \| string` | `InEuint8` | | `Encryptable.uint16(value)` | `bigint \| string` | `InEuint16` | | `Encryptable.uint32(value)` | `bigint \| string` | `InEuint32` | | `Encryptable.uint64(value)` | `bigint \| string` | `InEuint64` | | `Encryptable.uint128(value)` | `bigint \| string` | `InEuint128` | | `Encryptable.address(value)` | `bigint \| string` | `InEaddress` | You can also use the generic form: ```typescript theme={null} Encryptable.create('uint32', 42n); Encryptable.create('bool', false); Encryptable.create('address', '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'); ``` **Bit limit:** A single `encryptInputs` call may encrypt at most **2048 bits** of plaintext in total. Exceeding this limit throws a `ZkPackFailed` error. ## EncryptedItemInput — the result type Each element of the returned array is an `EncryptedItemInput`: ```typescript theme={null} type EncryptedItemInput = { ctHash: bigint; // The ciphertext hash registered with CoFHE securityZone: number; // The security zone the input was encrypted under utype: FheTypes; // The FHE type (Bool, Uint8, …, Uint160) signature: string; // CoFHE verifier signature authorizing this input }; ``` Pass these directly into a contract function that accepts `InEuint*` structs. The contract's CoFHE library validates the signature on-chain before operating on the ciphertext. ## Common pitfalls * **Wrong `Encryptable` type**: `Encryptable.uint32(...)` must match what your Solidity function expects (e.g. `InEuint32`). * **Wrong account / chain**: encrypted inputs are authorized for a specific `account + chainId`. If you override these, your inputs may not be usable for the intended transaction. * **Bit limit exceeded**: a single call can encrypt at most **2048 bits** of plaintext. Exceeding this throws `ZkPackFailed`. # Error Handling Source: https://cofhe-docs.fhenix.zone/client-sdk/guides/error-handling Handle errors from @cofhe/sdk operations using typed CofheError objects All SDK operations return values directly and throw typed `CofheError` objects on failure. This replaces the `Result` wrapper pattern used by `cofhejs`. ## Catching errors ```typescript theme={null} import { isCofheError, CofheErrorCode, Encryptable } from '@cofhe/sdk'; try { const [encrypted] = await client .encryptInputs([Encryptable.uint32(42n)]) .execute(); } catch (err) { if (isCofheError(err)) { console.error(err.code); // CofheErrorCode enum value console.error(err.message); // human-readable description } } ``` ## CofheError structure Every `CofheError` has: * `code` — a `CofheErrorCode` enum value identifying the error type * `message` — a human-readable description of what went wrong Use `isCofheError(err)` to check if a caught error is a `CofheError`. ## Common error codes | Error code | When it occurs | | ---------------- | ----------------------------------------------------------- | | `ZkPackFailed` | `encryptInputs` exceeded the 2048-bit plaintext limit | | `PermitNotFound` | No permit found for the given `chainId + account` | | `PermitInvalid` | The permit signature is invalid or expired | | `DecryptFailed` | Decryption request was rejected by the Threshold Network | | `NotConnected` | Attempted an operation before calling `client.connect(...)` | ## Error handling patterns ### Encryption errors ```typescript theme={null} import { isCofheError, CofheErrorCode, Encryptable } from '@cofhe/sdk'; try { const encrypted = await client .encryptInputs([Encryptable.uint128(veryLargeValue)]) .execute(); } catch (err) { if (isCofheError(err) && err.code === CofheErrorCode.ZkPackFailed) { console.error('Input too large — split into multiple calls'); } } ``` ### Decryption errors ```typescript theme={null} import { isCofheError, CofheErrorCode, FheTypes } from '@cofhe/sdk'; try { const plaintext = await client .decryptForView(ctHash, FheTypes.Uint32) .execute(); } catch (err) { if (isCofheError(err) && err.code === CofheErrorCode.PermitNotFound) { // Create a permit and retry await client.permits.getOrCreateSelfPermit(); const plaintext = await client .decryptForView(ctHash, FheTypes.Uint32) .execute(); } } ``` ### Distinguishing why a permit is invalid Since `@cofhe/sdk@0.5.0`, the decrypt flows call `PermitUtils.validate(permit)` internally before talking to the Threshold Network. That helper enforces **schema + signed + not-expired** all at once, so when it fails the recovery path depends on **which** check tripped. Use the non-throwing `ValidationUtils.isValid` helper from `@cofhe/sdk/permits` to pre-flight the active permit and route based on the typed reason — this avoids the thrown error path entirely: ```typescript theme={null} import { FheTypes } from '@cofhe/sdk'; import { ValidationUtils } from '@cofhe/sdk/permits'; const active = client.permits.getActivePermit(); const result = active ? ValidationUtils.isValid(active) : { valid: false, error: 'not-signed' as const }; if (!result.valid) { switch (result.error) { case 'expired': await client.permits.getOrCreateSelfPermit(); // create a fresh one break; case 'not-signed': await client.permits.getOrCreateSelfPermit(); // prompt the wallet to sign break; case 'invalid-schema': client.permits.removeActivePermit(); // stored payload is malformed break; } } const plaintext = await client .decryptForView(ctHash, FheTypes.Uint32) .execute(); ``` `ValidationResult.error` is the typed union `'invalid-schema' | 'expired' | 'not-signed' | null` — see [Permits → Validating permits](/client-sdk/guides/permits#validating-permits) for the full helper surface. If you prefer the throwing path: `PermitUtils.validate(permit)` raises plain `Error`s with messages `Permit is expired` / `Permit is not signed` (or a Zod schema error). These are not wrapped in `CofheError`, so use `err.message` rather than an error code to branch. # Permits Source: https://cofhe-docs.fhenix.zone/client-sdk/guides/permits Create and manage EIP-712 permits for decryption authorization Permits are EIP-712 signatures that authorize decryption of confidential data. The `issuer` field identifies who is accessing the data — the issuer must have been granted access on-chain via `FHE.allow(handle, address)`. When a permit is used, CoFHE validates it against the ACL contract to confirm that the issuer has access to the requested encrypted handle. Each permit includes a sealing keypair. The public key is sent to CoFHE so it can re-encrypt the data for the permit holder. The private key stays client-side and is used to unseal the returned data. ## When do you need a permit? * **`decryptForView`**: always requires a permit. * **`decryptForTx`**: depends on the contract's ACL policy for that `ctHash`. * If the policy allows anyone to decrypt, you can use `.withoutPermit()`. * If the policy restricts decryption, you must use `.withPermit(...)`. ## Prerequisites [Create and connect a client](/client-sdk/guides/client-setup). Permits are scoped to a **chainId + account**. ## Quick start The examples below show two approaches. The `client.permits` API is the recommended approach — it automatically signs permits with the connected wallet and manages the permit store. The `PermitUtils` API is a lower-level alternative that gives you direct control over signing and storage. ```typescript client.permits (recommended) theme={null} await client.connect(publicClient, walletClient); // Returns the active self permit if one exists, otherwise creates and signs a new one. const permit = await client.permits.getOrCreateSelfPermit(); ``` ```typescript PermitUtils theme={null} import { PermitUtils, setPermit, setActivePermitHash, } from '@cofhe/sdk/permits'; const permit = await PermitUtils.createSelfAndSign( { issuer: walletClient.account.address }, publicClient, walletClient ); // Manually store and activate the permit const chainId = await publicClient.getChainId(); const account = walletClient.account.address; setPermit(chainId, account, permit); setActivePermitHash(chainId, account, permit.hash); ``` After this, the active permit is picked up automatically: * `decryptForView(...).execute()` uses the active permit. * `decryptForTx(...).withPermit().execute()` uses the active permit. ## Permit types | Type | Who signs | Use case | | ----------- | ------------------------------------- | --------------------------------------------------------- | | `self` | issuer only | Decrypt your own data (most common) | | `sharing` | issuer only | A shareable "offer" created by the issuer for a recipient | | `recipient` | recipient (includes issuer signature) | The imported permit after the recipient signs it | * Permit `expiration` is a unix timestamp in **seconds**. The default is **7 days from creation**. * When a permit is created via `client.permits.*`, it is automatically stored and set as the active permit. ## Creating a self permit A self permit lets you decrypt data that was allowed to your address. ### createSelf ```typescript client.permits theme={null} await client.connect(publicClient, walletClient); const permit = await client.permits.createSelf({ issuer: walletClient.account.address, name: 'My self permit', }); permit.type; // 'self' permit.hash; // deterministic hash ``` ```typescript PermitUtils theme={null} import { PermitUtils } from '@cofhe/sdk/permits'; const permit = await PermitUtils.createSelfAndSign( { issuer: walletClient.account.address, name: 'My self permit', }, publicClient, walletClient ); permit.type; // 'self' permit.hash; // deterministic hash ``` ### getOrCreateSelfPermit Returns the active self permit if one exists. Otherwise creates and signs a new one. This is the recommended approach for most applications. ```typescript theme={null} await client.connect(publicClient, walletClient); const permit = await client.permits.getOrCreateSelfPermit(); permit.type; // 'self' ``` ## Sharing permits Sharing permits let an issuer delegate their ACL access to a recipient. The recipient can then decrypt the issuer's data without needing their own `FHE.allow`. The issuer creates a sharing permit specifying the recipient's address. ```typescript client.permits theme={null} await client.connect(publicClient, walletClient); const sharingPermit = await client.permits.createSharing({ issuer: walletClient.account.address, recipient, name: 'Share with recipient', }); ``` ```typescript PermitUtils theme={null} import { PermitUtils } from '@cofhe/sdk/permits'; const sharingPermit = await PermitUtils.createSharingAndSign( { issuer: walletClient.account.address, recipient, name: 'Share with recipient', }, publicClient, walletClient ); ``` Export the permit as a JSON blob and share it with the recipient. ```typescript theme={null} import { PermitUtils } from '@cofhe/sdk/permits'; const exported = PermitUtils.export(sharingPermit); ``` The exported JSON does not contain any sensitive data and can be shared via any channel. Do not share `serialize(permit)` output — serialization is meant for local persistence and includes the sealing private key. The recipient imports the exported JSON and signs it with their wallet. On import, a new sealing key is generated for the recipient. ```typescript client.permits theme={null} await client.connect(publicClient, walletClient); const recipientPermit = await client.permits.importShared(exported); recipientPermit.type; // 'recipient' recipientPermit.hash; ``` ```typescript PermitUtils theme={null} import { PermitUtils, setPermit, setActivePermitHash, } from '@cofhe/sdk/permits'; const recipientPermit = await PermitUtils.importSharedAndSign( exported, publicClient, walletClient ); const chainId = await publicClient.getChainId(); const account = walletClient.account.address; setPermit(chainId, account, recipientPermit); setActivePermitHash(chainId, account, recipientPermit.hash); ``` ## Active permit management The SDK tracks all stored permits and an **active permit hash** per `chainId + account`. Creating or importing a permit via `client.permits.*` automatically stores it and selects it as active. ### List stored permits ```typescript theme={null} const permits = client.permits.getPermits(); Object.keys(permits); // permit hashes ``` ### Read / select the active permit ```typescript theme={null} const active = client.permits.getActivePermit(); active?.hash; client.permits.selectActivePermit(somePermitHash); ``` ### Removing permits ```typescript theme={null} client.permits.removePermit(permitHash); client.permits.removeActivePermit(); ``` ## Validating permits Since `@cofhe/sdk@0.5.0`, `PermitUtils.validate` enforces the **full** check: schema + signed + not-expired. The decrypt flows (`decryptForView`, `decryptForTx` with `.withPermit(...)`) call this for you and surface failures as typed errors — you only need to validate manually when you want to inspect or filter permits before using them. ### Throwing helpers — `PermitUtils.*` | Function | What it checks | Behavior on failure | | ------------------------------------ | ------------------------------------------ | --------------------------------------------------------------------- | | `PermitUtils.validate(permit)` | Schema **and** signed **and** not-expired. | Throws (`Permit is expired` / `Permit is not signed` / schema error). | | `PermitUtils.validateSchema(permit)` | Schema only (shape + invariants). | Throws on schema failure; does **not** check expiry or signatures. | Use `validateSchema` when you've just received a permit from the wire (e.g. an imported sharing permit) and want to reject malformed payloads without yet caring about expiry. ```typescript theme={null} import { PermitUtils } from '@cofhe/sdk/permits'; try { PermitUtils.validate(permit); // permit is schema-valid, signed, and not expired } catch (err) { // err.message is "Permit is expired" / "Permit is not signed" / a Zod schema error } ``` ### Non-throwing helpers — `ValidationUtils.*` For inspection without exception handling, use the `ValidationUtils` helpers. They return a typed `ValidationResult`: ```typescript theme={null} import { ValidationUtils } from '@cofhe/sdk/permits'; const result = ValidationUtils.isValid(permit); result.valid; // boolean result.error; // 'invalid-schema' | 'expired' | 'not-signed' | null ``` | Function | Returns | Use case | | ----------------------------------------------- | ------------------ | -------------------------------------------------------------------------- | | `ValidationUtils.isValid(permit)` | `ValidationResult` | Full check (schema + signed + not-expired) without throwing. | | `ValidationUtils.isSignedAndNotExpired(permit)` | `ValidationResult` | Skip the schema parse if you already validated the shape. | | `ValidationUtils.isSigned(permit)` | `boolean` | "Does it carry a signature on the issuer / recipient side as appropriate?" | | `ValidationUtils.isExpired(permit)` | `boolean` | Compare `permit.expiration` to current time. | Pattern-match on `result.error` to render a precise UI message: ```typescript theme={null} switch (ValidationUtils.isValid(permit).error) { case 'expired': return 'This permit has expired — please re-sign.'; case 'not-signed': return 'Permit is awaiting signature.'; case 'invalid-schema': return 'Imported permit is malformed.'; case null: return null; } ``` ## Persistence and security * The SDK persists permits in a store keyed by `chainId + account`. * In web environments, this store uses `localStorage` under the key `cofhesdk-permits`. * A stored permit includes the **sealing private key**. Treat it like a secret. * Never share serialized permits with other users. * To share access, use `PermitUtils.export(...)` which strips sensitive fields. # Writing Decrypt Result to Contract Source: https://cofhe-docs.fhenix.zone/client-sdk/guides/writing-decrypt-result Submit a decryptForTx result on-chain for verification or publishing This page covers the "decrypt → write tx" flow after you run [`decryptForTx`](/client-sdk/guides/decrypt-to-tx): you take `{ ctHash, decryptedValue, signature }` and submit a transaction that your contract can verify. There are two common patterns: * **Publish**: call `FHE.publishDecryptResult(ctHash, plaintext, signature)` so other contracts/users can reference the published result. * **Verify-only**: call `FHE.verifyDecryptResult(ctHash, plaintext, signature)` inside your contract without publishing globally. ## Prerequisites 1. Run [`decryptForTx`](/client-sdk/guides/decrypt-to-tx) and get a result object with `ctHash`, `decryptedValue`, and `signature`. 2. Ensure the Solidity parameter type matches your encrypted type. `decryptedValue` is a `bigint`. If your Solidity function expects a smaller integer type (e.g. `uint32`), make sure the value is within range. ## Publish the decrypt result on-chain The intended consumer of `decryptForTx` is an on-chain verifier such as `FHE.publishDecryptResult(...)`. In practice, you publish the result by calling a function on **your contract** that invokes `FHE.publishDecryptResult` internally. ```solidity Solidity theme={null} import '@fhenixprotocol/cofhe-contracts/FHE.sol'; // Example wrapper (adjust plaintext/result type to match your encrypted type). function publishDecryptResult( bytes32 ctHash, uint32 plaintext, bytes calldata signature ) external { FHE.publishDecryptResult(ctHash, plaintext, signature); } ``` ```typescript TypeScript theme={null} // `decryptResult` is returned by `decryptForTx(...).execute()` const tx = await myContract.publishDecryptResult( decryptResult.ctHash, decryptResult.decryptedValue, decryptResult.signature ); await tx.wait(); ``` ## Verify a decrypt result signature (without publishing) Some protocols don't need (or don't want) to publish the decrypt result globally — they only need to verify that the provided plaintext and signature match a specific handle (`ctHash`). For example, an "unshield" flow can accept `(ctHash, plaintext, signature)` and only proceed if the signature is valid: ```solidity theme={null} import '@fhenixprotocol/cofhe-contracts/FHE.sol'; function unshield( bytes32 ctHash, uint32 plaintext, bytes calldata signature ) external { require( FHE.verifyDecryptResult(ctHash, plaintext, signature), 'Invalid decrypt signature' ); // ...continue with protocol logic... } ``` If you prefer to publish the result (so it can be reused elsewhere), use `FHE.publishDecryptResult(...)` instead. # Writing Encrypted Data to Contract Source: https://cofhe-docs.fhenix.zone/client-sdk/guides/writing-encrypted-data Encrypt plaintext values and pass them directly into a contract call This page covers the "encrypt → write tx" flow: encrypt plaintext values into `InE*` structs and pass them directly into a contract call. `encryptInputs` returns `EncryptedItemInput` objects that match the Solidity `InE*` input structs. The on-chain CoFHE library validates the verifier signature before the contract can use the ciphertext. ## Flow 1. Ensure your contract function accepts encrypted `InE*` parameters. 2. Encrypt the plaintext values with [`encryptInputs`](/client-sdk/guides/encrypting-inputs). 3. Send a transaction and pass the encrypted structs as the `InE*` arguments. ## Prerequisites 1. [Create and connect a client](/client-sdk/guides/client-setup). 2. Your contract function must accept encrypted `InE*` structs. The encrypted type you choose in TypeScript must match the Solidity parameter type: * `Encryptable.uint32(...)` → `InEuint32` * `Encryptable.bool(...)` → `InEbool` * `Encryptable.address(...)` → `InEaddress` ## Example: encrypt and call a contract ```solidity Solidity theme={null} // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.28; import '@fhenixprotocol/cofhe-contracts/FHE.sol'; contract EncryptedCounter { euint32 public count; function setCount(InEuint32 memory _inCount) external { count = FHE.asEuint32(_inCount); FHE.allowThis(count); FHE.allowSender(count); } } ``` ```typescript TypeScript (viem) theme={null} import { Encryptable, assertCorrectEncryptedItemInput } from '@cofhe/sdk'; import { parseAbi } from 'viem'; import { sepolia } from 'viem/chains'; const encryptedCounterAbi = parseAbi([ 'function setCount((uint256 ctHash, uint8 securityZone, uint8 utype, bytes signature) _inCount)', ]); // 1) Encrypt right before sending the transaction const [inCount] = await cofheClient .encryptInputs([Encryptable.uint32(42n)]) .execute(); assertCorrectEncryptedItemInput(inCount); // 2) Pass the encrypted struct as the InE* argument const hash = await walletClient.writeContract({ chain: sepolia, account, address: encryptedCounterAddress, abi: encryptedCounterAbi, functionName: 'setCount', args: [inCount], }); await publicClient.waitForTransactionReceipt({ hash }); ``` ```typescript TypeScript (ethers) theme={null} import { Encryptable } from '@cofhe/sdk'; // 1) Encrypt const [inCount] = await cofheClient .encryptInputs([Encryptable.uint32(42n)]) .execute(); // 2) Send the transaction const tx = await contract.setCount(inCount); await tx.wait(); ``` ## Common pitfalls * **Wrong `Encryptable` type**: the `Encryptable.*` factory must match the Solidity parameter type (`InEuint32` vs `InEuint64`, etc). * **Wrong account / chain**: encrypted inputs are authorized for a specific `account + chainId`. If you encrypt under the wrong wallet/network, the contract call may revert. * **ABI struct shape mismatch**: if you hand-write an ABI, ensure the tuple fields are `(ctHash, securityZone, utype, signature)` in the same order your client library expects. # Client Source: https://cofhe-docs.fhenix.zone/client-sdk/hardhat-plugin/client Create and connect a CofheClient in Hardhat tests The plugin extends `hre` with `hre.cofhe`, providing three ways to create and connect a `CofheClient` in your Hardhat tests. ## Batteries included (recommended) `hre.cofhe.createClientWithBatteries(signer?)` is the one-liner that handles everything: 1. Creates a CoFHE config with `environment: 'hardhat'` and `supportedChains: [hardhat]` 2. Creates a `CofheClient` 3. Connects it using the provided Hardhat signer (defaults to the first signer) 4. Generates a self-usage permit for the signer ```typescript theme={null} import hre from 'hardhat'; const [signer] = await hre.ethers.getSigners(); const cofheClient = await hre.cofhe.createClientWithBatteries(signer); cofheClient.connected; // true ``` `createClientWithBatteries` generates a self-permit automatically, so most test operations (encrypting inputs, decrypting for view, decrypting for tx) work immediately without any extra setup. If `signer` is omitted, the first signer from `hre.ethers.getSigners()` is used. ## Manual setup For more control — custom config options, multiple signers, or adjusting `encryptDelay` — you can set up the client step by step. ```typescript theme={null} import hre from 'hardhat'; import { hardhat } from '@cofhe/sdk/chains'; const config = await hre.cofhe.createConfig({ supportedChains: [hardhat], }); ``` `hre.cofhe.createConfig` wraps `createCofheConfig` from `@cofhe/sdk/node` with two Hardhat-specific additions: * Sets `environment: 'hardhat'` automatically * Defaults `mocks.encryptDelay` to `0` so tests run without artificial wait times ```typescript theme={null} const cofheClient = hre.cofhe.createClient(config); ``` ```typescript theme={null} await hre.cofhe.connectWithHardhatSigner(cofheClient, signer); ``` `connectWithHardhatSigner` uses `HardhatSignerAdapter` under the hood to convert a `HardhatEthersSigner` into the viem `PublicClient` + `WalletClient` pair that the SDK expects. ## Low-level adapter If you need direct access to the underlying viem clients, call the adapter directly: ```typescript theme={null} import hre from 'hardhat'; const [signer] = await hre.ethers.getSigners(); const { publicClient, walletClient } = await hre.cofhe.hardhatSignerAdapter(signer); await cofheClient.connect(publicClient, walletClient); ``` ## Using the client Once connected, the client works identically to the standard SDK client. See: * [Encrypting Inputs](/client-sdk/guides/encrypting-inputs) * [Decrypt to View](/client-sdk/guides/decrypt-to-view) * [Decrypt to Transact](/client-sdk/guides/decrypt-to-tx) * [Permits](/client-sdk/guides/permits) # Getting Started Source: https://cofhe-docs.fhenix.zone/client-sdk/hardhat-plugin/getting-started Set up @cofhe/hardhat-plugin for local FHE contract development and testing `@cofhe/hardhat-plugin` extends Hardhat with everything you need to build and test FHE-enabled contracts locally — mock contracts, a pre-configured CoFHE client, and test utilities. ## What the plugin provides * **Mock contracts** deployed automatically on every `npx hardhat test` / `npx hardhat node` run, simulating the full CoFHE coprocessor stack on the Hardhat network. * **`hre.cofhe`** — a namespaced API for creating and connecting CoFHE clients with Hardhat signers. * **`hre.cofhe.mocks`** — utilities for reading raw plaintext values and interacting with mock contracts directly in tests. * **Pre-configured networks** — `localcofhe`, `eth-sepolia`, and `arb-sepolia` are injected automatically. ## Installation ```bash npm theme={null} npm install @cofhe/hardhat-plugin @cofhe/sdk @cofhe/mock-contracts @fhenixprotocol/cofhe-contracts ``` ```bash pnpm theme={null} pnpm add @cofhe/hardhat-plugin @cofhe/sdk @cofhe/mock-contracts @fhenixprotocol/cofhe-contracts ``` ```bash yarn theme={null} yarn add @cofhe/hardhat-plugin @cofhe/sdk @cofhe/mock-contracts @fhenixprotocol/cofhe-contracts ``` ```typescript hardhat.config.ts theme={null} import '@cofhe/hardhat-plugin'; export default { solidity: '0.8.28', }; ``` That's it. The plugin automatically deploys the mock contracts before every test run. ## Configuration The plugin adds an optional `cofhe` key to your Hardhat config: ```typescript hardhat.config.ts theme={null} import '@cofhe/hardhat-plugin'; export default { solidity: '0.8.28', cofhe: { logMocks: true, // log FHE ops to the console (default: true) gasWarning: true, // warn when mock gas usage is high (default: true) mocksDeployVerbosity: 'v', // mock deployment log verbosity (default: 'v') }, }; ``` `mocksDeployVerbosity` controls how much output the plugin prints while deploying the mock contracts at the start of each `npx hardhat test` / `npx hardhat node` run: | Value | Output | | ------ | ----------------------------------------------------------------------------- | | `''` | Silent — no deploy lines. | | `'v'` | Single summary line per mock (default since `0.5.0`). | | `'vv'` | Full per-contract deployment log, including each contract's deployed address. | This is independent of `logMocks`, which controls **runtime** FHE-op logging (printed when your tests actually call `FHE.*`). ## Pre-configured networks The following networks are injected automatically. You can override any of them by defining the same key under `networks` in your config. | Network | URL | Chain ID | | ------------- | --------------------------- | ---------- | | `localcofhe` | `http://127.0.0.1:42069` | — | | `eth-sepolia` | Ethereum Sepolia public RPC | `11155111` | | `arb-sepolia` | Arbitrum Sepolia public RPC | `421614` | For testnets, set `PRIVATE_KEY` (and optionally `SEPOLIA_RPC_URL` / `ARBITRUM_SEPOLIA_RPC_URL`) in your environment. ## Auto-deployment Mock contracts are deployed automatically before: * `npx hardhat test` * `npx hardhat node` To skip auto-deployment (e.g., when running tests only against an external RPC): ```bash theme={null} COFHE_SKIP_MOCKS_DEPLOY=1 npx hardhat test ``` ## Next steps * [Client](/client-sdk/hardhat-plugin/client) — create and connect a CoFHE client in tests. * [Mock Contracts](/client-sdk/hardhat-plugin/mock-contracts) — read plaintext values and interact with mock contracts. * [Logging](/client-sdk/hardhat-plugin/logging) — inspect FHE operations in test output. * [Testing](/client-sdk/hardhat-plugin/testing) — end-to-end test patterns. # Logging Source: https://cofhe-docs.fhenix.zone/client-sdk/hardhat-plugin/logging Inspect FHE operations in your Hardhat test output The mock contracts log every FHE operation to the console. This makes it easy to inspect what your contracts are doing under the hood during tests. ## What gets logged Each FHE operation emits a formatted log entry showing the operation name, the input and output operand hashes (truncated), and the security zone: ``` ┌──────────────────┬────────────────────────────────────────────────── │ [COFHE-MOCKS] │ "counter.increment()" logs: ├──────────────────┴────────────────────────────────────────────────── ├ FHE.add | euint32(4473..3424)[0] + euint32(1157..3648)[1] => euint32(1106..1872)[1] ├ FHE.allowThis | euint32(1106..1872)[1] -> 0x663f..6602 ├ FHE.allow | euint32(1106..1872)[1] -> 0x3c44..93bc └───────────────────────────────────────────────────────────────────── ``` ## `withLogs(name, fn)` — recommended Wraps a block of code with logging enabled and prints a labeled box around the output. The `name` appears as the header so you can identify which call produced which operations. ```typescript theme={null} import hre from 'hardhat'; await hre.cofhe.mocks.withLogs('counter.increment()', async () => { await counter.increment(); }); ``` `withLogs` enables logging before the closure runs and disables it after, so only operations from within that block appear in the output. ## `enableLogs()` / `disableLogs()` — manual For finer-grained control, you can enable and disable logging manually: ```typescript theme={null} import hre from 'hardhat'; await hre.cofhe.mocks.enableLogs('counter.increment()'); await counter.increment(); await hre.cofhe.mocks.disableLogs(); ``` `enableLogs` accepts an optional label string. If provided, it prints a labeled header immediately — useful when you want to mark a section of output at a known point. ## Default logging behavior Logging is **enabled by default**. You can turn it off globally in your Hardhat config: ```typescript hardhat.config.ts theme={null} import '@cofhe/hardhat-plugin'; export default { solidity: '0.8.28', cofhe: { logMocks: false, // disable FHE op logs globally }, }; ``` You can also toggle logging for a specific test run using the Hardhat task: ```bash theme={null} npx hardhat task:cofhe-mocks:setlogops --enable true npx hardhat task:cofhe-mocks:setlogops --enable false ``` # Mock Contracts Source: https://cofhe-docs.fhenix.zone/client-sdk/hardhat-plugin/mock-contracts Interact with mock CoFHE contracts and read plaintext values in tests The plugin deploys a suite of mock contracts that simulate the full CoFHE coprocessor stack on the Hardhat network. This lets you develop and test FHE contracts without running the off-chain FHE engine. ## What the mocks simulate | Contract | Role | | ---------------------- | ------------------------------------------------------------------------------------- | | `MockTaskManager` | Manages FHE operations; stores plaintext values on-chain for testing | | `MockACL` | Access control for encrypted handles | | `MockZkVerifier` | Simulates ZK proof verification for encrypted inputs | | `MockThresholdNetwork` | Handles decryption requests | | `TestBed` | Helper contract for testing — exposes trivial value setters and a `numberHash` getter | The SDK automatically detects when it's running against the mock environment (by checking bytecode at the `MockZkVerifier` fixed address) and adapts its behavior accordingly — ZK proof generation is skipped and verification is handled by the mock contracts. ## Auto-deployment Mock contracts are deployed automatically before every `npx hardhat test` and `npx hardhat node` run. To skip auto-deployment: ```bash theme={null} COFHE_SKIP_MOCKS_DEPLOY=1 npx hardhat test ``` You only need `COFHE_SKIP_MOCKS_DEPLOY=1` if your tests exclusively target an external RPC and don't use the in-process Hardhat network at all. You can also deploy mock contracts manually via the Hardhat task: ```bash theme={null} npx hardhat task:cofhe-mocks:deploy npx hardhat task:cofhe-mocks:deploy --deployTestBed false # skip TestBed npx hardhat task:cofhe-mocks:deploy --silent true # suppress output ``` Or programmatically from a test or script: ```typescript theme={null} import hre from 'hardhat'; await hre.cofhe.mocks.deployMocks(); ``` ## Accessing mock contracts `hre.cofhe.mocks` exposes typed accessors for each mock contract: ```typescript theme={null} import hre from 'hardhat'; const taskManager = await hre.cofhe.mocks.getMockTaskManager(); const acl = await hre.cofhe.mocks.getMockACL(); const thresholdNetwork = await hre.cofhe.mocks.getMockThresholdNetwork(); const zkVerifier = await hre.cofhe.mocks.getMockZkVerifier(); const testBed = await hre.cofhe.mocks.getTestBed(); ``` ## Reading plaintext values Because `MockTaskManager` stores plaintext values on-chain, you can read the underlying plaintext of any encrypted handle directly in tests — no permit needed. ### `getPlaintext(ctHash)` Returns the plaintext `bigint` for a given ciphertext hash: ```typescript theme={null} import hre from 'hardhat'; import { expect } from 'chai'; const testBed = await hre.cofhe.mocks.getTestBed(); await testBed.setNumberTrivial(7); const ctHash = await testBed.numberHash(); const plaintext = await hre.cofhe.mocks.getPlaintext(ctHash); expect(plaintext).to.equal(7n); ``` ### `expectPlaintext(ctHash, expectedValue)` Assertion shorthand — wraps `getPlaintext` with a Chai `expect`: ```typescript theme={null} import hre from 'hardhat'; const testBed = await hre.cofhe.mocks.getTestBed(); await testBed.setNumberTrivial(7); const ctHash = await testBed.numberHash(); await hre.cofhe.mocks.expectPlaintext(ctHash, 7n); ``` `getPlaintext` and `expectPlaintext` only work on the Hardhat network (where `MockTaskManager` stores plaintexts). They will throw on `localcofhe` or any real network. # Testing Source: https://cofhe-docs.fhenix.zone/client-sdk/hardhat-plugin/testing Common patterns for writing Hardhat tests with the CoFHE plugin This page shows the common patterns for writing Hardhat tests with the CoFHE plugin. ## Test setup Use `hre.cofhe.createClientWithBatteries` in a `before` hook. It creates and connects a fully configured `CofheClient` — including a self-permit — so the client is ready for every test in the suite: ```typescript theme={null} import hre from 'hardhat'; import { CofheClient } from '@cofhe/sdk'; import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; let cofheClient: CofheClient; let signer: HardhatEthersSigner; before(async () => { [signer] = await hre.ethers.getSigners(); cofheClient = await hre.cofhe.createClientWithBatteries(signer); }); ``` See [Client](/client-sdk/hardhat-plugin/client) for manual setup options. ## Encrypt → store → decrypt The core test loop: encrypt a value, pass it to a contract, then decrypt the stored handle. ```typescript theme={null} import { Encryptable, FheTypes } from '@cofhe/sdk'; import { expect } from 'chai'; // 1. Encrypt the input const encrypted = await cofheClient .encryptInputs([Encryptable.uint32(100n)]) .execute(); // 2. Send to contract const tx = await testContract.setValue(encrypted[0]); await tx.wait(); // 3. Read the stored handle const ctHash = await testContract.storedValue(); // 4. Decrypt for display const decrypted = await cofheClient .decryptForView(ctHash, FheTypes.Uint32) .execute(); expect(decrypted).to.equal(100n); ``` ## Reading plaintext directly In tests you can bypass the normal decrypt flow and read the raw plaintext stored by the mock contracts. This is useful for asserting contract state without needing a permit: ```typescript theme={null} import hre from 'hardhat'; // Get raw plaintext value const plaintext = await hre.cofhe.mocks.getPlaintext(ctHash); // Or use the assertion shorthand await hre.cofhe.mocks.expectPlaintext(ctHash, 100n); ``` See [Mock Contracts](/client-sdk/hardhat-plugin/mock-contracts) for details. ## Permits `createClientWithBatteries` pre-generates a self-permit, so `decryptForView` and `decryptForTx().withPermit()` work immediately. For tests that need named permits or multiple signers, create them explicitly: ```typescript theme={null} import { PermitUtils } from '@cofhe/sdk/permits'; const permit = await cofheClient.permits.createSelf({ issuer: signer.address, name: 'My Test Permit', }); // Select it as the active permit const permitHash = PermitUtils.getHash(permit); cofheClient.permits.selectActivePermit(permitHash); ``` Alternatively, create a separate client for each signer: ```typescript theme={null} import hre from 'hardhat'; const [bob, alice] = await hre.ethers.getSigners(); const bobClient = await hre.cofhe.createClientWithBatteries(bob); const aliceClient = await hre.cofhe.createClientWithBatteries(alice); ``` ## `decryptForTx` patterns [`decryptForTx`](/client-sdk/guides/decrypt-to-tx) returns a `{ ctHash, decryptedValue, signature }` tuple for on-chain submission. The permit mode must be selected explicitly. ### Globally allowed values (`.withoutPermit()`) When a contract calls `FHE.allowPublic(handle)`, anyone can decrypt without a permit: ```typescript theme={null} import { expect } from 'chai'; const result = await cofheClient .decryptForTx(publicCtHash) .withoutPermit() .execute(); expect(result.decryptedValue).to.equal(55n); ``` ### Access-controlled values (`.withPermit()`) For handles restricted by ACL policy, supply a permit: ```typescript Explicit permit theme={null} const result = await cofheClient .decryptForTx(ctHash) .withPermit(permit) .execute(); expect(result.decryptedValue).to.equal(99n); ``` ```typescript Active permit theme={null} // resolves the active permit automatically const result = await cofheClient .decryptForTx(ctHash) .withPermit() .execute(); expect(result.decryptedValue).to.equal(99n); ``` ### Submitting the result on-chain Pass the result directly to your contract: ```typescript theme={null} await myContract.revealValue( result.ctHash, result.decryptedValue, result.signature ); ``` For a full walkthrough of `decryptForTx`, see [Decrypt to Transact](/client-sdk/guides/decrypt-to-tx). # Installation Source: https://cofhe-docs.fhenix.zone/client-sdk/introduction/installation Install and configure @cofhe/sdk for your project ## Prerequisites * Node.js 18+ * TypeScript 5+ * Viem 2+ ## Install packages ```bash npm theme={null} npm install @cofhe/sdk@^0.5.2 @fhenixprotocol/cofhe-contracts@^0.1.3 ``` ```bash pnpm theme={null} pnpm add @cofhe/sdk@^0.5.2 @fhenixprotocol/cofhe-contracts@^0.1.3 ``` ```bash yarn theme={null} yarn add @cofhe/sdk@^0.5.2 @fhenixprotocol/cofhe-contracts@^0.1.3 ``` | Package | Version | Purpose | | --------------------------------- | -------- | ----------------------------------------------------------- | | `@cofhe/sdk` | `^0.5.2` | Client-side encryption, decryption, and permit management | | `@fhenixprotocol/cofhe-contracts` | `^0.1.3` | `FHE.sol` — the Solidity library imported by your contracts | `@fhenixprotocol/cofhe-contracts@0.1.3` requires `@cofhe/sdk` version `>= 0.5.1`. Latest published is `0.5.2`. ## For Hardhat projects If you are using Hardhat for development and testing, also install the plugin: ```bash npm theme={null} npm install @cofhe/hardhat-plugin@^0.5.2 @cofhe/sdk@^0.5.2 @fhenixprotocol/cofhe-contracts@^0.1.3 ``` ```bash pnpm theme={null} pnpm add @cofhe/hardhat-plugin@^0.5.2 @cofhe/sdk@^0.5.2 @fhenixprotocol/cofhe-contracts@^0.1.3 ``` ```bash yarn theme={null} yarn add @cofhe/hardhat-plugin@^0.5.2 @cofhe/sdk@^0.5.2 @fhenixprotocol/cofhe-contracts@^0.1.3 ``` | Package | Version | Purpose | | --------------------------------- | -------- | ---------------------------------------------------------------------- | | `@cofhe/hardhat-plugin` | `^0.5.2` | Extends Hardhat with `hre.cofhe`, deploys mock contracts automatically | | `@cofhe/sdk` | `^0.5.2` | Client-side encryption, decryption, and permit management | | `@fhenixprotocol/cofhe-contracts` | `^0.1.3` | `FHE.sol` — the Solidity library imported by your contracts | See the [Hardhat Plugin Getting Started](/client-sdk/hardhat-plugin/getting-started) guide for configuration details. ## For Foundry projects If you are using Foundry for development and testing, install the Foundry plugin and its peer packages as dev dependencies: ```bash npm theme={null} npm install -D @cofhe/foundry-plugin@^0.5.2 @cofhe/mock-contracts@^0.5.2 @fhenixprotocol/cofhe-contracts@^0.1.3 @openzeppelin/contracts forge install foundry-rs/forge-std ``` ```bash pnpm theme={null} pnpm add -D @cofhe/foundry-plugin@^0.5.2 @cofhe/mock-contracts@^0.5.2 @fhenixprotocol/cofhe-contracts@^0.1.3 @openzeppelin/contracts forge install foundry-rs/forge-std ``` ```bash yarn theme={null} yarn add -D @cofhe/foundry-plugin@^0.5.2 @cofhe/mock-contracts@^0.5.2 @fhenixprotocol/cofhe-contracts@^0.1.3 @openzeppelin/contracts forge install foundry-rs/forge-std ``` | Package | Version | Purpose | | --------------------------------- | -------- | ------------------------------------------------------------------------ | | `@cofhe/foundry-plugin` | `^0.5.2` | `CofheTest` and `CofheClient` — Solidity test base and per-user SDK shim | | `@cofhe/mock-contracts` | `^0.5.2` | Mock CoFHE contracts used by the Foundry plugin | | `@fhenixprotocol/cofhe-contracts` | `^0.1.3` | `FHE.sol` — the Solidity library imported by your contracts | The Foundry plugin uses Solidity-only abstractions — no `@cofhe/sdk` (JS) needed for tests. See the [Foundry Plugin Getting Started](/client-sdk/foundry-plugin/getting-started) guide for `foundry.toml` and `remappings.txt` setup. ## Runtime entrypoints Import from the entrypoint that matches your runtime: ```typescript theme={null} // Browser apps (React, Next.js, etc.) import { createCofheConfig, createCofheClient } from '@cofhe/sdk/web'; // Node.js scripts, backends, Hardhat tests import { createCofheConfig, createCofheClient } from '@cofhe/sdk/node'; // Shared types (works in any runtime) import { Encryptable, FheTypes } from '@cofhe/sdk'; ``` ## Next steps * [Quick Start](/client-sdk/quick-start) — write your first FHE contract and test * [Client Setup](/client-sdk/guides/client-setup) — configure and connect the SDK client # Mental Model Source: https://cofhe-docs.fhenix.zone/client-sdk/introduction/mental-model Understand how data flows through FHE-enabled dApps using the @cofhe/sdk To understand how `@cofhe/sdk` fits into the Fhenix framework, you'll explore a simple mental model using a Counter smart contract example. This will show you how data flows through FHE-enabled dApps—from encryption to computation to decryption. ## The Counter Example Imagine a smart contract called **Counter** where each user has their own private counter. Users can increment their counter and read its value with complete privacy—no one, including the smart contract itself, can see the actual counter values. ### Key Concepts * **Public Key** = A lock that anyone can use to seal data * **Private Key** = The unique key to unlock sealed data * **CoFHE Co-Processor** = Fhenix's off-chain service that handles FHE operations * **Ciphertext** = Encrypted data that can be computed on without decryption When a user wants to add `5` to their counter, the data must first be encrypted before being sent to the smart contract. **What happens:** 1. The user's plaintext value `5` is encrypted using `client.encryptInputs([Encryptable.uint32(5n)]).execute()` 2. The SDK generates a ZK proof and submits the encrypted value to the CoFHE verifier 3. The returned `EncryptedItemInput` is sent to the smart contract on-chain 4. The blockchain sees only encrypted data, never the actual value `5` ### The "Locked Box" Analogy Think of this as placing the value `5` in a box and locking it with the CoFHE co-processor's public key. The locked box (ciphertext) can be sent to the smart contract, but no one can see what's inside without the private key. Once the encrypted data reaches the smart contract, FHE magic happens. The smart contract can perform arithmetic operations directly on the ciphertext without ever decrypting it. **What happens:** 1. The smart contract receives the encrypted value 2. It retrieves the user's encrypted counter from storage (also encrypted) 3. Using FHE operations, it adds the encrypted values together 4. The result is stored as encrypted data 5. **Critical**: At no point does the smart contract, blockchain, or anyone else see the actual numbers ### FHE Computation Magic This is where Fully Homomorphic Encryption shines. The CoFHE co-processor enables the smart contract to: * Add encrypted values together * Compare encrypted values * Perform other arithmetic operations * All while the data remains encrypted and private When a user wants to read their counter value, they use one of the SDK's two decryption methods depending on their goal. **For UI display (`decryptForView`):** 1. The user reads the encrypted handle (`ctHash`) from the contract 2. A permit authorizes decryption — created via `client.permits.getOrCreateSelfPermit()` 3. The SDK requests re-encryption from the Threshold Network using the permit's sealing key 4. The plaintext is returned locally for display **For on-chain use (`decryptForTx`):** 1. The user reads the encrypted handle (`ctHash`) from the contract 2. The SDK requests decryption from the Threshold Network 3. The plaintext and a verifiable signature are returned 4. The signature can be verified on-chain via `FHE.verifyDecryptResult(...)` ### The "Lock Exchange" Analogy This is like exchanging locks on the box: * The box starts locked with the CoFHE co-processor's lock * The user sends their own lock to the co-processor (via the permit's sealing key) * The co-processor removes its lock and applies the user's lock * The box remains locked throughout, but now only the user can open it * The data remains private at every step ## The Complete Flow ```mermaid theme={null} sequenceDiagram participant User participant SDK as "@cofhe/sdk (Client)" participant Contract as "Smart Contract" participant CoFHE as "CoFHE Co-Processor" participant Blockchain as "Blockchain" Note over User,SDK: Step 1: Encrypting Input Data User->>SDK: Add value 5 to counter SDK->>SDK: encryptInputs([Encryptable.uint32(5n)]) SDK->>CoFHE: Submit ZK proof + encrypted value CoFHE-->>SDK: Signed EncryptedItemInput SDK->>Blockchain: Send transaction with encrypted value Blockchain->>Contract: increment(encryptedValue) Note over Contract,Blockchain: Step 2: Performing Computations Contract->>Contract: Retrieve encrypted counter Contract->>Contract: FHE.add(encryptedCounter, encryptedValue) Contract->>Blockchain: Store encrypted result Blockchain-->>Contract: Transaction confirmed Note over User,CoFHE: Step 3: Retrieving Encrypted Output User->>SDK: Read counter value SDK->>Blockchain: Request encrypted counter Blockchain->>Contract: getCounter(userAddress) Contract-->>SDK: ctHash (encrypted handle) SDK->>SDK: Use permit with sealing key SDK->>CoFHE: decryptForView(ctHash, FheTypes.Uint32) CoFHE->>CoFHE: Re-encrypt with user's sealing key CoFHE-->>SDK: Re-encrypted data SDK->>SDK: Unseal with private key SDK-->>User: Counter value: 5 ``` ## Key Takeaways 1. **Encryption happens client-side**: The SDK encrypts data with ZK proofs before it reaches the blockchain 2. **Computation happens on-chain**: Smart contracts perform operations on encrypted data via `FHE.sol` 3. **FHE enables privacy-preserving computation**: The blockchain never sees plaintext values 4. **Permits enable access control**: EIP-712 signed permits authorize who can decrypt specific data 5. **Two decryption paths**: `decryptForView` for UI display, `decryptForTx` for on-chain verification This architecture ensures that sensitive data remains private throughout its entire lifecycle—from input to computation to output—while still enabling powerful decentralized applications. # Migrating from cofhejs Source: https://cofhe-docs.fhenix.zone/client-sdk/introduction/migrating-from-cofhejs Side-by-side migration guide from cofhejs to @cofhe/sdk `@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. ### Why migrate? * **Explicit API** — no more implicit initialization or auto-generated permits. Every action is opt-in. * **Builder pattern** — `encryptInputs`, `decryptForView`, and `decryptForTx` use a chainable builder so you can set overrides (account, chain, callbacks) before calling `.execute()`. * **`decryptForTx` feature** — `cofhejs` 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`: ```bash theme={null} 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. | | `cofhejs` | `@cofhe/sdk` | | ------------------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | **Entry** | `cofhejs.initializeWithEthers(...)` / `cofhejs.initializeWithViem(...)` | `createCofheConfig(...)` → `createCofheClient(config)` → `client.connect(...)` | | **Key fetching** | Immediate (during init) | Deferred (first `encryptInputs` call) | | **WASM init** | Immediate (during init) | Deferred (first `encryptInputs` call) | | **Environment** | `"LOCAL"` / `"TESTNET"` / `"MAINNET"` string | Chain objects via `supportedChains: [chains.sepolia]` | | **Provider format** | Ethers provider/signer or viem clients | Always viem clients (use adapters for ethers) | ### Before (cofhejs) ```typescript Ethers theme={null} import { cofhejs } from 'cofhejs/node'; await cofhejs.initializeWithEthers({ ethersProvider: provider, ethersSigner: signer, environment: 'TESTNET', }); ``` ```typescript Viem theme={null} import { cofhejs } from 'cofhejs/web'; await cofhejs.initializeWithViem({ viemClient: publicClient, viemWalletClient: walletClient, environment: 'TESTNET', }); ``` ### After (@cofhe/sdk) ```typescript Web (viem) theme={null} 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); ``` ```typescript Node (viem) theme={null} import { createCofheConfig, createCofheClient } from '@cofhe/sdk/node'; import { chains } from '@cofhe/sdk/chains'; const config = createCofheConfig({ supportedChains: [chains.sepolia], }); const client = createCofheClient(config); await client.connect(publicClient, walletClient); ``` ```typescript Ethers v6 (via adapter) theme={null} import { createCofheConfig, createCofheClient } from '@cofhe/sdk/web'; import { Ethers6Adapter } from '@cofhe/sdk/adapters'; import { chains } from '@cofhe/sdk/chains'; const config = createCofheConfig({ supportedChains: [chains.sepolia], }); const client = createCofheClient(config); const { publicClient, walletClient } = await Ethers6Adapter( provider, signer ); await client.connect(publicClient, walletClient); ``` ## 2. Encrypting inputs `cofhejs.encrypt(...)` is replaced by a builder: `client.encryptInputs([...]).execute()`. | | `cofhejs` | `@cofhe/sdk` | | --------------------- | ------------------------------------------------ | ------------------------------------------------------------ | | **Function** | `cofhejs.encrypt([...], callback)` | `client.encryptInputs([...]).execute()` | | **Return value** | `Result` with `.success` / `.data` / `.error` | Direct value (throws `CofheError` on failure) | | **Progress callback** | Second argument to `encrypt` | `.onStep(callback)` on the builder | | **Overrides** | Not available | `.setAccount(...)`, `.setChainId(...)`, `.setUseWorker(...)` | ### Before (cofhejs) ```typescript theme={null} 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) ```typescript theme={null} 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(); ``` The `Encryptable` factory functions (`Encryptable.uint32(...)`, `Encryptable.bool(...)`, etc.) work the same way in both libraries. ## 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. | | `cofhejs` | `@cofhe/sdk` | | ------------------------- | ------------------------------------- | ---------------------------------------------------------------------- | | **Function** | `cofhejs.unseal(sealed, type)` | `client.decryptForView(ctHash, type)` or `client.decryptForTx(ctHash)` | | **Permit handling** | Automatic (uses most recent permit) | Explicit — `.withPermit()` / `.withoutPermit()` | | **Return value** | `Result` | Direct value for view; `{ ctHash, decryptedValue, signature }` for tx | | **On-chain verification** | Not built in | `decryptForTx` returns a signature for `FHE.publishDecryptResult(...)` | ### Before (cofhejs) ```typescript theme={null} 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 ```typescript theme={null} 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 TypeScript theme={null} const ctHash = await myContract.getEncryptedAmount(); const { decryptedValue, signature } = await client .decryptForTx(ctHash) .withoutPermit() .execute(); await myContract.publishDecryptResult(ctHash, decryptedValue, signature); ``` ```solidity MyContract.sol theme={null} import '@fhenixprotocol/cofhe-contracts/FHE.sol'; contract MyContract { euint64 private _encryptedAmount; function publishDecryptResult( euint64 ctHash, uint64 plaintext, bytes calldata signature ) external { FHE.publishDecryptResult(ctHash, plaintext, signature); } } ``` ## 4. Permits Permits are no longer auto-generated during initialization. All permit operations are now explicit through `client.permits`. | | `cofhejs` | `@cofhe/sdk` | | ------------------- | ---------------------------------------- | ---------------------------------------------------------------------------- | | **Auto-generation** | `generatePermit: true` (default) | Never — always explicit | | **Creation** | `cofhejs.createPermit({ type, issuer })` | `client.permits.createSelf(...)`, `client.permits.createSharing(...)` | | **Return type** | `Result` | Direct `Permit` object | | **Active permit** | Implicitly used by `unseal` | `getOrCreateSelfPermit()` sets active; used automatically by decrypt methods | ### Before (cofhejs) ```typescript theme={null} await cofhejs.initializeWithEthers({ ethersProvider: provider, ethersSigner: signer, environment: 'TESTNET', // generatePermit: true ← default }); const result = await cofhejs.createPermit({ type: 'self', issuer: wallet.address, }); ``` ### After (@cofhe/sdk) ```typescript theme={null} // 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(); ``` ## 5. Error handling ### Before (cofhejs) ```typescript theme={null} 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) ```typescript theme={null} 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` | # Overview Source: https://cofhe-docs.fhenix.zone/client-sdk/introduction/overview Introduction to @cofhe/sdk - the TypeScript client SDK for building FHE-enabled applications on Fhenix `@cofhe/sdk` is the TypeScript client SDK for [CoFHE](/deep-dive/cofhe-components/overview). It handles the client-side operations required to interact with FHE-enabled smart contracts: encrypting inputs with ZK proofs, decrypting ciphertext handles via the Threshold Network, and managing EIP-712 permits for access control. On-chain, contracts use [`FHE.sol`](/fhe-library/introduction/overview) to operate on encrypted data. Off-chain, this SDK prepares the inputs and reads the outputs. ## What the SDK does Packs plaintext values, generates a ZKPoK, and submits them to the CoFHE verifier. Returns signed EncryptedItemInput objects for use in contract calls. Requests decryption of a ciphertext handle via the Threshold Network using a permit. Returns the plaintext locally, not published on-chain. Requests decryption and returns the plaintext with a Threshold Network signature for on-chain verification. Creates, stores, and manages EIP-712 permits that authorize decryption of specific ciphertext handles. ## Entrypoints | Entrypoint | Contents | | --------------------- | ----------------------------------------------------------------------------------------------------------- | | `@cofhe/sdk` | Core types (`Encryptable`, `FheTypes`, `EncryptStep`, `CofheError`), shared across runtimes | | `@cofhe/sdk/web` | `createCofheConfig` / `createCofheClient` with browser defaults (IndexedDB storage, TFHE WASM, Web Workers) | | `@cofhe/sdk/node` | `createCofheConfig` / `createCofheClient` with Node.js defaults (filesystem storage, `node-tfhe`) | | `@cofhe/sdk/permits` | Permit creation, validation, serialization, and storage utilities | | `@cofhe/sdk/adapters` | `Ethers5Adapter`, `Ethers6Adapter`, `WagmiAdapter`, `HardhatSignerAdapter` | | `@cofhe/sdk/chains` | Built-in chain definitions and `getChainById` / `getChainByName` helpers | ```typescript theme={null} // Browser import { createCofheConfig, createCofheClient } from '@cofhe/sdk/web'; // Node.js import { createCofheConfig as createCofheConfigNode, createCofheClient as createCofheClientNode, } from '@cofhe/sdk/node'; // Shared import { Encryptable, FheTypes } from '@cofhe/sdk'; import { chains } from '@cofhe/sdk/chains'; import { Ethers6Adapter } from '@cofhe/sdk/adapters'; ``` ## Client lifecycle The SDK follows a three-step lifecycle: **config → client → connect**. ``` createCofheConfig({ supportedChains }) → createCofheClient(config) → client.connect(publicClient, walletClient) ``` ```typescript theme={null} import { createCofheConfig, createCofheClient } from '@cofhe/sdk/web'; import { Encryptable, FheTypes } from '@cofhe/sdk'; import { chains } from '@cofhe/sdk/chains'; const config = createCofheConfig({ supportedChains: [chains.sepolia], }); const client = createCofheClient(config); await client.connect(publicClient, walletClient); // Encrypt and send const [encrypted] = await client .encryptInputs([Encryptable.uint64(100n)]) .execute(); await contract.deposit(encrypted); // Decrypt for UI await client.permits.getOrCreateSelfPermit(); const ctHash = await contract.getBalance(); const balance = await client .decryptForView(ctHash, FheTypes.Uint64) .execute(); ``` ## Key concepts Install the SDK, write a contract, and run your first encrypt → store → decrypt test in minutes. Encrypt plaintext values with ZK proofs before passing them to your smart contract. Create and manage EIP-712 permits that authorize decryption of confidential data. Reveal encrypted values locally for UI display using permits. Decrypt with a verifiable Threshold Network signature for on-chain use. # Quick Start: Foundry Source: https://cofhe-docs.fhenix.zone/client-sdk/quick-start/foundry Set up a Foundry project with @cofhe/foundry-plugin, write a contract, and test the encrypt → store → decrypt flow Set up a Foundry project with `@cofhe/foundry-plugin`, write a contract that stores an encrypted `uint32`, and test the encrypt → store → decrypt flow under `forge test`. Want to skip the setup? Clone the [cofhe-foundry-starter](https://github.com/FhenixProtocol/cofhe-foundry-starter) template to get a pre-configured project with everything ready to go. ## Prerequisites * [Foundry](https://book.getfoundry.sh/getting-started/installation) (`forge`, `cast`, `anvil`) * Node.js 18+ with npm/pnpm/yarn (for installing the plugin's npm packages) ## 1. Install dependencies The plugin and its CoFHE dependencies are distributed via npm. Install them as dev dependencies, then `forge install` for `forge-std`: ```bash npm theme={null} npm install -D @cofhe/foundry-plugin@^0.5.2 @cofhe/mock-contracts@^0.5.2 @fhenixprotocol/cofhe-contracts@^0.1.3 @openzeppelin/contracts forge install foundry-rs/forge-std ``` ```bash pnpm theme={null} pnpm add -D @cofhe/foundry-plugin@^0.5.2 @cofhe/mock-contracts@^0.5.2 @fhenixprotocol/cofhe-contracts@^0.1.3 @openzeppelin/contracts forge install foundry-rs/forge-std ``` ```bash yarn theme={null} yarn add -D @cofhe/foundry-plugin@^0.5.2 @cofhe/mock-contracts@^0.5.2 @fhenixprotocol/cofhe-contracts@^0.1.3 @openzeppelin/contracts forge install foundry-rs/forge-std ``` ## 2. Configure `foundry.toml` ```toml foundry.toml theme={null} [profile.default] src = "src" out = "out" test = "test" script = "script" libs = ["node_modules"] solc_version = "0.8.25" auto_detect_remappings = false code_size_limit = 100000 ``` `code_size_limit = 100000` is required — the mock contracts exceed the EIP-170 24 KB limit. `evm_version = "cancun"` is no longer required as of `@cofhe/mock-contracts@0.5.0` — `MockACL` was migrated off transient storage to block-number-based storage. Set it only if your own contracts need cancun-specific opcodes. ## 3. Configure `remappings.txt` ```text remappings.txt theme={null} forge-std/=node_modules/forge-std/src/ hardhat/=node_modules/forge-std/src/ @openzeppelin/contracts/=node_modules/@openzeppelin/contracts/ @fhenixprotocol/cofhe-contracts/=node_modules/@fhenixprotocol/cofhe-contracts/ @cofhe/mock-contracts/=node_modules/@cofhe/mock-contracts/ @cofhe/foundry-plugin/=node_modules/@cofhe/foundry-plugin/ ``` The `hardhat/=node_modules/forge-std/src/` line is **load-bearing** — `MockCoFHE.sol` imports `hardhat/console.sol`, and this alias resolves it to forge-std's compatible `console.sol`. ## 4. Write a contract ```solidity src/MyContract.sol theme={null} // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.25; import "@fhenixprotocol/cofhe-contracts/FHE.sol"; contract MyContract { euint32 public storedValue; function setValue(InEuint32 memory inValue) external { storedValue = FHE.asEuint32(inValue); FHE.allowThis(storedValue); FHE.allowSender(storedValue); } } ``` * `euint32` — an encrypted `uint32` stored on-chain as a ciphertext handle. * `InEuint32` — the encrypted input struct produced by `CofheClient`. * `FHE.allowThis` / `FHE.allowSender` — grant the contract and caller permission to read the encrypted value (required by the ACL). ## 5. Write a test ```solidity test/MyContract.t.sol theme={null} // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.25; import { CofheTest } from "@cofhe/foundry-plugin/contracts/CofheTest.sol"; import { CofheClient } from "@cofhe/foundry-plugin/contracts/CofheClient.sol"; import { InEuint32 } from "@fhenixprotocol/cofhe-contracts/FHE.sol"; import { MyContract } from "../src/MyContract.sol"; contract MyContractTest is CofheTest { MyContract public myContract; CofheClient public bob; uint256 constant BOB_PKEY = 0xB0B; function setUp() public { deployMocks(); bob = createCofheClient(); bob.connect(BOB_PKEY); vm.prank(bob.account()); myContract = new MyContract(); } function test_StoresAndDecryptsAnEncryptedValue() public { // 1. Encrypt the input InEuint32 memory encrypted = bob.createInEuint32(42); // 2. Send to contract vm.prank(bob.account()); myContract.setValue(encrypted); // 3. Assert the stored plaintext directly (mock-only, no permit needed) expectPlaintext(myContract.storedValue(), uint32(42)); } } ``` ## 6. Run ```bash theme={null} forge test -vvv ``` ``` [PASS] test_StoresAndDecryptsAnEncryptedValue() (gas: …) Test result: ok. 1 passed; 0 failed; 0 skipped; finished in … ``` ## What just happened? 1. **`deployMocks()`** deployed the full CoFHE coprocessor mock stack (TaskManager, ACL, ZK verifier, threshold network) to the in-process EVM. 2. **`createCofheClient()` + `bob.connect(BOB_PKEY)`** spun up an in-Solidity SDK shim bound to `vm.addr(BOB_PKEY)`. 3. **`bob.createInEuint32(42)`** produced a signed `InEuint32` — the same shape your contract receives on testnet, signed by `MockZkVerifierSigner`. 4. **`vm.prank(bob.account())` + `setValue(...)`** called the contract as Bob. The contract stored the ciphertext handle and granted ACL access to itself and Bob. 5. **`expectPlaintext(myContract.storedValue(), 42)`** read the on-chain plaintext directly from `MockTaskManager.mockStorage` — no permit, no SDK round-trip. ## Next steps * [Foundry Plugin → Getting Started](/client-sdk/foundry-plugin/getting-started) — full plugin configuration and features. * [CofheTest](/client-sdk/foundry-plugin/cofhe-test) — `deployMocks`, `expectPlaintext`, log toggles. * [CofheClient](/client-sdk/foundry-plugin/cofhe-client) — encrypt inputs, decrypt for view / tx, permits. * [Testing](/client-sdk/foundry-plugin/testing) — canonical test patterns for ACL, public-decrypt, and fuzzing. # Quick Start: Hardhat Source: https://cofhe-docs.fhenix.zone/client-sdk/quick-start/hardhat Set up a Hardhat project with @cofhe/sdk, write a contract, and test the encrypt → store → decrypt flow Set up a Hardhat project with `@cofhe/hardhat-plugin`, write a contract that stores an encrypted `uint32`, and test the encrypt → store → decrypt flow. Want to skip the setup? Clone the [cofhe-hardhat-starter](https://github.com/FhenixProtocol/cofhe-hardhat-starter) template to get a pre-configured project with everything ready to go. ## Prerequisites * Node.js 18+ * An existing Hardhat project (or run `npx hardhat init` to create one) * `@nomicfoundation/hardhat-toolbox` or `@nomicfoundation/hardhat-ethers` installed ## 1. Install dependencies ```bash npm theme={null} npm install @cofhe/hardhat-plugin@^0.5.2 @cofhe/sdk@^0.5.2 @fhenixprotocol/cofhe-contracts@^0.1.3 ``` ```bash pnpm theme={null} pnpm add @cofhe/hardhat-plugin@^0.5.2 @cofhe/sdk@^0.5.2 @fhenixprotocol/cofhe-contracts@^0.1.3 ``` ```bash yarn theme={null} yarn add @cofhe/hardhat-plugin@^0.5.2 @cofhe/sdk@^0.5.2 @fhenixprotocol/cofhe-contracts@^0.1.3 ``` ## 2. Configure Hardhat Import the plugin and set `evmVersion` to `cancun` (required for the transient storage opcodes used by FHE contracts). ```typescript hardhat.config.ts theme={null} import { HardhatUserConfig } from 'hardhat/config'; import '@nomicfoundation/hardhat-toolbox'; import '@cofhe/hardhat-plugin'; const config: HardhatUserConfig = { solidity: { version: '0.8.28', settings: { evmVersion: 'cancun', }, }, }; export default config; ``` Without `evmVersion: 'cancun'`, compilation will fail with errors from `@fhenixprotocol/cofhe-contracts`. ## 3. Write a contract Create a minimal contract that accepts an encrypted input and stores it on-chain. ```solidity contracts/MyContract.sol theme={null} // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.28; import '@fhenixprotocol/cofhe-contracts/FHE.sol'; contract MyContract { euint32 public storedValue; function setValue(InEuint32 memory inValue) external { storedValue = FHE.asEuint32(inValue); FHE.allowThis(storedValue); FHE.allowSender(storedValue); } } ``` * `euint32` — an encrypted `uint32` stored on-chain as a ciphertext handle. * `InEuint32` — the encrypted input struct produced by the SDK. * `FHE.allowThis` / `FHE.allowSender` — grant the contract and caller permission to read the encrypted value (required by the ACL). ## 4. Write a test Use `hre.cofhe.createClientWithBatteries` to get a fully configured SDK client with a self-permit, then encrypt → send → decrypt. ```typescript test/MyContract.test.ts theme={null} import hre from 'hardhat'; import { CofheClient, Encryptable, FheTypes } from '@cofhe/sdk'; import { HardhatEthersSigner } from '@nomicfoundation/hardhat-ethers/signers'; import { expect } from 'chai'; describe('MyContract', () => { let cofheClient: CofheClient; let signer: HardhatEthersSigner; before(async () => { [signer] = await hre.ethers.getSigners(); cofheClient = await hre.cofhe.createClientWithBatteries(signer); }); it('stores and decrypts an encrypted value', async () => { const Factory = await hre.ethers.getContractFactory('MyContract'); const contract = await Factory.deploy(); // 1. Encrypt the input const [encrypted] = await cofheClient .encryptInputs([Encryptable.uint32(42n)]) .execute(); // 2. Send to contract await (await contract.setValue(encrypted)).wait(); // 3. Read the stored handle and decrypt const ctHash = await contract.storedValue(); const decrypted = await cofheClient .decryptForView(ctHash, FheTypes.Uint32) .execute(); expect(decrypted).to.equal(42n); }); }); ``` ## 5. Run ```bash theme={null} npx hardhat test ``` The plugin deploys mock contracts automatically — no extra setup needed. ``` MyContract ✓ stores and decrypts an encrypted value 1 passing (1s) ``` ## What just happened? 1. The **Hardhat plugin** deployed mock versions of the CoFHE coprocessor contracts (TaskManager, ACL, ZK verifier, threshold network) before the test ran. 2. `createClientWithBatteries` created an SDK client connected to the Hardhat network, with a self-permit ready to go. 3. `encryptInputs` encrypted the plaintext `42` into an FHE ciphertext with a ZK proof (simulated by the mock verifier). 4. The contract stored the ciphertext handle on-chain and set ACL permissions. 5. `decryptForView` used the permit to decrypt the handle back to `42n` locally. ## Next steps * [Hardhat Plugin](/client-sdk/hardhat-plugin/getting-started) — full plugin configuration and features. * [Mock Contracts](/client-sdk/hardhat-plugin/mock-contracts) — read plaintext directly and assert encrypted state in tests. * [Logging](/client-sdk/hardhat-plugin/logging) — inspect every FHE operation your contracts perform. # Quick Start: JavaScript Source: https://cofhe-docs.fhenix.zone/client-sdk/quick-start/javascript Get started with @cofhe/sdk in a browser or Node.js app Connect to an FHE-enabled contract, encrypt a value, send it on-chain, and decrypt the result — all from JavaScript. ## Prerequisites * Node.js 18+ * A deployed FHE contract on a supported network (e.g. Sepolia) * A wallet with testnet ETH ## 1. Install ```bash npm theme={null} npm install @cofhe/sdk@^0.5.2 viem ``` ```bash pnpm theme={null} pnpm add @cofhe/sdk@^0.5.2 viem ``` ```bash yarn theme={null} yarn add @cofhe/sdk@^0.5.2 viem ``` ## 2. Create and connect the client Import from `@cofhe/sdk/web` for browser apps or `@cofhe/sdk/node` for Node.js scripts. ```typescript Web (browser) theme={null} import { createCofheConfig, createCofheClient } from '@cofhe/sdk/web'; import { chains } from '@cofhe/sdk/chains'; import { createPublicClient, createWalletClient, http, custom } from 'viem'; import { sepolia } from 'viem/chains'; // 1. Configure const config = createCofheConfig({ supportedChains: [chains.sepolia], }); const client = createCofheClient(config); // 2. Create viem clients (browser wallet) const publicClient = createPublicClient({ chain: sepolia, transport: http(), }); const walletClient = createWalletClient({ chain: sepolia, transport: custom(window.ethereum), }); // 3. Connect await client.connect(publicClient, walletClient); ``` ```typescript Node.js theme={null} import { createCofheConfig, createCofheClient } from '@cofhe/sdk/node'; import { chains } from '@cofhe/sdk/chains'; import { createPublicClient, createWalletClient, http } from 'viem'; import { sepolia } from 'viem/chains'; import { privateKeyToAccount } from 'viem/accounts'; // 1. Configure const config = createCofheConfig({ supportedChains: [chains.sepolia], }); const client = createCofheClient(config); // 2. Create viem clients const account = privateKeyToAccount('0xYOUR_PRIVATE_KEY'); const publicClient = createPublicClient({ chain: sepolia, transport: http(), }); const walletClient = createWalletClient({ chain: sepolia, transport: http(), account, }); // 3. Connect await client.connect(publicClient, walletClient); ``` If you use Ethers.js instead of viem, see the [Client Setup](/client-sdk/guides/client-setup) guide for adapter usage. ## 3. Encrypt and send ```typescript theme={null} import { Encryptable } from '@cofhe/sdk'; // Encrypt a uint32 value const [encrypted] = await client .encryptInputs([Encryptable.uint32(42n)]) .execute(); // Pass it to your contract await contract.setValue(encrypted); ``` ## 4. Decrypt for display ```typescript theme={null} import { FheTypes } from '@cofhe/sdk'; // Create a permit (one-time per account + chain) await client.permits.getOrCreateSelfPermit(); // Read the encrypted handle from your contract const ctHash = await contract.storedValue(); // Decrypt locally const plaintext = await client .decryptForView(ctHash, FheTypes.Uint32) .execute(); console.log(plaintext); // 42n ``` ## Next steps * [Client Setup](/client-sdk/guides/client-setup) — adapters, connection management, and config options. * [Encrypting Inputs](/client-sdk/guides/encrypting-inputs) — supported types, builder API, and progress callbacks. * [Permits](/client-sdk/guides/permits) — create, share, and manage decryption authorization. * [Decrypt to View](/client-sdk/guides/decrypt-to-view) — reveal encrypted state in your UI. * [Decrypt to Transact](/client-sdk/guides/decrypt-to-tx) — decrypt with a verifiable signature for on-chain use. # Quick Start: React Source: https://cofhe-docs.fhenix.zone/client-sdk/quick-start/react Get started with @cofhe/sdk in a React application `@cofhe/react` is currently in development. This page will be updated with a full quick start guide once the React library is available. In the meantime, you can use `@cofhe/sdk/web` directly in your React app. See the [Client Setup](/client-sdk/guides/client-setup) guide for how to configure and connect the SDK with viem or wagmi. # Foundry Plugin Reference Source: https://cofhe-docs.fhenix.zone/client-sdk/reference/foundry-reference Complete API reference for @cofhe/foundry-plugin This page summarizes the public surface of `@cofhe/foundry-plugin`. For task-oriented walkthroughs, see [Getting Started](/client-sdk/foundry-plugin/getting-started) and [Testing](/client-sdk/foundry-plugin/testing). ## `CofheTest` Abstract test base. Inherit it instead of `forge-std/Test` (it inherits `Test` for you). ```solidity theme={null} import { CofheTest } from "@cofhe/foundry-plugin/contracts/CofheTest.sol"; ``` ### Setup | Function | Description | | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `deployMocks()` | Deploy `MockTaskManager`, `MockACL`, `MockZkVerifier`, `MockZkVerifierSigner`, `MockThresholdNetwork`, `MockThresholdNetworkSigner`. Wires them via `setACLContract` / `setVerifierSigner` / `setDecryptResultSigner`. Funds the ZK signer with 10 ether. Call once in `setUp()`. | | `createCofheClient()` | Returns a fresh `CofheClient`. Follow with `client.connect(pkey)`. | ### Plaintext reads | Function | Description | | ---------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- | | `getPlaintext(bytes32 handle)` | Returns the raw `bytes32` plaintext from `MockTaskManager.mockStorage`. | | `getPlaintext(ebool)` / `(euint8)` / `(euint16)` / `(euint32)` / `(euint64)` / `(euint128)` / `(eaddress)` | Typed overloads returning `bool` / `uint8`–`uint128` / `address`. | | `expectPlaintext(handle, value)` | Assertion variant — typed overloads for the same set. | | `expectPlaintext(handle, value, "msg")` | With assertion message. | Reverts if the handle isn't in mock storage. ### Logging | Function | Description | | --------------- | ----------------------------------------- | | `enableLogs()` | Calls `mockTaskManager.setLogOps(true)`. | | `disableLogs()` | Calls `mockTaskManager.setLogOps(false)`. | ### Public state | Field | Type | | ---------------------------- | ---------------------------- | | `mockTaskManager` | `MockTaskManager` | | `mockAcl` | `MockACL` | | `mockZkVerifier` | `MockZkVerifier` | | `mockZkVerifierSigner` | `MockZkVerifierSigner` | | `mockThresholdNetwork` | `MockThresholdNetwork` | | `mockThresholdNetworkSigner` | `MockThresholdNetworkSigner` | ## `CofheClient` Per-user shim. One client per scenario address. ```solidity theme={null} import { CofheClient } from "@cofhe/foundry-plugin/contracts/CofheClient.sol"; ``` ### Connection | Function | Description | | ----------------------- | ------------------------------------------------------------------------------------------------- | | `connect(uint256 pkey)` | Set the connected account to `vm.addr(pkey)`. Required before any `createIn*` or `permit_*` call. | | `account()` | Returns the connected `address`. | ### Encrypt inputs | Function | Returns | | --------------------------- | ------------ | | `createInEbool(bool)` | `InEbool` | | `createInEuint8(uint8)` | `InEuint8` | | `createInEuint16(uint16)` | `InEuint16` | | `createInEuint32(uint32)` | `InEuint32` | | `createInEuint64(uint64)` | `InEuint64` | | `createInEuint128(uint128)` | `InEuint128` | | `createInEaddress(address)` | `InEaddress` | All produce signed `EncryptedInput` shapes signed for `account()`. ### Decrypt | Function | Returns | Notes | | ------------------------------------------------------------ | --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `decryptForTx_withoutPermit(bytes32 ctHash)` | `(bytes32, uint256, bytes)` | `(ctHash, plaintext, signature)`. Signature consumable by `FHE.publishDecryptResult`. Requires `FHE.allowPublic(handle)` to have been called. | | `decryptForTx_withPermit(bytes32 ctHash, Permission permit)` | `(bytes32, uint256, bytes)` | ACL-gated `decryptForTx`. | | `decryptForView(bytes32 ctHash, Permission permit)` | `uint256` | Off-chain seal/unseal. **Reverts on deny** — to assert deny use `mockThresholdNetwork.querySealOutput(...)`. | ### Permits | Function | Description | | ------------------------------------------------ | -------------------------------------------------------------------------- | | `permit_createSelf()` | Self-permit for the connected account; sealing key auto-derived. | | `permit_createShared(address recipient)` | Issuer-side shared permit. | | `permit_exportShared(Permission perm)` | Strip sensitive fields → `SharedPermitExport`. | | `permit_importShared(SharedPermitExport export)` | Recipient-side completion. Reverts unless `export.recipient == account()`. | | `createSealingKey(bytes32 seed)` | Custom sealing key (rarely needed). | ## Mock storage layout `MockTaskManager.mockStorage` is the on-chain plaintext storage that backs `getPlaintext` / `expectPlaintext`. It only exists in the mock environment — `getPlaintext` reverts on real CoFHE networks. ## `foundry.toml` requirements ```toml theme={null} [profile.default] solc_version = "0.8.25" # cofhe-contracts target auto_detect_remappings = false code_size_limit = 100000 # mocks exceed 24 KB libs = ["node_modules"] ``` `evm_version = "cancun"` is no longer required as of `@cofhe/mock-contracts@0.5.0` — `MockACL` was migrated off transient storage to block-number-based storage. Set it only if your own contracts need cancun-specific opcodes. ## `remappings.txt` (canonical shape) ```text theme={null} forge-std/=node_modules/forge-std/src/ hardhat/=node_modules/forge-std/src/ @openzeppelin/contracts/=node_modules/@openzeppelin/contracts/ @fhenixprotocol/cofhe-contracts/=node_modules/@fhenixprotocol/cofhe-contracts/ @cofhe/mock-contracts/=node_modules/@cofhe/mock-contracts/ @cofhe/foundry-plugin/=node_modules/@cofhe/foundry-plugin/ ``` * `@cofhe/mock-contracts/` points at the **package root**, not `…/contracts/`. The plugin's imports include the `contracts/` segment themselves. * `hardhat/=node_modules/forge-std/src/` is required so that `MockCoFHE.sol`'s `import "hardhat/console.sol"` resolves to forge-std's compatible console. ## Version pinning `@cofhe/foundry-plugin` and `@cofhe/mock-contracts` pin `@fhenixprotocol/cofhe-contracts` *exactly* — keep all three CoFHE packages aligned. Known-good tuple as of the latest release: | Package | Version | | --------------------------------- | ------- | | `@cofhe/foundry-plugin` | `0.5.2` | | `@cofhe/mock-contracts` | `0.5.2` | | `@fhenixprotocol/cofhe-contracts` | `0.1.3` | See the [Compatibility](/get-started/introduction/compatibility) page for the canonical table. # Hardhat Plugin Reference Source: https://cofhe-docs.fhenix.zone/client-sdk/reference/hardhat-reference Complete API reference for @cofhe/hardhat-plugin This page is under construction. A full API reference documenting all functions, parameters, and return types will be added here. ## `hre.cofhe` The plugin extends the Hardhat Runtime Environment with a `cofhe` namespace. ### Client creation | Method | Description | | ---------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | | `hre.cofhe.createClientWithBatteries(signer?)` | Creates a fully configured `CofheClient` with a self-permit. Uses the first signer if none provided. | | `hre.cofhe.createConfig(options)` | Wraps `createCofheConfig` with Hardhat defaults (`environment: 'hardhat'`, `encryptDelay: 0`). | | `hre.cofhe.createClient(config)` | Creates a `CofheClient` from a config object. | | `hre.cofhe.connectWithHardhatSigner(client, signer)` | Connects a client using a `HardhatEthersSigner`. | | `hre.cofhe.hardhatSignerAdapter(signer)` | Returns `{ publicClient, walletClient }` from a Hardhat signer. | ### `hre.cofhe.mocks` | Method | Description | | --------------------------------------------------- | ----------------------------------------------------- | | `hre.cofhe.mocks.deployMocks()` | Programmatically deploy mock contracts. | | `hre.cofhe.mocks.getPlaintext(ctHash)` | Returns the plaintext `bigint` for a ciphertext hash. | | `hre.cofhe.mocks.expectPlaintext(ctHash, expected)` | Chai assertion shorthand for `getPlaintext`. | | `hre.cofhe.mocks.getMockTaskManager()` | Returns the `MockTaskManager` contract instance. | | `hre.cofhe.mocks.getMockACL()` | Returns the `MockACL` contract instance. | | `hre.cofhe.mocks.getMockThresholdNetwork()` | Returns the `MockThresholdNetwork` contract instance. | | `hre.cofhe.mocks.getMockZkVerifier()` | Returns the `MockZkVerifier` contract instance. | | `hre.cofhe.mocks.getTestBed()` | Returns the `TestBed` contract instance. | | `hre.cofhe.mocks.withLogs(name, fn)` | Wraps a function with labeled FHE operation logging. | | `hre.cofhe.mocks.enableLogs(label?)` | Enables FHE operation logging. | | `hre.cofhe.mocks.disableLogs()` | Disables FHE operation logging. | ## Hardhat config ```typescript hardhat.config.ts theme={null} import '@cofhe/hardhat-plugin'; export default { solidity: '0.8.28', cofhe: { logMocks: true, // Log FHE operations (default: true) gasWarning: true, // Warn on high mock gas usage (default: true) }, }; ``` ## Hardhat tasks | Task | Description | | ---------------------------- | -------------------------------------------------------------- | | `task:cofhe-mocks:deploy` | Deploy mock contracts. Options: `--deployTestBed`, `--silent`. | | `task:cofhe-mocks:setlogops` | Toggle FHE operation logging. Options: `--enable`. | ## Pre-configured networks | Network | URL | Chain ID | | ------------- | --------------------------- | ---------- | | `localcofhe` | `http://127.0.0.1:42069` | — | | `eth-sepolia` | Ethereum Sepolia public RPC | `11155111` | | `arb-sepolia` | Arbitrum Sepolia public RPC | `421614` | # SDK Reference Source: https://cofhe-docs.fhenix.zone/client-sdk/reference/sdk-reference Complete API reference for @cofhe/sdk This page is under construction. A full API reference documenting all functions, parameters, and return types will be added here. ## Entrypoints | Entrypoint | Contents | | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | `@cofhe/sdk` | Core types: `Encryptable`, `FheTypes`, `EncryptStep`, `CofheError`, `CofheErrorCode`, `isCofheError`, `assertCorrectEncryptedItemInput` | | `@cofhe/sdk/web` | `createCofheConfig`, `createCofheClient` (browser defaults) | | `@cofhe/sdk/node` | `createCofheConfig`, `createCofheClient` (Node.js defaults) | | `@cofhe/sdk/permits` | `PermitUtils`, `setPermit`, `setActivePermitHash`, `getPermit`, `getActivePermitHash` | | `@cofhe/sdk/adapters` | `Ethers5Adapter`, `Ethers6Adapter`, `WagmiAdapter`, `HardhatSignerAdapter` | | `@cofhe/sdk/chains` | `chains`, `getChainById`, `getChainByName`, `hardhat` | ## Core types ### `Encryptable` Factory for creating encryptable input items. | Method | Input type | Solidity param | | --------------------------------- | ------------------ | -------------- | | `Encryptable.bool(value)` | `boolean` | `InEbool` | | `Encryptable.uint8(value)` | `bigint \| string` | `InEuint8` | | `Encryptable.uint16(value)` | `bigint \| string` | `InEuint16` | | `Encryptable.uint32(value)` | `bigint \| string` | `InEuint32` | | `Encryptable.uint64(value)` | `bigint \| string` | `InEuint64` | | `Encryptable.uint128(value)` | `bigint \| string` | `InEuint128` | | `Encryptable.address(value)` | `bigint \| string` | `InEaddress` | | `Encryptable.create(type, value)` | varies | varies | ### `FheTypes` Enum of supported FHE types used with `decryptForView`. | Value | JS return type | | ------------------ | ------------------------------ | | `FheTypes.Bool` | `boolean` | | `FheTypes.Uint8` | `bigint` | | `FheTypes.Uint16` | `bigint` | | `FheTypes.Uint32` | `bigint` | | `FheTypes.Uint64` | `bigint` | | `FheTypes.Uint128` | `bigint` | | `FheTypes.Uint160` | `string` (checksummed address) | ### `EncryptedItemInput` ```typescript theme={null} type EncryptedItemInput = { ctHash: bigint; securityZone: number; utype: FheTypes; signature: string; }; ``` ### `EncryptStep` Enum values fired during the encryption pipeline: | Value | Description | | ----------------------- | ---------------------------- | | `EncryptStep.InitTfhe` | Initialize TFHE WASM module | | `EncryptStep.FetchKeys` | Fetch FHE public key and CRS | | `EncryptStep.Pack` | Pack plaintext values | | `EncryptStep.Prove` | Generate ZK proof | | `EncryptStep.Verify` | Submit to CoFHE verifier | # ACL (Access Control Layer) Source: https://cofhe-docs.fhenix.zone/deep-dive/cofhe-components/acl On-chain contract that manages and enforces access control for ciphertexts, ensuring only authorized contracts can reference or decrypt them | Aspect | Description | | -------------------- | ---------------------------------------------------------------------------------------------------------------------- | | **Type** | Contract deployed on the destination blockchain | | **Function** | Manages and enforces access control for ciphertexts, ensuring only authorized contracts can reference or decrypt them. | | **Responsibilities** | An internal contract that is responsible for managing and verifying access for each and every ciphertext. | # CommitmentRegistry Source: https://cofhe-docs.fhenix.zone/deep-dive/cofhe-components/commitment-registry Registry-chain contract that records FHE computation commitments. The Threshold Network reads it to verify ciphertext integrity before decrypting. | Aspect | Description | | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Type** | UUPS-upgradeable Solidity contract deployed on a **registry chain** (Arbitrum One in production). Distinct from the per-host-chain [CTRegistry](/deep-dive/cofhe-components/ct-registry). | | **Function** | Records `(version, handle) → commitHash` entries for every FHE operation result that the coprocessor produces. | | **Responsibilities** | • Provide an authoritative source of ciphertext integrity that the Threshold Network checks **before** issuing a decryption.
• Group commitments by an opaque `version` tag so a future tfhe-rs / FHE-parameter upgrade can roll out without invalidating earlier ciphertexts.
• Enforce write-once semantics per `(version, handle)` to prevent commitment replacement.
• Expose paginated enumeration so off-chain tooling can audit what has been posted. | | **Deployment** | One deployment per registry chain, behind an ERC-1967 proxy. Initialized with `(initialOwner, initialPoster)`. Owner is `Ownable2Step` — transfers require explicit accept. | ## Why a separate registry chain? The Threshold Network needs to confirm that the ciphertext it's about to decrypt is **exactly** the one the FHE Engine produced (not a tampered or stale handle). The natural place to anchor that proof is on-chain, but doing it on every host chain would force the network to maintain N RPC paths and pay gas on N chains for every FHE operation. Instead, the coprocessor posts commitments to a **single registry chain** (currently Arbitrum One), and the Threshold Network only watches that one. This is why the host-chain [CTRegistry](/deep-dive/cofhe-components/ct-registry) (which maps temporary → final ciphertext hashes inside one chain's lifecycle) and `CommitmentRegistry` (which records the canonical commitment for every produced ciphertext, cross-chain) are deliberately distinct components. ## Storage shape ```solidity theme={null} mapping(bytes32 version => mapping(bytes32 handle => bytes32 commitHash)) commitments; mapping(bytes32 version => bytes32[]) handlesByVersion; mapping(bytes32 version => VersionStatus) versionStatus; mapping(address => bool) posters; ``` `commitments` is the source-of-truth lookup. `handlesByVersion` is an array kept in parallel so paginated enumeration is `O(limit)` instead of `O(total)`. Storage lives at the ERC-7201 slot derived from `cofhe.storage.CommitmentRegistry`, so the contract is upgrade-safe. ## Version lifecycle `version` is an opaque `bytes32` tag chosen by the coprocessor when FHE parameters change (see [the FHE Engine `COMMITMENT_VERSION` notes](https://github.com/FhenixProtocol/cofhe/blob/master/CHANGELOG.md#060---2026-05-05)). Every version moves through a small state machine: ``` Unset ─┐ ▼ Active ─┬──────► Deprecated ──► Revoked └─────────────────────► Revoked ``` | State | Meaning | Allowed transitions | | ------------ | ------------------------------------------------------------------------------------------- | --------------------------- | | `Unset` | Default. No commitments have been posted under this version. | → `Active` | | `Active` | Posters may write commitments under this version. The Threshold Network honors lookups. | → `Deprecated`, → `Revoked` | | `Deprecated` | New commitments rejected. Existing lookups still resolve. Used during a parameter rollover. | → `Revoked` | | `Revoked` | Hard kill. No further transitions; the version is dead. | — (terminal) | Owner-only `setVersionStatus(version, newStatus)` enforces these transitions and reverts with `InvalidVersionTransition` otherwise. The transition emits `VersionStatusChanged(version, oldStatus, newStatus)`. ## Roles and write surface | Role | How it's set | What it can do | | ---------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------- | | **Owner** | `initialize(initialOwner, …)`, then `Ownable2Step` transfer. | `addPoster`, `removePoster`, `setVersionStatus`, `_authorizeUpgrade`. | | **Poster** | Owner-only `addPoster(address)`. Initial poster supplied to `initialize`. | `postCommitments`, `postCommitmentsSafe`. | Non-poster posts revert with `OnlyPosterAllowed(caller)`. In production, the [`blockchain-poster`](https://github.com/FhenixProtocol/cofhe/blob/master/CHANGELOG.md#060---2026-05-05) service holds the only poster role and signs through OpenZeppelin Relayer. ## Writing commitments ```solidity theme={null} function postCommitments( bytes32 version, bytes32[] calldata handles, bytes32[] calldata commitHashes ) external onlyPoster; function postCommitmentsSafe( bytes32 version, bytes32[] calldata handles, bytes32[] calldata commitHashes ) external onlyPoster; ``` Both functions batch-write `(version, handle) → commitHash` rows and require: * `version` is in `Active` state — otherwise reverts with `VersionNotActive(version)`. * `handles.length == commitHashes.length` and `> 0` — otherwise `LengthMismatch` / `EmptyBatch`. * Each `commitHash != bytes32(0)` — otherwise `ZeroCommitHash(handle)`. The difference is in **how duplicates are handled**: | Function | Duplicate handle under same version | Use case | | --------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | `postCommitments` | Reverts the whole batch with `CommitmentAlreadyExists(version, handle)`. | Strict integrity — caller knows it's posting unique data. | | `postCommitmentsSafe` | Silently skips the handle; emits `CommitmentsPostedSafe(version, newlyPosted, skipped)`. | Idempotent re-flushes (e.g. when the coprocessor's message broker redelivers a commitment batch). | `postCommitments` emits `CommitmentsPosted(version, batchSize)`. `postCommitmentsSafe` emits `CommitmentsPostedSafe(version, newlyPosted, skipped)` so the off-chain caller can tell whether the round did real work. Both enforce **write-once per (version, handle)** — a commitment can never be overwritten, only superseded by writing the same handle under a new `version`. ## Reading commitments | Function | Returns | Notes | | ------------------------------------ | --------------- | ------------------------------------------------------------------------------------------- | | `getCommitment(version, handle)` | `bytes32` | `bytes32(0)` means "not posted". | | `getVersionStatus(version)` | `VersionStatus` | `Unset` if never registered. | | `getSize(version)` | `uint256` | Number of handles ever committed under `version`. | | `getHandleByIndex(version, index)` | `bytes32` | Direct array lookup. Reverts on out-of-range. | | `getHandles(version, offset, limit)` | `bytes32[]` | Paginated. Returns an empty array if `offset >= total`; clamps `offset + limit` at `total`. | | `isPoster(address)` | `bool` | Useful for off-chain ops dashboards. | The paginated `getHandles` is the recommended way to enumerate a version — `getSize` first to compute pages, then `getHandles(version, offset, pageSize)` in a loop. ## Events | Event | Emitted by | Use | | ------------------------------------------------------------------------------------------------- | ---------------------------- | ----------------------------------------------------------------------------- | | `CommitmentsPosted(bytes32 indexed version, uint256 batchSize)` | `postCommitments` | Confirm a strict batch landed. | | `CommitmentsPostedSafe(bytes32 indexed version, uint256 newlyPosted, uint256 skipped)` | `postCommitmentsSafe` | Reconcile "how many were new" in an idempotent flow. | | `VersionStatusChanged(bytes32 indexed version, VersionStatus oldStatus, VersionStatus newStatus)` | `setVersionStatus` | Watch for `Active → Deprecated` to know when to stop posting under a version. | | `PosterAdded(address indexed poster)` / `PosterRemoved(address indexed poster)` | `addPoster` / `removePoster` | Audit role changes. | ## Upgrades The contract is `UUPSUpgradeable`. `_authorizeUpgrade` is gated by `onlyOwner`. The constructor calls `_disableInitializers()` so the implementation contract itself can never be initialized — initialization happens through the proxy via `initialize(initialOwner, initialPoster)`. ## Source * Solidity: [`contracts/internal/registry-chain/contracts/commitment-registry/CommitmentRegistry.sol`](https://github.com/FhenixProtocol/cofhe-contracts/blob/master/contracts/internal/registry-chain/contracts/commitment-registry/CommitmentRegistry.sol). * Off-chain poster service: [`src/services/blockchain-poster/`](https://github.com/FhenixProtocol/cofhe/tree/master/src/services/blockchain-poster) — introduced in [cofhe `0.6.0`](https://github.com/FhenixProtocol/cofhe/blob/master/CHANGELOG.md#060---2026-05-05). * FHE Engine commitment-version bumping: [`fhe-engine/src/rabbitmq/handlers.rs`](https://github.com/FhenixProtocol/cofhe/blob/master/fhe-engine/src/rabbitmq/handlers.rs). # CTRegistry Source: https://cofhe-docs.fhenix.zone/deep-dive/cofhe-components/ct-registry Registry contract that manages the mapping between temporary ciphertext hashes and their actual hash values, ensuring secure lookup and verification | Aspect | Description | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Type** | Registry Contract | | **Function** | Manages the mapping between temporary ciphertext hashes and their actual hash values | | **Responsibilities** | • Maintains a consistent record of ciphertext identifiers throughout the CoFHE lifecycle
• Enables secure lookup of final ciphertexts using their temporary handles
• Restricts read/write access to ensure integrity and prevent unauthorized updates | The CTRegistry acts as a source of truth for encrypted data identifiers, mapping temporary hashes to their final computed values. This ensures results from off-chain computation can be securely resolved and verified by their originating requests. # FheOs - Server Source: https://cofhe-docs.fhenix.zone/deep-dive/cofhe-components/fheos-server Off-chain computational layer that executes FHE operations and manages encrypted computations | Aspect | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Type** | Off-chain computational layer. | | **Function** | Executes FHE operations and manages encrypted computations | | **Responsibilities** | • Receives the request from the Slim Listener
• Executes the FHE operations
• Calls Result Processor when result is created
• Returns plaintext results when requested (i.e decrypt/seal output), preserving privacy throughout the pipeline | The FHE Operating System server manages the execution environment for FHE operations. # CoFHE Architecture Overview Source: https://cofhe-docs.fhenix.zone/deep-dive/cofhe-components/overview Comprehensive overview of the CoFHE architecture, components, and data flows for privacy-preserving blockchain computations # CoFHE Architecture CoFHE Architecture Diagram *Click on the image to view in full size* ## System Overview CoFHE (Co-processor for Fully Homomorphic Encryption) is designed as a modular, layered architecture that enables privacy-preserving computations on blockchain networks. The system combines on-chain smart contracts with off-chain processing capabilities to deliver secure, efficient fully homomorphic encryption operations. ### Key Components #### User-Facing Utilities * **Client SDK** (`@cofhe/sdk`): A TypeScript library that provides client-side functionality for encrypting inputs, managing permits, and decrypting outputs. Serves as the primary interface between applications and the CoFHE ecosystem. * **FHE.sol**: The Solidity library that enables smart contracts to perform operations on encrypted data. It exposes a comprehensive API for arithmetic, comparison, and logical operations on encrypted values. #### Internal Utilities * **Task Manager**: Acts as the gateway for all FHE operation requests, validating requests and managing permissions through the Access Control Layer (ACL). * **Slim Listener**: Monitors blockchain events and forwards FHE operation requests to the off-chain execution environment. * **Result Processor**: Handles FHE operation results from the computation layer and publishes them back to the blockchain. * **FHEOS Server**: Executes the actual FHE operations on encrypted data and maintains the encrypted state. * **Threshold Network**: A distributed system that securely handles decryption requests through multi-party computation, ensuring no single entity can access the decryption key. * **Ciphertext Registry**: Per-host-chain registry that maintains references to encrypted values during a chain's lifecycle. * **Commitment Registry**: Single-chain (Arbitrum One) registry where the coprocessor records `(version, handle) → commitHash` entries for every FHE computation result. The Threshold Network reads it to verify ciphertext integrity before issuing a decryption — distinct from the host-chain Ciphertext Registry above. ### Data Flows CoFHE implements several critical data flows that maintain privacy throughout the computation lifecycle: 1. **Encryption Request**: Manages the secure encryption of input data via ZK proofs before it enters the blockchain. 2. **FHE Operation Flow**: Handles the process of requesting and executing computations on encrypted data. 3. **Decryption Request**: Processes requests to decrypt data using the Threshold Network. 4. **Decrypt/Seal Output**: Enables users to access encrypted results while maintaining privacy. This architecture ensures that data remains encrypted throughout its entire lifecycle while still enabling complex computations, providing a foundation for privacy-preserving blockchain applications. # Plaintexts Storage Source: https://cofhe-docs.fhenix.zone/deep-dive/cofhe-components/plaintext-storage Internal smart contract that manages storage and retrieval of plaintext values in the host chain with caching mechanisms | Aspect | Description | | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Type** | Internal Smart Contract | | **Function** | Storage and management of plaintext values in the host chain | | **Responsibilities** | • Manages the storage and retrieval of plaintext values in the system
• Provides caching mechanism for plaintext values to improve retrieval performance
• Ensures secure handling of decrypted data within the CoFHE ecosystem | # Result Processor Source: https://cofhe-docs.fhenix.zone/deep-dive/cofhe-components/result-processor Off-chain service that handles FHE operation results and publishes them back to the blockchain | Aspect | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Type** | Off-chain result handling service | | **Function** | Receives computation results from fheOS and publishes them to the blockchain | | **Responsibilities** | • Receives FHE operation results from the fheOS server
• Sends results to the Data Availability layer
• Publishes decryption results back to the Task Manager on the host chain | The Result Processor ensures that computation results from the off-chain fheOS server are properly relayed back to the blockchain, completing the FHE operation lifecycle. # Slim Listener Source: https://cofhe-docs.fhenix.zone/deep-dive/cofhe-components/slim-listener Off-chain service that monitors blockchain events and forwards FHE operation requests to the computation layer | Aspect | Description | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Type** | Off-chain event monitoring service | | **Function** | Listens to blockchain events and forwards FHE operation requests to the fheOS server | | **Responsibilities** | • Monitors events emitted by the Task Manager contract on the destination chain
• Processes incoming requests and forwards them to the fheOS server
• Ensures reliable delivery of operation requests to the computation layer | The Slim Listener acts as the bridge between on-chain events and the off-chain computation layer, ensuring that all FHE operation requests are captured and forwarded for processing. # TaskManager Source: https://cofhe-docs.fhenix.zone/deep-dive/cofhe-components/task-manager On-chain entry point for CoFHE integration that initiates FHE operations, generates unique handles, and verifies decrypt result signatures | Aspect | Description | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **Type** | Contract deployed on the destination blockchain | | **Function** | Acts as the on-chain entry point for CoFHE integration | | **Responsibilities** | • Initiates FHE operations by serving as the on-chain entry point. The dApp contract calls the FHE.sol library which triggers the TaskManager contract to submit a new encrypted computation task.
• Generates unique handles that act as references to the results of FHE operations. These results are computed asynchronously off-chain.
• Emits structured events containing the unique handle of the ciphertext, operation type, and other required metadata.
• Verifies ECDSA signatures on client-published decrypt results and stores them on-chain. | | **Deployment** | A separate Task Manager Contract is deployed for each supported destination chain, enabling chain-specific integrations | ## Decrypt Result Signature Verification The TaskManager supports **permissionless publishing of decrypt results**. Anyone holding a valid ECDSA signature from the Threshold Network's Dispatcher can publish a decrypt result on-chain. The TaskManager verifies the signature before storing the result. ### Key State | Variable | Description | | --------------------- | ---------------------------------------------------------------------------------------------------------- | | `decryptResultSigner` | Address of the authorized Threshold Network signer. Set to `address(0)` to skip verification (debug mode). | ### Functions | Function | Description | | ---------------------------------------------------------------- | --------------------------------------------------------------------------------- | | `publishDecryptResult(ctHash, result, signature)` | Verify signature and store the decrypt result on-chain. Emits `DecryptionResult`. | | `publishDecryptResultBatch(ctHashes[], results[], signatures[])` | Batch publish multiple results in one transaction for gas efficiency. | | `verifyDecryptResult(ctHash, result, signature)` | Verify a signature without publishing (view). Reverts on failure. | | `verifyDecryptResultSafe(ctHash, result, signature)` | Verify a signature without publishing (view). Returns `false` on failure. | | `setDecryptResultSigner(address)` | Admin-only. Set the authorized signer address. | ### Signature Message Format The signed message is a fixed **76-byte** buffer: | Field | Size | Encoding | | ---------- | -------- | ------------------------------------------------ | | `result` | 32 bytes | uint256, big-endian, left-padded with zeros | | `enc_type` | 4 bytes | i32, big-endian (extracted from ctHash metadata) | | `chain_id` | 8 bytes | u64, big-endian (from `block.chainid`) | | `ct_hash` | 32 bytes | uint256, big-endian | The message is hashed with `keccak256` and verified using OpenZeppelin's `ECDSA.tryRecover`. The `enc_type` and `chain_id` are derived on-chain, binding each signature to a specific ciphertext type and chain. # Threshold Network Source: https://cofhe-docs.fhenix.zone/deep-dive/cofhe-components/threshold-network Off-chain distributed network that processes and executes decryption requests using Multi-Party Computation (MPC) protocols | Aspect | Description | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | **Type** | Offchain, distributed network. | | **Function** | Process and execute decryption requests. | | **Responsibilities** | • Gets ciphertext decryption requests
• Authenticates and validates them
• Runs an MPC protocol to decrypt the ciphertext | In any system utilizing encryption, a crucial step is the eventual decryption of data. For example, if we were to build a privacy-preserving ERC20 contract, users would ultimately need to access their encrypted balances. In the case of CoFHE, this decryption process is managed by the Threshold Network. ## Motivation The Threshold Network is a component of a complex cryptographic system with the sole purpose of enhancing the security and trustworthiness of the system by distributing control of the decryption process. Rather than having a single secret key stored and used for the decryption by a centralized entity, we distribute secret shares (to hide the original decryption key) among multiple parties. This enforces collaboration among parties in order to decrypt; the parties perform an MPC (Multi-Party Computation) protocol that results in the decrypted value of a given ciphertext block (single ciphertext can contain a multiple of these so called blocks), ensuring that no information about the full secret key is leaked at any time. A practical example of a threshold network in practice is vote counting. Multiple representatives of competing parties gather around to count votes from recent elections. In order to attempt voter fraud all of the participating parties would have to collaborate (which is unlikely). Threshold Network is built on the exact same principle. ## Concept Threshold Network performs decryption operations. The Threshold Network is currently initialized by a Trusted Dealer (in the future, we plan to eliminate the Trusted Dealer). The Dealer initially generates a key. The Trusted Dealer uses the private key within a secret-sharing algorithm to generate secret shares to share among individual members. Each member holds exactly one secret share. To perform a decryption, the secret shares are used to perform partial decryptions through a multiparty computation (MPC) protocol. These partial decryptions are then combined into the final plaintext. The protocol requires cooperation from all participants to perform a decryption, ensuring no single entity can decrypt the ciphertext alone. This distributed control mechanism enhances security by preventing unilateral access to encrypted data. ## Decryption Process The Threshold network includes three main components: * **Coordinator** - coordinates communication between the party members to perform the MPC protocol. * **Party Members** - the individual parties that hold a secret share and execute the MPC protocol. * **Trusted Dealer** - responsible for initializing the protocol, and for providing random data to the party members, needed to perform the protocol securely. Threshold Network Flow All incoming decryption requests reach the Coordinator (1). The coordinator splits the CT into individual Learning With Errors (LWE) CT blocks. These blocks then get broadcast to partymembers (2). During this process data is exchanged back and forth until the decryption of all blocks is complete upon which the coordinator reassembles the plaintext from decrypted LWE CT blocks. The plaintext value then gets sent back to the user. The MPC protocol consists of multiple stages. In each stage, a partymember performs a calculation on a received input and returns the result (a.k.a. intermediate result) to the Coordinator. Each intermediate result gets sent back to the coordinator in order to get distributed among other partymembers as an input for the next stage. ## Dispatcher Signing The Threshold Network's **Dispatcher** component signs every decrypt and sealoutput result with an ECDSA key. This signature enables on-chain verification — clients can publish signed decrypt results directly to the TaskManager contract via `FHE.publishDecryptResult()`. ### Signed Message Format For decrypt results, the Dispatcher produces a fixed **76-byte** message before signing: | Field | Size | Encoding | | ---------- | -------- | ------------------------------------------- | | `result` | 32 bytes | uint256, big-endian, left-padded with zeros | | `enc_type` | 4 bytes | i32, big-endian | | `chain_id` | 8 bytes | u64, big-endian | | `ct_hash` | 32 bytes | uint256, big-endian | This format is aligned with Solidity types so the TaskManager can reconstruct and verify the same hash on-chain using `_computeDecryptResultHash`. ### Signature V Format The ECDSA recovery ID (`v` value) can be returned in two formats, controlled by the HTTP header `X-Signature-V-Format`: | Header Value | V Format | Use Case | | ----------------- | -------- | --------------------------------------------------------------------- | | `"raw"` (default) | 0-3 | General purpose, k256 native | | `"evm"` | 27-28 | Direct use with Solidity's `ecrecover` / OpenZeppelin `ECDSA.recover` | For on-chain verification via `FHE.publishDecryptResult()`, use `"evm"` format so the signature is directly compatible with the TaskManager's ECDSA verification. ### Signer Registration The Dispatcher's signing key address is registered on-chain as `decryptResultSigner` in the TaskManager contract. Only results signed by this address are accepted. Setting it to `address(0)` disables verification (debug mode only). # ZK Verifier Source: https://cofhe-docs.fhenix.zone/deep-dive/cofhe-components/zk-verifier Off-chain service that verifies user inputs using Zero-Knowledge Proofs of Knowledge (ZKPoK) to ensure encrypted data is safe to use in smart contracts **ZK-Verifier** is an essential component for encrypting and providing data as inputs to confidential smart contracts. | Aspect | Description | | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Type** | Off-chain service, used by clients. | | **Function** | Verifies the user's input, ensuring that it is safe to use. | | **Responsibilities** | • Receives a user's ZKPoK of their inputs.
• Verifies said ZK proofs.
• Generates a signature, allowing the user to use these inputs in a smart contract function call.
• Stores inputs and their proofs in GCS public bucket.
• Communicates directly with FheOS. | ## ZKPoK - Why? **Zero-Knowledge Proof of Knowledge (ZKPoK)** provides a crucial security mechanism in CoFHE. It allows users to prove they know the plaintext of an encrypted input they're sending to a smart contract, without revealing the plaintext itself. ZKPoKs protect against potential malicious vectors, including: 1. **Malleability Attacks**: Without ZKPoK protection, attackers could manipulate encrypted data by applying transformations to observed ciphertexts, even without knowing what's inside them. For example, they might combine existing ciphertexts with encrypted zero values to create new valid-looking encrypted data, potentially compromising user's confidentiality. 2. **Chosen Ciphertext Attacks (CCAs)**: Attackers can submit modified ciphertexts to the system and observe the results, potentially exploiting homomorphic operations to infer sensitive information, manipulate data, or even recover the secret key. Requiring a ZKPoK for each encrypted input helps running an encryption system in the public space that is a blockchain runtime. It ensures that only users with knowledge of the original plaintext can produce valid proofs. This approach eliminates multiple security risks, protecting sensitive user data and maintaining the system's integrity. ## Sending encrypted inputs As mentioned before, when providing ciphertexts as an input to a smart contract, users have to generate a ZKPoK and get a verification approval first. Although most of the work will be handled by the Client SDK (`@cofhe/sdk`) and FHE.sol, we will describe this mechanism in high-level (also in the diagram below). ZK Proof of Knowledge Flow The process of sending input(s) to a Smart Contract: 1. User encrypts an input(s) and generate a ZK proof of knowledge for it. 2. User sends the ciphertext(s) and proof(s) to the ZKVerifier. 3. ZK-Verifier verifies the proof. If valid, sign a message that approves input(s). 4. ZK-Verifier returns the signed approval to User. 5. User sends `(ciphertext, signed_approve)` (one or more) as input(s) to a contract call. 6. Contract verifies the signed message, approving the input(s). 7. Contract performs actual logic. As mentioned, most of this process is abstracted away. In fact, steps 1-6 are all handled behind the scenes, while step 7 (the actual logic) is, of course, up to the user to write. ## ZK-Verifier The ZK-Verifier is a zk-verification program. It has two purposes: 1. Verify the ZKPoK's of ciphertexts that are intended to be inputted to a CoFHE smart contract. 2. Sign a verification message, allowing the said contract to ensure that the inputs are safe and were validated. The signed message that the ZK-Verifier outputs will then be verified by the receiving contract using `ecrecover`. That means that the ZKVerifier's public key will be predetermined and well-known. The ZKVerifier is intended to run in a TEE to reduce trust and ensure the integrity of the inputs and signed verification messages. # Decryption Request Flow Source: https://cofhe-docs.fhenix.zone/deep-dive/data-flows/decryption-request-flow Complete flow of a decryption request in the CoFHE ecosystem through smart contracts # Decryption Request Flow The process of requesting decryption through Smart Contracts starts the same as every other [FHE Operation Request](/deep-dive/data-flows/fhe-operation-request-flow) 📌steps 1-4 Here we'll continue from FheOS server handling such request as follows: ## Flow Diagram The following diagram illustrates the complete flow of an FHE Decryption request in the CoFHE ecosystem: End-to-end flow of an FHE Decryption request through the CoFHE system components *Figure 1: End-to-end flow of an FHE Decryption request through the CoFHE system components* ## Step-by-Step Flow The decryption request follows the same initial steps as a standard FHE operation request: 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ Refer to [FHE Operation Request Flow](/deep-dive/data-flows/fhe-operation-request-flow) for details on steps 1-4, which include: * Integration with the Client SDK * Requesting an FHE Operation * Task Manager Processing * Slim Listener Processing The FheOS server handles decryption requests: 1. **Create execution thread** on the fheOS server 2. **FheOS server calls the threshold network** with: * The ciphertext to be decrypted * Transaction hash from the host chain * Original operation handle The Threshold Network performs secure decryption: * Verify the host chain requested the desired decryption * Retrieve the actual ciphertext hash from private storage * Validate ciphertext hash integrity * Perform secure decryption After decryption is complete: 7️⃣ * The Threshold Network returns the plaintext along with an **ECDSA signature** to the client (via the Client SDK) * The client (or any relayer) calls `FHE.publishDecryptResult(ctHash, result, signature)` or `FHE.verifyDecryptResult(ctHash, result, signature)` on-chain * The on-chain contract verifies the signature before accepting the result This enables permissionless result delivery — anyone holding a valid signature can publish. This is useful for client-driven settlement or relayer patterns. # Encryption Request Flow Source: https://cofhe-docs.fhenix.zone/deep-dive/data-flows/encryption-request-flow Complete flow of the encryption request process using the Client SDK for encrypting data for private computation with smart contracts ## Overview This document outlines the complete flow of the encryption request process using the `@cofhe/sdk` Client SDK, a TypeScript library designed to help users encrypt data for private computation with smart contracts. Understanding this process is essential for developers who want to enable their users to interact with privacy-preserving smart contracts using encrypted inputs. ## Key Components | Component | Description | | ----------------------------- | ------------------------------------------------------------------------------- | | **dApp** | The decentralized application that interacts with the user and the contracts | | **Client SDK** (`@cofhe/sdk`) | TypeScript package designed for seamless interaction with Fhenix's co-processor | | **Threshold Network** | (When applicable) Handles secure decryption operations | ## Flow Diagram The following diagram illustrates the complete flow of an Encryption request in the CoFHE ecosystem: End-to-end flow of an Encryption request through the CoFHE system components *Figure 1: End-to-end flow of an Encryption request through the CoFHE system components* ## Step-by-Step Flow Install, include and initialize the Client SDK in your project (full details [here](/client-sdk/introduction/installation)). ```bash theme={null} npm install @cofhe/sdk ``` ```javascript theme={null} const { Encryptable, FheTypes } = require("@cofhe/sdk"); const { createCofheConfig, createCofheClient } = require("@cofhe/sdk/node"); ``` 1️⃣ The data is encrypted locally using the `encrypt` function. Under the hood, `encrypt` encrypts the data using the TFHE library and create a zkPoK to prove the encryption is correct. The zkPoK is verified using the `verify` function. 2️⃣ This verification process ensures that the ciphertext was generated correctly—that it represents a valid encryption of a known plaintext—and that the data has not been tampered with. Upon successful verification, the encrypted data is stored in the Data Availability (DA) layer. 3️⃣ 4️⃣ The function returns a value handle that can be used to reference the encrypted data later, along with a signature. 5️⃣ The user can send the value handle to the contract as an encrypted input. This handle represents the ciphertext stored in the DA layer and allows the contract to reference the encrypted value. Read more about the implementation details [here](/client-sdk/guides/encrypting-inputs) # FHE Operation Request Flow Source: https://cofhe-docs.fhenix.zone/deep-dive/data-flows/fhe-operation-request-flow Complete flow of an FHE operation request in the CoFHE ecosystem through smart contracts # FHE Operation Request Flow ## Overview This document outlines the complete flow of an FHE (Fully Homomorphic Encryption) operation request in the CoFHE ecosystem through Smart Contracts. Understanding this process is essential for developers integrating private computation capabilities into their smart contracts. ## Key Components | Component | Description | | --------------------- | ----------------------------------------------------------------------- | | **dApp** | The decentralized application that requests FHE operations | | **FHE.sol** | The library providing FHE operation functions | | **Task Manager** | Verifies and forwards operation requests | | **Slim Listener** | Monitors blockchain events and forwards requests to the execution layer | | **Result Processor** | Handles operation results and publishes them back to the blockchain | | **fheOS Server** | Executes the actual FHE operations | | **Threshold Network** | (When applicable) Handles secure decryption operations | ## Flow Diagram The following diagram illustrates the complete flow of an FHE operation request in the CoFHE ecosystem: End-to-end flow of an FHE operation request through the CoFHE system components *Figure 1: End-to-end flow of an FHE operation request through the CoFHE system components* ## Step-by-Step Flow The decentralized application (dApp) integrates with CoFHE by utilizing the **Client SDK** (`@cofhe/sdk`) for encryption. [See in GitHub](https://github.com/FhenixProtocol/cofhesdk) 1️⃣ [Encrypt request](/deep-dive/data-flows/encryption-request-flow) using the Client SDK, returns `InEuint` structure. This step happens on the client side before blockchain interaction. When the dApp needs to perform an encrypted operation within the smart contract: 2️⃣ **Import the FHE library in Solidity:** ```solidity theme={null} import "@fhenixprotocol/cofhe-contracts/FHE.sol"; ``` **Call the appropriate FHE function** from the imported library: ```solidity theme={null} // using trivial encrypt or the returned structures from the previous step. function addExample(InEuint32 encryptedInput) { euint32 lhs = FHE.asEuint32(encryptedInput); euint32 rhs = FHE.asEuint32(10); // Request an operation (addition in this example) euint32 result = FHE.add(lhs, rhs); } ``` **FHE.sol forwards the request** to the Task Manager contract The Task Manager serves as the gateway for all FHE operation requests: 1. **Validate request structure** to ensure all inputs are properly formatted 2. **Verify access permissions** by checking if the caller has proper access to the encrypted inputs (using ACL.sol) 3️⃣ 3. **Generate a unique handle** that will be used to reference the future ciphertext result 4. **Return the handle** to the calling dApp contract 5. **Emit an event** containing the operation details for the Slim Listener to process 4️⃣ The Slim Listener monitors and forwards FHE operation requests: 1. **Listen for events** from the Task Manager 5️⃣ 2. **Forward request details** to the fheOS server 6️⃣ The FheOS server handles requests: 1. **Create execution thread** on the fheOS server 2. **Execute the requested operation** on encrypted data 3. **Generate result ciphertext** containing the encrypted result 4. **Map the handle** to the actual ciphertext hash in the private storage 5. **Make result available** for subsequent operations 6. **Notify the Result Processor** of operation completion For standard FHE operations (not decryption): 1. **Update ciphertext registry** with the new encrypted result 7️⃣ At this point **the operation cycle is completed**, preserving the confidentiality of all encrypted values. # Off-Chain Decryption Flow Source: https://cofhe-docs.fhenix.zone/deep-dive/data-flows/off-chain-decryption-flow Complete flow of off-chain decryption using decryptForTx and decryptForView # Off-Chain Decryption Flow ## Overview This document lays out the complete flow of off-chain decryption requests. There are two methods for decrypting encrypted data off-chain: * **`decryptForTx`** — Returns the plaintext value and a Threshold Network signature. Used when the decrypted value needs to be submitted on-chain (e.g., via `FHE.publishDecryptResult` or `FHE.verifyDecryptResult`). * **`decryptForView`** — Returns only the plaintext value. Used for UI display or off-chain reads where no on-chain proof is needed. ## Key Components | Component | Description | | --------------------- | --------------------------------------------------------------------------------------- | | **CtHash** | A `bytes32` handle representing an encrypted value. Fetched on-chain. | | **SDK Client** | Client library handling `permits` and the `decryptForTx` / `decryptForView` operations. | | **Threshold Network** | Decentralized decryption network that handles the requests and produces signatures. | | **ACL** | On-chain **A**ccess **C**ontrol **L**ist responsible for tracking **CtHash** access. | ## decryptForTx Flow Use `decryptForTx` when you need to submit the decrypted value on-chain with a proof. Solidity contract: ```solidity theme={null} contract Example { euint32 public count; function setCount(uint32 num) public { count = FHE.asEuint32(num); FHE.allowThis(count); FHE.allowPublic(count); // Allow anyone to request decryption } } ``` Fetch the `CtHash` from the chain: ```typescript theme={null} const ctHash = await example.count(); ``` All encrypted types (`euint8`, `euint16`, `euint32`, `euint64`, `euint128`, `ebool`, `eaddress`) are wrappers around `bytes32`. The data returned from the contract can be used as a `CtHash` directly. Call `decryptForTx` on the SDK client. Since `FHE.allowPublic` was used, no permit is needed: ```typescript theme={null} const result = await client .decryptForTx(ctHash) .withoutPermit() .execute(); ``` If the value was granted access via `FHE.allow` (not `allowPublic`), use `.withPermit()` instead: ```typescript theme={null} const result = await client .decryptForTx(ctHash) .withPermit() .execute(); ``` Behind the scenes: 1. The SDK sends the decryption request to the Threshold Network 2. The Threshold Network verifies on-chain that the requester has access to the `CtHash` via the ACL 3. The Threshold Network performs secure decryption 4. The Threshold Network signs the plaintext result and returns both the plaintext and the signature The SDK returns an object containing the decrypted value and signature. Submit these on-chain: ```typescript theme={null} // result contains: { ctHash, decryptedValue, signature } await example.revealCount( result.decryptedValue, result.signature ); ``` The on-chain function verifies the signature using `FHE.publishDecryptResult` or `FHE.verifyDecryptResult`: ```solidity theme={null} function revealCount(uint32 _decrypted, bytes memory _signature) external { FHE.publishDecryptResult(count, _decrypted, _signature); } ``` *** ## decryptForView Flow Use `decryptForView` when you only need to display the value in the UI — no on-chain transaction is needed. The contract must have granted access to the user via `FHE.allow` or `FHE.allowSender`: ```solidity theme={null} contract Example { mapping(address => euint32) private balances; function getBalance() public view returns (euint32) { return balances[msg.sender]; } function deposit(uint32 amount) public { balances[msg.sender] = FHE.asEuint32(amount); FHE.allowThis(balances[msg.sender]); FHE.allowSender(balances[msg.sender]); // Grant access to the user } } ``` ```typescript theme={null} const ctHash = await example.getBalance(); ``` Call `decryptForView` with a permit (required since this is user-specific data): ```typescript theme={null} const result = await client .decryptForView(ctHash) .withPermit() .execute(); console.log(`Balance: ${result.decryptedValue}`); ``` Behind the scenes: 1. The SDK sends the decryption request with the user's permit to the Threshold Network 2. The Threshold Network verifies the permit's signature and checks on-chain that `permit.issuer` has access to the `CtHash` via the ACL 3. The Threshold Network performs secure decryption 4. The plaintext value is returned to the SDK (no signature needed since this is view-only) *** ## Comparison | | `decryptForTx` | `decryptForView` | | ------------------------- | ----------------------------------------------- | ---------------- | | **Returns** | Plaintext + Threshold Network signature | Plaintext only | | **Use case** | Submit decrypted value on-chain | Display in UI | | **Requires permit** | Only if not `allowPublic` | Yes | | **On-chain verification** | `publishDecryptResult` or `verifyDecryptResult` | Not applicable | | **Gas cost** | Yes (on-chain tx needed) | None | # Future Plans Source: https://cofhe-docs.fhenix.zone/deep-dive/research/future-plans Roadmap for CoFHE decentralization, upcoming features, and planned improvements Future Plans ## Road to Decentralization Integrating FHE into a blockchain-runtime is a hard and complex task. Our engineering philosophy is *Ship Fast*, and we believe that to build the best possible product we need to meet real users early. Similar to the approach described in [Vitalik's "training wheels" post](https://ethereum-magicians.org/t/proposed-milestones-for-rollups-taking-off-training-wheels/11571) (in the context of rollups), we too are relying on "training wheels" releasing CoFHE to achieve this goal. Outlined here is a non-exhaustive list of trust-points, centralized components and compromises made to ship CoFHE to users as fast as possible, along with how we plan to address them in the future. This list will be updated as things progress. | Component | Compromise | Plan to solve | Timeline | Status | | ---------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------------- | -------- | ------ | | Threshold Network (TN) | All parties are run by Fhenix | N/A | N/A | ❌ | | Threshold Network (TN) | Use of a Trusted Dealer for keys and random data generation | N/A | N/A | ❌ | | Threshold Network (TN) | Parties trust the Coordinator | N/A | N/A | ❌ | | Threshold Network (TN) | TN trusts CoFHE (tx-flow decryptions) | N/A | N/A | ❌ | | Threshold Network (TN) | Parties trust a Trusted Dealer | 1. Run TD in a TEE
2. Public ceremony for share creation
3. Eliminate TD | N/A | ❌ | | Threshold Network (TN) | Parties are not using unique random data within the protocol | Pull random data from the TD | N/A | ❌ | | Threshold Network (TN) | SealOutput reencryption performed in a centralized manner | N/A | N/A | ❌ | | ZK-Verifier (ZKV) | CoFHE trusts ZK-Verifier | Run ZKV in a TEE | N/A | ❌ | | CoFHE | Trust in CoFHE to perform correct FHE computations | External verification using AVS | N/A | ❌ | | CoFHE | User inputs stored in a centralized manner | Use a decentralized DA | N/A | ❌ | | All | Codebase is unaudited | Perform a security audit | N/A | ❌ | | All | Codebase is not fully open-source | Open-source codebase | N/A | ❌ | ## Upcoming Features In the spirit of transparency, here we describe the general feature-roadmap planned for CoFHE. This list will be updated as things progress. | Feature | Type | Description | Timeline | Status | | ------------------------------ | ------------------- | -------------------------------------------------------------------------------- | -------- | ------ | | Integration SDK | DevX | SDK to easily integrate CoFHE-specific components into dApps | N/A | ❌ | | Additional external devtools | DevX | Remix, Alchemy SDK and more | N/A | ❌ | | RNG | DevX | Ability to generate secure randomness in contracts | N/A | ❌ | | Alternative runtimes | DevX | Support for additional runtimes other than EVM | N/A | ❌ | | FHE ops in view functions | DevX | Ability to execute FHE operations in view functions in contracts | N/A | ❌ | | GPU support | UX | Run FHE operations on a GPU backend, improving performance and overall latency | N/A | ❌ | | FPGA support | UX | Run FHE operations on an FPGA backend, improving performance and overall latency | N/A | ❌ | | T-out-of-N MPC protocol | Robustness | Improve robustness of the TN by not requiring all parties to be online | N/A | ❌ | | Support additional host-chains | DevX/UX | N/A | N/A | ❌ | | Key shares rotation | Robustness/Security | Ability to rotate the party shares in the TN | N/A | ❌ | | Key Rotation | Robustness/Security | Ability to rotate the key for the entire protocol | N/A | ❌ | # Research in Fhenix Source: https://cofhe-docs.fhenix.zone/deep-dive/research/research-in-fhenix Research into novel cryptographic techniques and optimization of Fully Homomorphic Encryption for blockchain applications # Research in Fhenix Our research explores novel cryptographic techniques to push the boundaries of Fully Homomorphic Encryption (FHE). We mainly delve into optimizing the latency of FHE-based smart contracts using state-of-the-art schemes, ensuring they remain both efficient and secure. Ultimately, our aim is to broaden the practical viability of FHE by delivering protocols that meet real-world performance needs. We also designed a secure high performance threshold decryption protocol (see below). ## Current Project: Threshold Decryption for FHE We designed a new threshold FHE decryption protocol that achieves both **unprecedented throughput** and **shortest latency** compared to existing solutions. Specifically, it improves **throughput by \~20,000×** and **cuts latency by up to 37×** relative to the state of the art. This is achieved by securely removing ciphertext noise with an efficient MPC-based approach, eliminating the need for noise flooding while maintaining strong simulation-based security. # Best Practices Source: https://cofhe-docs.fhenix.zone/fhe-library/confidential-contracts/dual-mode/best-practices Privacy boundaries, backing, zero-replacement, and access-control guidance for ERC20Confidential ## Overview `ERC20Confidential` combines a fully transparent ERC-20 with a confidential layer. That dual nature is powerful but introduces its own footguns: value straddles a public/private boundary, confidential mutations never revert, and encrypted handles change on every update. This page collects the practices that matter most for dual-mode tokens. The confidential-transfer, operator, and callback guidance from [FHERC20 Best Practices](/fhe-library/confidential-contracts/fherc20/best-practices) applies here too. *** ## Mind the Privacy Boundary The single most important thing to understand about dual-mode: **the public layer reveals everything, the confidential layer reveals nothing.** Value only becomes private once it crosses into the confidential layer via `shield`. `shield()` emits `TokensShielded(account, amount)` with a **plaintext** amount, and moves a visible public balance into `CONFIDENTIAL_POOL`. Anyone can see *that* you shielded and *how much*. ```solidity theme={null} event TokensShielded(address indexed account, uint256 amount); // amount is plaintext! ``` Privacy begins **after** shielding — confidential transfers between holders are opaque. But the act of entering and exiting the pool is on the public record. **Recommendation:** if the entry/exit amount itself is sensitive, shield in standardized denominations, or shield more than you need and keep a confidential buffer, so the public shield amount doesn't map 1:1 to a later private action. `CONFIDENTIAL_POOL`'s public balance equals the total backing of all confidential balances. `confidentialTotalSupply()` derives directly from it. This is fine — it's an aggregate — but remember it's fully public and cannot be hidden. A DEX that only ever sees the *public* side of your token learns nothing about confidential holdings. Route sensitive flows through confidential transfers, and only unshield when the recipient genuinely needs public liquidity. *** ## Backing and Minting Confidential balances are only redeemable because `CONFIDENTIAL_POOL` holds the corresponding public tokens. There are two correct ways to create confidential balances: ```solidity theme={null} // ✅ User-driven: mint public tokens, user shields them (pool is funded by the shield) function mint(address to, uint256 amount) external onlyOwner { _mint(to, amount); // public tokens; user later calls shield() } // ✅ Direct confidential mint: backs itself by minting public tokens into the pool function _confidentialMint(address to, uint64 amount) internal virtual { _mint(CONFIDENTIAL_POOL, uint256(amount) * _rate()); // fund the pool _confidentialUpdate(address(0), to, FHE.asEuint64(amount)); } ``` Never credit a confidential balance without a matching increase in the pool's public balance. If you write a custom mint that calls `_confidentialUpdate(address(0), to, ...)` **without** minting the backing into `CONFIDENTIAL_POOL`, unshield claims will drain the pool and eventually fail. `ERC20Confidential` is abstract and ships no minting policy. Add access control: ```solidity theme={null} import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; contract MyDualToken is ERC20Confidential, Ownable { constructor() ERC20Confidential("My Dual Token", "MDT", 18) Ownable(msg.sender) {} function mint(address to, uint256 amount) external onlyOwner { _mint(to, amount); } } ``` *** ## Handle Zero-Replacement Correctly Confidential transfers, unshields, and burns **never revert on insufficient balance** — they move encrypted zero instead. This preserves privacy but breaks any code that assumes the requested amount moved. ```solidity theme={null} // ❌ Assumes the full amount transferred function badDeposit(address token, uint64 amount) external { ERC20Confidential(token).confidentialTransferFrom(msg.sender, address(this), amount); _credit(msg.sender, amount); // wrong if the transfer moved 0! } // ✅ Use the returned encrypted amount function goodDeposit(address token, InEuint64 memory amount) external { euint64 moved = ERC20Confidential(token).confidentialTransferFrom(msg.sender, address(this), amount); _credit(msg.sender, moved); // credit exactly what arrived } ``` The same applies to `unshield` — unshielding more than your confidential balance creates a claim for **zero**. Verify balances before unshielding when the exact amount matters. *** ## Access Control on Encrypted Handles Each confidential mutation stores a **fresh** `euint64` handle. `FHE.allow` grants on an old handle don't carry over. `_confidentialUpdate` re-grants `allowThis` + `allow(..., holder)` automatically for balances and transferred amounts, but if **your** contract caches or re-exposes a handle, re-grant after every change. ```solidity theme={null} // A contract that holds confidential tokens and lets admins decrypt its balance function grantBalanceAccess(address admin) external onlyOwner { euint64 handle = token.confidentialBalanceOf(address(this)); if (euint64.unwrap(handle) == bytes32(0)) return; // no balance yet FHE.allow(handle, admin); // must be re-called after each balance change } ``` `confidentialBalanceOf` returns `0` for an account that has never had a confidential balance. Always check before decrypting: ```typescript theme={null} const handle = await token.confidentialBalanceOf(user.address); if (handle && handle !== 0n) { const balance = await cofheClient.decryptForView(handle, FheTypes.Uint64).withPermit().execute(); } ``` `confidentialTotalSupply()` returns a synthetic handle wrapping a plaintext number — it is **not** a registered ciphertext. Never attempt to decrypt it or feed it into FHE operations; read it as informational only. *** ## Operators The operator guidance is identical to FHERC20 — operators get **full**, all-or-nothing access to a holder's confidential balance until expiry. Grant `setOperator(spender, block.timestamp + 10 minutes)` for a single interaction rather than `type(uint48).max`. `setOperator(spender, uint48(block.timestamp))` revokes immediately — no separate revoke function needed. `until` is a Unix timestamp in **seconds**. Use `Math.floor(Date.now()/1000)`, never `Date.now()`. An operator can move the holder's entire confidential balance. Only authorize trusted contracts/addresses. See [Confidential Operations](/fhe-library/confidential-contracts/dual-mode/confidential-operations#the-operator-system) for signatures and [FHERC20 Best Practices](/fhe-library/confidential-contracts/fherc20/best-practices#operators-best-practices) for the full operator playbook. *** ## Choosing the Contract Variant * **`ERC20Confidential`** — immutable, constructor-based. Simpler, cheaper to deploy, no proxy. Use it when you don't need upgradeability. * **`ERC20ConfidentialUpgradeable`** — proxy-safe. No constructor; call `__ERC20Confidential_init(name, symbol, decimals)` from your initializer. State lives in an ERC-7201 namespaced struct to avoid storage-layout collisions across upgrades. For the upgradeable variant, initialize **once** behind a proxy and follow OpenZeppelin's upgrade-safety rules (no constructors for logic contracts, no immutables for versioned state, protect the initializer). The public token can be 18 decimals to match mainstream ERC-20s, but the confidential layer caps at 6. That means sub-`rate()` dust (e.g. sub-`10^12` for an 18-decimal token) can never be shielded. If precise small amounts matter for your use case, consider a lower public `decimals` so `rate()` is smaller (or `1`). *** *** ## Related Topics * [Overview](/fhe-library/confidential-contracts/dual-mode/overview) — what dual-mode is and when to use it * [The Dual-Balance Model](/fhe-library/confidential-contracts/dual-mode/dual-balance-model) — pool, rate, and the update engine * [Shield & Unshield](/fhe-library/confidential-contracts/dual-mode/shield-unshield) — the bridge between layers * [Confidential Operations](/fhe-library/confidential-contracts/dual-mode/confidential-operations) — transfers, operators, callbacks * [FHERC20 Best Practices](/fhe-library/confidential-contracts/fherc20/best-practices) — shared confidential-token guidance # Confidential Operations Source: https://cofhe-docs.fhenix.zone/fhe-library/confidential-contracts/dual-mode/confidential-operations Confidential transfers, the operator system, and transfer callbacks on ERC20Confidential ## Overview Once value is [shielded](/fhe-library/confidential-contracts/dual-mode/shield-unshield) into the confidential layer, `ERC20Confidential` exposes the full [ERC-7984](https://eips.ethereum.org/EIPS/eip-7984) surface for moving it privately. This API is **identical to FHERC20's** — confidential transfers, a time-based operator system, and safe transfers with receiver callbacks. This page covers each, with the specifics of the `ERC20Confidential` implementation. The confidential-transfer API here mirrors FHERC20. If you already know FHERC20's [operators](/fhe-library/confidential-contracts/fherc20/operators) and [transfer callbacks](/fhe-library/confidential-contracts/fherc20/transfer-callbacks), the same mental model applies — only the contract name and errors differ. *** ## Confidential Transfers Every transfer function comes in two overloads: an `InEuint64` overload for encrypted input coming from off-chain users, and an `euint64` overload for already-encrypted, contract-to-contract values. ```solidity theme={null} // From caller function confidentialTransfer(address to, InEuint64 memory inValue) public returns (euint64 transferred); function confidentialTransfer(address to, euint64 value) public returns (euint64 transferred); // From a third party (requires operator permission) function confidentialTransferFrom(address from, address to, InEuint64 memory inValue) public returns (euint64 transferred); function confidentialTransferFrom(address from, address to, euint64 value) public returns (euint64 transferred); ``` ### How a transfer is authorized The `euint64` overloads require the caller to already hold FHE access to the value; the `InEuint64` overloads accept a fresh encrypted input and convert it in-place. ```solidity theme={null} function confidentialTransfer(address to, euint64 value) public virtual returns (euint64 transferred) { if (!FHE.isAllowed(value, msg.sender)) { revert ERC20ConfidentialUnauthorizedUseOfEncryptedAmount(value, msg.sender); } transferred = _confidentialTransfer(msg.sender, to, value); FHE.allowTransient(transferred, msg.sender); // caller can read what actually moved } ``` The internal path validates addresses and routes through the [confidential update engine](/fhe-library/confidential-contracts/dual-mode/dual-balance-model#the-confidential-update-engine): ```solidity theme={null} function _confidentialTransfer(address from, address to, euint64 value) internal virtual returns (euint64 transferred) { if (from == address(0)) revert ERC20InvalidSender(address(0)); if (to == address(0)) revert ERC20InvalidReceiver(address(0)); transferred = _confidentialUpdate(from, to, value); } ``` **Zero-replacement:** if the sender's confidential balance is insufficient, the transfer moves **encrypted zero** rather than reverting (to avoid leaking balance information). Always use the returned `transferred` handle — not the requested amount — for any downstream logic. ### Usage ```typescript theme={null} import { Encryptable } from '@cofhe/sdk'; // Encrypt the amount off-chain (6 confidential decimals) const [enc] = await cofheClient .encryptInputs([Encryptable.uint64(100_000_000n)]) // 100 tokens .execute(); // Transfer confidentially await token.confidentialTransfer(recipient.address, enc); ``` The caller is granted **transient** access (`FHE.allowTransient`) to the `transferred` handle — access valid only for the current transaction. Both the sender and recipient receive persistent access to it inside `_confidentialUpdate`. *** ## The Operator System Like FHERC20, `ERC20Confidential` replaces ERC-20 allowances (which would leak amounts) with **time-based operators**. An operator can move any amount of a holder's confidential balance until an expiration timestamp. ```solidity theme={null} function setOperator(address operator, uint48 until) public virtual { _setOperator(msg.sender, operator, until); } function isOperator(address holder, address spender) public view virtual returns (bool) { return holder == spender || block.timestamp <= _operators[holder][spender]; } ``` `isOperator` returns `true` when `holder == spender`, so a holder can always move their own tokens via `confidentialTransferFrom` without granting anything. Permission is valid while `block.timestamp <= until`. Set `until` to `block.timestamp` to revoke immediately, or `type(uint48).max` for effectively indefinite access. ### Granting and using operators ```typescript theme={null} // Grant a DEX operator rights for 1 hour await token.setOperator( dexAddress, Math.floor(Date.now() / 1000) + 3600 ); // The operator transfers on the holder's behalf const [enc] = await cofheClient .encryptInputs([Encryptable.uint64(50_000_000n)]) .execute(); await token.connect(dex).confidentialTransferFrom(holder.address, recipient.address, enc); ``` `confidentialTransferFrom` checks operator status before moving tokens: ```solidity theme={null} function confidentialTransferFrom(address from, address to, euint64 value) public virtual returns (euint64 transferred) { if (!FHE.isAllowed(value, msg.sender)) { revert ERC20ConfidentialUnauthorizedUseOfEncryptedAmount(value, msg.sender); } if (!isOperator(from, msg.sender)) { revert ERC20ConfidentialUnauthorizedSpender(from, msg.sender); } transferred = _confidentialTransfer(from, to, value); FHE.allowTransient(transferred, msg.sender); } ``` An operator has access to a holder's **entire** confidential balance until expiry, not a specific amount. Grant only to trusted addresses and prefer short windows. Expiry is silent — a `confidentialTransferFrom` after `until` reverts with `ERC20ConfidentialUnauthorizedSpender`. `OperatorSet(holder, operator, until)` is emitted on every grant or revocation. *** ## Transfer Callbacks The `...AndCall` functions transfer confidential tokens **and** notify the recipient contract, which can accept or reject the transfer. All four overloads exist: ```solidity theme={null} function confidentialTransferAndCall(address to, InEuint64 memory encryptedAmount, bytes calldata data) public returns (euint64); function confidentialTransferAndCall(address to, euint64 amount, bytes calldata data) public returns (euint64); function confidentialTransferFromAndCall(address from, address to, InEuint64 memory encryptedAmount, bytes calldata data) public returns (euint64); function confidentialTransferFromAndCall(address from, address to, euint64 amount, bytes calldata data) public returns (euint64); ``` ### The accept-or-refund mechanism `ERC20Confidential` moves the tokens first, asks the recipient, and **refunds** whatever the recipient rejects — all under FHE, without revealing amounts: ```solidity theme={null} function _confidentialTransferAndCall(address from, address to, euint64 amount, bytes calldata data) internal virtual returns (euint64 transferred) { euint64 sent = _confidentialTransfer(from, to, amount); ebool success = FHERC20Utils.checkOnTransferReceived(msg.sender, from, to, sent, data); // If the receiver rejected, refund the full sent amount back to `from` euint64 refund = _confidentialUpdate(to, from, FHE.select(success, FHE.asEuint64(0), sent)); transferred = FHE.sub(sent, refund); } ``` `sent` tokens move from `from` to `to` via the confidential update engine. `FHERC20Utils.checkOnTransferReceived` invokes `onConfidentialTransferReceived` on the recipient (if it's a contract) and returns an encrypted `success` flag. `FHE.select(success, 0, sent)` computes the refund: zero if accepted, the full amount if rejected. That refund moves back from `to` to `from`. `transferred = sent - refund` — the actual net amount that stuck with the recipient. ### The receiver interface A recipient contract must implement `IERC7984Receiver` and return an **encrypted** bool: ```solidity theme={null} interface IERC7984Receiver { function onConfidentialTransferReceived( address operator, address from, euint64 amount, bytes calldata data ) external returns (ebool); } ``` * Return `FHE.asEbool(true)` to accept the transfer. * Return `FHE.asEbool(false)` to reject it — the tokens are refunded to the sender under FHE. ```solidity theme={null} contract ConfidentialVault is IERC7984Receiver { ERC20Confidential public immutable token; mapping(address => euint64) public deposits; constructor(address token_) { token = ERC20Confidential(token_); } function onConfidentialTransferReceived( address operator, address from, euint64 amount, bytes calldata data ) external override returns (ebool) { if (msg.sender != address(token)) return FHE.asEbool(false); // validate the token deposits[from] = FHE.add(deposits[from], amount); FHE.allowThis(deposits[from]); FHE.allow(deposits[from], from); return FHE.asEbool(true); } } ``` Unlike an all-or-nothing revert, the accept/reject decision is evaluated under encryption and settled by refunding the rejected portion. See [FHERC20 Transfer Callbacks](/fhe-library/confidential-contracts/fherc20/transfer-callbacks) for receiver patterns, reentrancy guidance, and data-passing examples — the receiver contract is written the same way. *** ## Errors ```solidity theme={null} // Caller lacks FHE access to the encrypted amount it's trying to spend error ERC20ConfidentialUnauthorizedUseOfEncryptedAmount(euint64 value, address user); // Caller is not an authorized operator for `holder` error ERC20ConfidentialUnauthorizedSpender(address holder, address spender); // Standard OpenZeppelin ERC-20 address guards (used by _confidentialTransfer) error ERC20InvalidSender(address sender); error ERC20InvalidReceiver(address receiver); ``` *** ## Events ```solidity theme={null} // Confidential transfer — the amount is an encrypted handle, not a plaintext value event ConfidentialTransfer(address indexed from, address indexed to, euint64 indexed amount); // Operator permission granted or revoked event OperatorSet(address indexed holder, address indexed operator, uint48 until); ``` `ConfidentialTransfer` carries an encrypted `euint64` handle, never a plaintext amount — you cannot index transfer amounts off-chain from events. The sidecar `ERC20ConfidentialIndicator` also emits a standard `Transfer(from, to, 10110000001)` so explorers register activity without exposing amounts. *** ## Related Topics * Bridge value into the confidential layer with [Shield & Unshield](/fhe-library/confidential-contracts/dual-mode/shield-unshield) * Understand the encrypted bookkeeping in [The Dual-Balance Model](/fhe-library/confidential-contracts/dual-mode/dual-balance-model) * Receiver patterns and reentrancy: [FHERC20 Transfer Callbacks](/fhe-library/confidential-contracts/fherc20/transfer-callbacks) * Review [Best Practices](/fhe-library/confidential-contracts/dual-mode/best-practices) for secure operator and access-control usage # The Dual-Balance Model Source: https://cofhe-docs.fhenix.zone/fhe-library/confidential-contracts/dual-mode/dual-balance-model How ERC20Confidential maintains a real public balance and an encrypted balance backed by a single pool ## Overview The defining feature of `ERC20Confidential` is that **every holder owns two balances simultaneously** inside one contract: a public ERC-20 balance and a confidential (FHE-encrypted) balance. This page explains how those two layers are stored, how they stay backed 1:1, and how the encrypted bookkeeping engine (`_confidentialUpdate`) drives every confidential mutation. *** ## The Two Layers ```solidity theme={null} abstract contract ERC20Confidential is ERC20, ERC165, IERC20Confidential, FHERC20WrapperClaimHelper { address public constant CONFIDENTIAL_POOL = address(0x1011000000000000000000000000000000000000); mapping(address => euint64) private _confidentialBalances; // confidential layer mapping(address => mapping(address => uint48)) private _operators; // confidential operators ERC20ConfidentialIndicator public immutable indicatorToken; // display sidecar uint8 private immutable _decimals; uint8 private immutable _confidentialDecimals; uint256 private immutable _conversionRate; } ``` **Inherited from OpenZeppelin `ERC20`** Transparent balances tracked in the standard `_balances` mapping. `balanceOf`, `transfer`, `approve`, `transferFrom`, and `totalSupply` behave like any ERC-20. Fully visible on-chain. **`mapping(address => euint64) _confidentialBalances`** Encrypted balances stored as `euint64` handles. Readable only by the holder (via ACL grant + permit) and the contract. Operated on entirely under FHE. Because `ERC20Confidential` inherits the **real** OpenZeppelin `ERC20`, the public side is genuinely functional — this is the inversion of FHERC20, where those same functions revert. ```solidity theme={null} /// @dev `false` because {balanceOf} returns the real public ERC-20 balance, not an indicator. function balanceOfIsIndicator() public pure virtual returns (bool) { return false; } /// @dev Always `0`: {balanceOf} is not an indicator on this token. function indicatorTick() public pure virtual returns (uint256) { return 0; } ``` On FHERC20, `balanceOfIsIndicator()` returns `true` and `balanceOf` returns a fake activity number. On `ERC20Confidential`, both signals declare the public balance **real**, and confidential-activity display is delegated to a separate [sidecar token](#the-indicator-sidecar-token). *** ## The Confidential Pool `CONFIDENTIAL_POOL` is a fixed sentinel address (`0x1011...0000`) that **custodies the public tokens backing every confidential balance**. It is the linchpin of the dual-balance model. When you `shield`, your public tokens are transferred to `CONFIDENTIAL_POOL` and an equal encrypted amount is credited to your confidential balance. At any moment, the public balance sitting at `CONFIDENTIAL_POOL` equals the total value of all confidential balances (scaled by the conversion rate). When you unshield and claim, public tokens are transferred out of `CONFIDENTIAL_POOL` back to you. This backing invariant is why `confidentialTotalSupply()` can be derived directly from the pool's public balance: ```solidity theme={null} /// @dev Derived on read from CONFIDENTIAL_POOL's public balance, scaled to confidential decimals. /// The returned handle is not a registered ciphertext, so it is informational only and cannot /// be decrypted or used in FHE operations. function confidentialTotalSupply() public view virtual returns (euint64) { return euint64.wrap(bytes32(balanceOf(CONFIDENTIAL_POOL) / _rate())); } ``` The `euint64` returned by `confidentialTotalSupply()` is **synthetic** — it wraps a plaintext number, not a registered ciphertext. It is for display/inspection only; you cannot decrypt it or feed it into FHE operations. *** ## Conversion Rate and Decimals The public token may use any decimals; the confidential layer is capped at **6** to keep encrypted balances within `euint64`. ```solidity theme={null} _decimals = decimals_; _confidentialDecimals = decimals_ <= 6 ? decimals_ : 6; _conversionRate = decimals_ > 6 ? 10 ** (decimals_ - 6) : 1; ``` | Public decimals | `confidentialDecimals()` | `_rate()` | 1 confidential unit = | | --------------- | ------------------------ | --------- | -------------------------- | | 18 | 6 | `10^12` | `10^12` public base units | | 8 | 6 | `10^2` | `100` public base units | | 6 | 6 | `1` | `1` public base unit (1:1) | | 4 | 4 | `1` | `1` public base unit (1:1) | The rate governs both directions of the bridge: * **Shielding** rounds the public amount *down* to a whole multiple of the rate, then divides by the rate to get the confidential amount. * **Claiming** multiplies the decrypted confidential amount by the rate to compute the public payout. Dust below one confidential unit cannot be shielded. `shield()` reverts with `AmountTooSmallForConfidentialPrecision` if the rounded amount is zero. See [Shield & Unshield](/fhe-library/confidential-contracts/dual-mode/shield-unshield). *** ## The Confidential Update Engine `_confidentialUpdate` is the encrypted analog of ERC-20's `_update`. Every confidential mutation — shield mint, confidential transfer, unshield burn — flows through it. Passing `address(0)` as `from` means *mint*; passing `address(0)` as `to` means *burn*. ```solidity theme={null} function _confidentialUpdate( address from, address to, euint64 amount ) internal virtual returns (euint64 transferred) { ebool success; euint64 ptr; if (from != address(0)) { euint64 fromBalance = _confidentialBalances[from]; // Underflow-safe encrypted subtraction: success is an encrypted bool (success, ptr) = FHESafeMath.tryDecrease(fromBalance, amount); FHE.allowThis(ptr); FHE.allow(ptr, from); _confidentialBalances[from] = ptr; } // If the sender had too little, transferred collapses to 0 — no revert, no leak transferred = from != address(0) ? FHE.select(success, amount, FHE.asEuint64(0)) : amount; if (to != address(0)) { ptr = FHE.add(_confidentialBalances[to], transferred); FHE.allowThis(ptr); FHE.allow(ptr, to); _confidentialBalances[to] = ptr; } if (from != address(0)) FHE.allow(transferred, from); if (to != address(0)) FHE.allow(transferred, to); FHE.allowThis(transferred); indicatorToken.emitConfidentialTransfer(from, to); // nudge the display sidecar emit ConfidentialTransfer(from, to, transferred); } ``` Key behaviors baked into this function: `FHESafeMath.tryDecrease` returns an encrypted `success` flag. If the sender's balance is too low, `FHE.select(success, amount, 0)` sets the transferred amount to **encrypted zero** instead of reverting. This is deliberate: reverting would leak whether the sender had enough balance. The trade-off is that transfers can silently move zero tokens — always work with the returned `transferred` handle, never the requested amount. Each balance change stores a **new** `euint64` handle (`ptr`). Any `FHE.allow` you granted on a previous handle no longer applies to the new one. The engine re-grants `allowThis` + `allow(..., holder)` on every update so the contract and holder retain access. After each mutation the function grants: * `FHE.allowThis(...)` on the new balance and the transferred amount — so the contract can keep operating on them. * `FHE.allow(balance, holder)` — so the holder can decrypt their own balance. * `FHE.allow(transferred, from/to)` — so both parties can see what moved. Learn more in [Access Control](/fhe-library/core-concepts/access-control). *** ## The Indicator Sidecar Token On FHERC20 the token's own `balanceOf` doubles as a wallet "activity indicator." `ERC20Confidential` can't do that — its `balanceOf` is a real balance. Instead, the constructor deploys a **separate companion token**, `ERC20ConfidentialIndicator`, dedicated to showing confidential activity in wallets and explorers **without revealing real amounts**. ```solidity theme={null} constructor(address parentAddress, string memory parentName, string memory parentSymbol) ERC20(string.concat("1011000 ", parentName), string.concat("c", parentSymbol)) { parent = parentAddress; } function decimals() public pure override returns (uint8) { return 4; } function balanceOf(address account) public view override returns (uint256) { return 10110000000 + _indicatedBalances[account]; } ``` Characteristics of the sidecar: * **Name/symbol:** `"1011000 "` / `"c"` (e.g. `cMDT`), with **4 decimals**. * **Fake balances:** `balanceOf` returns `10110000000 + _indicatedBalances[account]` — a deliberately non-revealing number that changes with activity but encodes no real amount. * **Driven only by the parent:** `emitConfidentialTransfer(from, to)` is `onlyParent`. The parent calls it inside `_confidentialUpdate`, nudging a bounded internal counter and emitting a `Transfer(from, to, 10110000001)` so explorers register that *something* happened. * **Inert on its own:** `transfer`, `transferFrom`, `approve`, and `allowance` all revert with `ERC20ConfidentialIndicatorNoOp()`. You cannot move or approve the indicator token — it exists purely for display. ```solidity theme={null} function emitConfidentialTransfer(address from, address to) public onlyParent { _incrementIndicatedBalance(to); _decrementIndicatedBalance(from); emit Transfer(from, to, 10110000001); } ``` The indicator's counter jitters within bounds (increment seeds at `5001`, capped at `9999`; decrement seeds at `4999`, floored at `1`). These numbers are intentionally meaningless — they signal activity, not balance. Access the deployed sidecar via the parent's public `indicatorToken` variable. *** ## Reading Balances ```solidity Public Balance (real) theme={null} // Standard ERC-20 — returns the actual, visible balance uint256 pub = token.balanceOf(user); ``` ```solidity Confidential Balance (handle) theme={null} // Returns an encrypted euint64 handle, NOT the value euint64 handle = token.confidentialBalanceOf(user); ``` ```typescript Decrypt Confidential (off-chain) theme={null} import { FheTypes } from '@cofhe/sdk'; const handle = await token.confidentialBalanceOf(user.address); // Handle === 0 means "no confidential balance ever recorded", not "balance is zero" if (handle && handle !== 0n) { const balance = await cofheClient .decryptForView(handle, FheTypes.Uint64) .withPermit() .execute(); } ``` A confidential balance handle of `0` means the account has **never** held a confidential balance — not that the balance is zero. Always check before attempting to decrypt. *** ## Related Topics * Bridge value between layers in [Shield & Unshield](/fhe-library/confidential-contracts/dual-mode/shield-unshield) * Move confidential tokens in [Confidential Operations](/fhe-library/confidential-contracts/dual-mode/confidential-operations) * Understand the FHERC20 indicator design in [FHERC20 Core Features](/fhe-library/confidential-contracts/fherc20/core-features#the-indicator-system-in-detail) * Learn about [Access Control](/fhe-library/core-concepts/access-control) for FHE permissions # ERC20Confidential Overview Source: https://cofhe-docs.fhenix.zone/fhe-library/confidential-contracts/dual-mode/overview The dual-mode confidential token — a real public ERC-20 and an encrypted balance in one contract ## What is ERC20Confidential? `ERC20Confidential` is a **dual-mode** confidential token. Unlike [FHERC20](/fhe-library/confidential-contracts/fherc20/overview)—where every balance is encrypted and the public ERC-20 surface is only a decorative shim—`ERC20Confidential` gives each holder **two real balances in the same contract**: 1. A **public ERC-20 balance**, inherited from the standard OpenZeppelin `ERC20`. `balanceOf`, `transfer`, `approve`, `transferFrom`, and `totalSupply` all work exactly as they do on any normal token. 2. A **confidential, FHE-encrypted balance**, stored as an `euint64` handle. Nobody—not even the contract—can read it without an ACL grant and a decryption permit. Tokens move between the two layers with **shield** (public → confidential) and **unshield → claim** (confidential → public). Think of it as a normal ERC-20 with a built-in privacy pool baked directly into the token. A working public ERC-20 balance **and** a parallel encrypted balance for the same holder — no separate wrapper contract required. Convert value between the public and confidential layers directly on the token via `shield()` and `unshield()`. The public side is a genuine ERC-20, so existing wallets, DEXs, and explorers interact with it normally. The confidential side implements the same [ERC-7984](https://eips.ethereum.org/EIPS/eip-7984) API as FHERC20: `confidentialTransfer`, operators, and receiver callbacks. `ERC20Confidential` is an **abstract** contract. You deploy it by inheriting from it and adding your own mint/access-control logic (see the [Quick Start](#quick-start-example) below). *** ## ERC20Confidential vs FHERC20 Both live in the `fhenix-confidential-contracts` package and share the entire confidential-transfer API. The difference is what the **public ERC-20 surface** means. | Aspect | FHERC20 | ERC20Confidential (Dual-Mode) | | ------------------------------------------- | --------------------------------------------- | ----------------------------------------------------------- | | **Public `balanceOf`** | Returns an *indicator* (fake activity number) | Returns the **real** public balance | | **`transfer` / `approve` / `transferFrom`** | Revert (`FHERC20IncompatibleFunction`) | Fully functional public ERC-20 | | **`balanceOfIsIndicator()`** | `true` | `false` | | **`indicatorTick()`** | `> 0` | `0` | | **Confidential balance** | The *only* balance | A *second* balance alongside the public one | | **Public ↔ confidential bridge** | Separate wrapper contracts | Built into the token (`shield` / `unshield`) | | **Wallet activity indicator** | The token's own `balanceOf` | A **separate sidecar token** (`ERC20ConfidentialIndicator`) | | **Standard** | ERC-7984 (with an ERC-20 shim) | ERC-20 **and** ERC-7984 together | In short: **FHERC20 is confidential-only with a fake public face. ERC20Confidential is a real ERC-20 with a confidential layer bolted on.** If you want a token that trades openly on public DeFi *and* supports optional private holdings, use `ERC20Confidential`. If you want everything to be private with no public balances at all, use [FHERC20](/fhe-library/confidential-contracts/fherc20/overview). *** ## The Dual-Balance Model at a Glance ```solidity theme={null} abstract contract ERC20Confidential is ERC20, ERC165, IERC20Confidential, FHERC20WrapperClaimHelper { // Sentinel address that custodies the public tokens backing all confidential balances address public constant CONFIDENTIAL_POOL = address(0x1011000000000000000000000000000000000000); // The encrypted (confidential) balance layer mapping(address => euint64) private _confidentialBalances; // Time-based operator permissions for confidential transfers mapping(address => mapping(address => uint48)) private _operators; // Sidecar display token for wallets/explorers ERC20ConfidentialIndicator public immutable indicatorToken; } ``` Every confidential unit is **fully backed** by real public tokens sitting in `CONFIDENTIAL_POOL`. When you shield, your public tokens are physically transferred into that pool; when you unshield and claim, they come back out. The encrypted ledger simply tracks who owns what inside the pool. Real, transparent balances. Anyone can see them. Standard `transfer` / `approve` / `transferFrom` apply. Move public tokens into the confidential pool and mint yourself an equal encrypted balance. Transfer privately with `confidentialTransfer`, delegate with operators, and receive with callbacks — all on encrypted amounts. Burn confidential tokens (async), decrypt the burned amount off-chain, then claim the equivalent public tokens back out of the pool. See [The Dual-Balance Model](/fhe-library/confidential-contracts/dual-mode/dual-balance-model) for a full breakdown of the pool, the conversion rate, and the indicator sidecar token. *** ## Decimals and the Conversion Rate The public token can use **any** number of decimals (e.g. 18, to match mainstream ERC-20s). The confidential layer is capped at **6 decimals** because encrypted balances are `euint64` and must avoid overflow. ```solidity theme={null} constructor(string memory name_, string memory symbol_, uint8 decimals_) ERC20(name_, symbol_) { indicatorToken = new ERC20ConfidentialIndicator(address(this), name_, symbol_); _decimals = decimals_; _confidentialDecimals = decimals_ <= 6 ? decimals_ : 6; _conversionRate = decimals_ > 6 ? 10 ** (decimals_ - 6) : 1; } ``` For an 18-decimal token, `_conversionRate = 10^(18 - 6) = 10^12`. One confidential unit equals `10^12` public base units. Shielded amounts are always rounded down to a whole multiple of this rate. Query the two decimal values with `decimals()` (public) and `confidentialDecimals()` (confidential). For tokens with 6 or fewer decimals the rate is `1` and the two layers map 1:1. *** ## Contract Variants **Base (constructor) implementation** Abstract dual-balance token. Inherit it and add your mint / ownership logic. **Upgradeable variant** Identical API, built on OpenZeppelin's upgradeable contracts with ERC-7201 namespaced storage and an `__ERC20Confidential_init(...)` initializer instead of a constructor. **Sidecar display token** Auto-deployed by the constructor. Shows non-revealing "activity" balances in wallets and explorers. All of its mutative functions revert. **Interface** `IERC20Confidential is IFHERC20` — adds the shield/unshield bridge to the shared confidential-token surface. *** ## Quick Start Example Because `ERC20Confidential` is abstract, you inherit it and expose whatever minting policy you need. Minting creates **public** tokens; holders then shield them to go confidential. ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; import { ERC20Confidential } from "fhenix-confidential-contracts/contracts/ERC20Confidential/ERC20Confidential.sol"; contract MyDualToken is ERC20Confidential { // 18 public decimals; the confidential layer is auto-capped to 6 constructor() ERC20Confidential("My Dual Token", "MDT", 18) {} // Mints public ERC-20 tokens (OpenZeppelin _mint) function mint(address to, uint256 amount) external { _mint(to, amount); } } ``` Using the token end-to-end: ```typescript theme={null} import { Encryptable } from '@cofhe/sdk'; // 1. Mint public tokens — a normal, transparent ERC-20 balance await token.mint(user.address, ethers.parseUnits("1000", 18)); const publicBal = await token.balanceOf(user.address); // real balance, visible to all // 2. Shield 500 tokens into the confidential layer await token.shield(ethers.parseUnits("500", 18)); // 3. Transfer confidentially — amount is encrypted const [encAmount] = await cofheClient .encryptInputs([Encryptable.uint64(100_000_000n)]) // 100 tokens at 6 confidential decimals .execute(); await token.confidentialTransfer(recipient.address, encAmount); // 4. Read your encrypted balance (returns a handle you decrypt off-chain) const handle = await token.confidentialBalanceOf(user.address); ``` The example `mint` is unguarded for illustration only. In production, gate minting behind access control (e.g. `Ownable` / `AccessControl`). *** ## Next Steps The public/confidential split, the backing pool, the conversion rate, and the indicator sidecar token. The bridge between layers: synchronous shielding and the asynchronous unshield/claim flow. Confidential transfers, the operator system, and transfer callbacks. Backing, privacy boundaries, zero-replacement, and access-control pitfalls. *** ## Related Topics * Compare with the confidential-only [FHERC20 standard](/fhe-library/confidential-contracts/fherc20/overview) * Learn about FHE operations in [Encrypted Operations](/fhe-library/core-concepts/encrypted-operations) * Understand permissions in [Access Control](/fhe-library/core-concepts/access-control) * Encrypt inputs and decrypt handles with the [Client SDK](/client-sdk/introduction/overview) # Shield & Unshield Source: https://cofhe-docs.fhenix.zone/fhe-library/confidential-contracts/dual-mode/shield-unshield The built-in bridge that moves value between the public and confidential layers of ERC20Confidential ## Overview `ERC20Confidential` bridges its two balances with three operations. **Shielding** (public → confidential) is synchronous. **Unshielding** (confidential → public) is a two-step, asynchronous flow because FHE decryption happens off-chain. Move public tokens into the confidential pool and mint yourself an equal encrypted balance — in a single transaction. Burn confidential tokens, decrypt the burned amount off-chain, then claim the equivalent public tokens back out of the pool. Both directions are governed by the [conversion rate](/fhe-library/confidential-contracts/dual-mode/dual-balance-model#conversion-rate-and-decimals): `_rate()` public base units equal one confidential unit. *** ## Shielding (Public → Confidential) ```solidity theme={null} function shield(uint256 amount) public virtual { uint256 rate = _rate(); uint256 amountToShield = amount - (amount % rate); // round down to confidential precision if (amountToShield == 0) { revert AmountTooSmallForConfidentialPrecision(); } uint64 amountConfidential = SafeCast.toUint64(amountToShield / rate); _transfer(msg.sender, CONFIDENTIAL_POOL, amountToShield); // public tokens -> pool _confidentialUpdate(address(0), msg.sender, FHE.asEuint64(amountConfidential)); // mint confidential emit TokensShielded(msg.sender, amountToShield); } ``` **What happens:** `amount` is rounded down to the nearest whole multiple of `_rate()`. If that leaves zero, the call reverts with `AmountTooSmallForConfidentialPrecision`. The rounded amount is transferred from the caller's public balance into `CONFIDENTIAL_POOL` via the standard ERC-20 `_transfer`. An equal (rate-scaled) encrypted amount is credited to the caller's confidential balance through `_confidentialUpdate(address(0), msg.sender, ...)`. `TokensShielded(msg.sender, amountToShield)` records the public amount that was shielded. Shielding requires no approval — it moves the caller's **own** public balance. The caller must already hold at least `amountToShield` public tokens. ### Example ```typescript theme={null} // Token has 18 public decimals → rate = 1e12, confidential decimals = 6 // Shield 500 tokens await token.shield(ethers.parseUnits("500", 18)); // → 500e18 public tokens move to CONFIDENTIAL_POOL // → 500_000_000 confidential units (500 at 6 decimals) minted to the caller // Amounts not aligned to the rate are truncated: await token.shield(ethers.parseUnits("1.5000005", 18)); // → only 1.5 tokens (1.5e18) are shielded; the sub-rate dust stays in the public balance // Below one confidential unit reverts: await token.shield(999n); // rate = 1e12 → rounds to 0 → AmountTooSmallForConfidentialPrecision ``` *** ## Unshielding (Confidential → Public) Unshielding is asynchronous. `unshield` burns the confidential tokens and marks the burned ciphertext publicly decryptable; the network decrypts it off-chain; `claimUnshielded` then verifies the decryption proof and releases the public tokens. ### Step 1 — Burn and create a claim There are two overloads: one takes a plaintext `uint64`, the other an already-encrypted `euint64` handle. ```solidity theme={null} function unshield(uint64 amount) public virtual returns (euint64) { return _unshield(FHE.asEuint64(amount), amount); } function unshield(euint64 amount) public virtual returns (euint64) { if (!FHE.isAllowed(amount, msg.sender)) { revert ERC20ConfidentialUnauthorizedUseOfEncryptedAmount(amount, msg.sender); } return _unshield(amount, 0); } function _unshield(euint64 amount, uint64 requestedAmount) internal virtual returns (euint64 burned) { burned = _confidentialUpdate(msg.sender, address(0), amount); // burn from confidential balance FHE.allowPublic(burned); // make the burned handle publicly decryptable _createClaim(msg.sender, requestedAmount, burned); emit TokensUnshielded(msg.sender, burned); } ``` Because of [zero-replacement](/fhe-library/confidential-contracts/dual-mode/dual-balance-model#the-confidential-update-engine), unshielding more than your confidential balance burns **zero** — it does not revert. You'll end up with a claim for zero tokens. Ensure sufficient balance before unshielding. ### Step 2 — Decrypt off-chain `unshield` returns the burned `euint64` handle and calls `FHE.allowPublic(burned)`, so **anyone** can decrypt it — no permit needed. Retrieve the claim's `ctHash` and request decryption: ```typescript theme={null} // Find the pending claim's ctHash const claims = await token.getUserClaims(user.address); const ctHash = claims[claims.length - 1].ctHash; // Decrypt off-chain — no permit required (allowPublic was called) const decryptResult = await cofheClient .decryptForTx(ctHash) .withoutPermit() .execute(); // decryptResult.decryptedValue → the plaintext confidential amount // decryptResult.signature → the decryption proof ``` ### Step 3 — Claim the public tokens Submit the plaintext and proof. The contract verifies the proof (via `FHE.verifyDecryptResult`), then transfers the rate-scaled public amount out of the pool. ```solidity theme={null} function claimUnshielded(bytes32 ctHash, uint64 decryptedAmount, bytes calldata decryptionProof) public virtual { Claim memory claim = _handleClaim(ctHash, decryptedAmount, decryptionProof); // verifies proof uint256 amountPublic = uint256(claim.decryptedAmount) * _rate(); _transfer(CONFIDENTIAL_POOL, claim.to, amountPublic); // pool -> claimant emit UnshieldedTokensClaimed(claim.to, ctHash, FHE.wrapEuint64(ctHash), claim.decryptedAmount); } ``` ```typescript theme={null} const tx = await token.claimUnshielded( decryptResult.ctHash, decryptResult.decryptedValue, decryptResult.signature ); await tx.wait(); // Public tokens have been transferred from CONFIDENTIAL_POOL back to the claimant const publicBal = await token.balanceOf(user.address); ``` The public payout goes to the claim's stored `to` (the address that called `unshield`), regardless of who submits the `claimUnshielded` transaction. The proof — not the sender — authorizes the release. *** ## Batch Claiming Claim several pending unshields in one transaction: ```solidity theme={null} function claimUnshieldedBatch( bytes32[] calldata ctHashes, uint64[] calldata decryptedAmounts, bytes[] calldata decryptionProofs ) public virtual; ``` ```typescript theme={null} const claims = await token.getUserClaims(user.address); const ctHashes = [], amounts = [], proofs = []; for (const claim of claims) { const r = await cofheClient.decryptForTx(claim.ctHash).withoutPermit().execute(); ctHashes.push(r.ctHash); amounts.push(r.decryptedValue); proofs.push(r.signature); } await token.claimUnshieldedBatch(ctHashes, amounts, proofs); ``` The three arrays must be the same length, or the call reverts with `LengthMismatch`. Each entry is processed by the same `_handleClaim` used by the single-claim path. *** ## Claim Lifecycle Claims are tracked by the inherited `FHERC20WrapperClaimHelper`. ```solidity theme={null} struct Claim { address to; // recipient of the public tokens bytes32 ctHash; // ciphertext hash identifying the claim uint64 requestedAmount; // amount requested at unshield time (0 for the euint64 overload) uint64 decryptedAmount; // actual decrypted amount (set at claim time) bool claimed; // whether the public tokens have been released } ``` ### Querying claims ```solidity Single Claim theme={null} function getClaim(bytes32 ctHash) public view returns (Claim memory); ``` ```solidity Pending User Claims theme={null} // Returns only unclaimed (pending) claims for a user function getUserClaims(address user) public view returns (Claim[] memory); ``` `getUserClaims` returns only **pending** claims — a claim is removed from the user's set once it is successfully claimed. Use it to drive a "claimable balance" view in your UI. ### Claim errors ```solidity theme={null} error ClaimNotFound(); // ctHash has no associated claim error AlreadyClaimed(); // this claim was already settled error LengthMismatch(); // batch arrays differ in length ``` *** ## Full Round-Trip Example ```typescript theme={null} import { Encryptable } from '@cofhe/sdk'; // --- Public → Confidential --- await token.mint(user.address, ethers.parseUnits("1000", 18)); // public tokens await token.shield(ethers.parseUnits("400", 18)); // → 400 confidential // --- Transact confidentially --- const [enc] = await cofheClient .encryptInputs([Encryptable.uint64(150_000_000n)]) // 150 tokens (6 decimals) .execute(); await token.confidentialTransfer(recipient.address, enc); // --- Confidential → Public --- await token.unshield(100_000_000n); // burn 100 confidential, create claim const claims = await token.getUserClaims(user.address); const { ctHash, decryptedValue, signature } = await cofheClient .decryptForTx(claims[0].ctHash) .withoutPermit() .execute(); await token.claimUnshielded(ctHash, decryptedValue, signature); // → 100e18 public tokens returned from CONFIDENTIAL_POOL to the user ``` *** ## Events ```solidity theme={null} // Public tokens shielded into the confidential layer event TokensShielded(address indexed account, uint256 amount); // Unshield request created (confidential tokens burned) event TokensUnshielded(address indexed account, euint64 indexed amount); // Public tokens released after a verified claim event UnshieldedTokensClaimed( address indexed account, bytes32 indexed unshieldRequestId, euint64 indexed unshieldAmount, uint64 unshieldAmountCleartext ); ``` *** ## Related Topics * Understand the backing pool and rate in [The Dual-Balance Model](/fhe-library/confidential-contracts/dual-mode/dual-balance-model) * Move tokens privately with [Confidential Operations](/fhe-library/confidential-contracts/dual-mode/confidential-operations) * Compare with the [FHERC20 wrapper unshield flow](/fhe-library/confidential-contracts/fherc20/fherc20-wrapper#unshielding-tokens) * Decrypt handles off-chain with the [Client SDK — Decrypt to Transaction](/client-sdk/guides/decrypt-to-tx) # Best Practices Source: https://cofhe-docs.fhenix.zone/fhe-library/confidential-contracts/fherc20/best-practices Security, gas optimization, and implementation guidelines for FHERC20 tokens ## Overview Building with FHERC20 requires understanding both traditional smart contract best practices and the unique considerations that come with fully homomorphic encryption. This guide provides actionable recommendations for secure, efficient, and privacy-preserving implementations. *** ## Security Best Practices **Risk:** Operators have unlimited access to a user's balance until expiration. **Recommendation:** ```solidity theme={null} // ✅ Use short expiration times for specific operations function authorizeSwap(address dex) external { // 10 minutes - just enough for the transaction token.setOperator(dex, uint48(block.timestamp + 10 minutes)); } // ❌ Avoid indefinite permissions unless absolutely necessary function dangerousApproval(address spender) external { // This grants permanent access - very risky! token.setOperator(spender, type(uint48).max); } ``` **Best practices:** * Grant operators only for the minimum necessary time * Use specific timeframes: 5-10 minutes for single transactions, 1 day for recurring operations * Document operator requirements clearly in your UI * Provide easy revocation by calling `setOperator(address, block.timestamp)` **Risk:** Insufficient balance transfers zero tokens instead of reverting, which can lead to unexpected behavior. **Recommendation:** ```solidity theme={null} // ✅ Check balance before operations that depend on success function safeSwap(address tokenIn, address tokenOut, uint64 amountIn) external { euint64 balance = IFHERC20(tokenIn).confidentialBalanceOf(msg.sender); // Request decryption to verify balance (in real implementation) // For demo, assume we have a way to verify // Only proceed if we can verify sufficient balance require(canVerifyBalance(balance, amountIn), "Insufficient balance"); euint64 transferred = IFHERC20(tokenIn).confidentialTransfer( address(this), amountIn ); // Perform swap logic... } // ❌ Don't assume transfers always succeed function dangerousSwap(address tokenIn, uint64 amountIn) external { // This might transfer zero tokens! IFHERC20(tokenIn).confidentialTransfer(address(this), amountIn); // Continuing as if transfer succeeded is dangerous _executeSwap(amountIn); // ❌ Wrong amount! } ``` **Best practices:** * Always work with the returned `euint64 transferred` value * Implement balance checks when transfer success is critical * Use the transferred amount in subsequent operations, not the requested amount * Consider using transfer callbacks for atomic operations **Risk:** Improper FHE access control can prevent users from accessing their own balances or expose data to unauthorized parties. **Recommendation:** ```solidity theme={null} // ✅ Grant appropriate access after balance updates function _updateBalanceWithAccess(address account, euint64 newBalance) internal { _confidentialBalances[account] = newBalance; // Contract needs access for operations FHE.allowThis(newBalance); // User needs access to query their balance FHE.allow(newBalance, account); } // ✅ Grant access to transferred amounts function confidentialTransfer(address to, euint64 value) external returns (euint64 transferred) { transferred = _transfer(msg.sender, to, value); // Both parties should be able to see what was transferred FHE.allow(transferred, msg.sender); FHE.allow(transferred, to); return transferred; } // ❌ Don't forget to grant access function badTransfer(address to, euint64 value) external { euint64 transferred = _transfer(msg.sender, to, value); // Forgot FHE.allow calls - users can't see transferred amount! return transferred; } ``` **Best practices:** * Always call `FHE.allowThis()` for values the contract needs to use * Always call `FHE.allow(value, user)` for values users need to access * Grant access immediately after creating or modifying encrypted values * Review access control on all encrypted state changes **Risk:** The `onConfidentialTransferReceived` callback is executed during the transfer, creating reentrancy opportunities. **Recommendation:** ```solidity theme={null} // ✅ Use reentrancy guards import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; contract SafeReceiver is IERC7984Receiver, ReentrancyGuard { function onConfidentialTransferReceived( address operator, address from, euint64 amount, bytes calldata data ) external override nonReentrant returns (ebool) { // Protected against reentrancy attacks _processDeposit(from, amount); return FHE.asEbool(true); } } // ✅ Follow checks-effects-interactions pattern contract SecureStaking is IERC7984Receiver { mapping(address => euint64) public stakedBalances; function onConfidentialTransferReceived( address operator, address from, euint64 amount, bytes calldata data ) external override returns (ebool) { // Checks require(msg.sender == address(stakingToken), "Wrong token"); // Effects (update state first) stakedBalances[from] = stakedBalances[from] + amount; FHE.allowThis(stakedBalances[from]); FHE.allow(stakedBalances[from], from); // Interactions (external calls last) if (data.length > 0) { _notifyReferral(from, data); } return FHE.asEbool(true); } } ``` **Best practices:** * Use OpenZeppelin's ReentrancyGuard for all receiver implementations * Follow checks-effects-interactions pattern * Minimize external calls in callbacks * Keep callback logic simple and gas-efficient **Risk:** Unvalidated inputs can lead to unexpected behavior or security vulnerabilities. **Recommendation:** ```solidity theme={null} // ✅ Validate all inputs function onConfidentialTransferReceived( address operator, address from, euint64 amount, bytes calldata data ) external override returns (ebool) { // Validate token source if (msg.sender != address(trustedToken)) { return FHE.asEbool(false); } // Validate sender (if needed) if (!isAllowedSender(from)) { return FHE.asEbool(false); } // Validate data length and content if (data.length > 0) { if (data.length != 64) { return FHE.asEbool(false); // Expected specific data format } // Safely decode with bounds checking (uint256 lockDuration, address referrer) = abi.decode( data, (uint256, address) ); if (lockDuration > 365 days) { return FHE.asEbool(false); // Reject unreasonable durations } if (referrer == address(0)) { return FHE.asEbool(false); // Reject zero address } } return FHE.asEbool(true); } // ❌ Don't trust inputs blindly function dangerousReceiver( address operator, address from, euint64 amount, bytes calldata data ) external override returns (ebool) { // Accepts anything - very dangerous! _processData(data); // Could contain malicious data return FHE.asEbool(true); } ``` **Best practices:** * Validate token source (`msg.sender`) * Validate transfer initiator (`operator`) and source (`from`) when needed * Check data length before decoding * Validate decoded parameters for reasonable bounds * Return `FHE.asEbool(false)` to reject invalid transfers *** ## Operators Best Practices ### When to Use Operators **Use operators when:** * User directly approves via wallet transaction * Simple on-chain permission grants ```solidity theme={null} // User directly approves DEX await token.connect(user).setOperator( dexAddress, Math.floor(Date.now() / 1000) + 3600 ); // DEX can now swap on behalf of user await dex.swap(tokenIn, tokenOut, amountIn); ``` **Advantages:** * Simple, direct approach * No signature complexity * Immediate effect **Use operators when:** * Granting recurring permissions ```solidity theme={null} // Grant 30-day subscription access await token.setOperator( subscriptionContract, uint48(block.timestamp + 30 days) ); ``` ### Comparison Table | Factor | Operator (setOperator) | | --------------------- | ---------------------- | | **Gas Cost** | User pays gas | | **Transaction Count** | 2 (approve + action) | | **Complexity** | Simple | | **User Experience** | Standard wallet flow | | **Use Case** | General permissions | | **Implementation** | Direct on-chain | *** ## Common Pitfalls and Solutions **Problem:** ```solidity theme={null} function badMint(address to, uint64 amount) internal { euint64 value = FHE.asEuint64(amount); _confidentialBalances[to] = _confidentialBalances[to] + value; // ❌ Forgot to grant access! // User and contract can't read the balance } ``` **Solution:** ```solidity theme={null} function goodMint(address to, uint64 amount) internal { euint64 value = FHE.asEuint64(amount); _confidentialBalances[to] = _confidentialBalances[to] + value; // ✅ Grant necessary access FHE.allowThis(_confidentialBalances[to]); // Contract can use it FHE.allow(_confidentialBalances[to], to); // User can query it } ``` **Problem:** ```solidity theme={null} function badSwap(address tokenIn, uint64 amountIn) external { // Transfer might return zero! token.confidentialTransfer(address(this), amountIn); // ❌ Assumes transfer succeeded with full amount _executeSwap(amountIn); // Wrong if transfer was zero! } ``` **Solution:** ```solidity theme={null} function goodSwap(address tokenIn, uint64 amountIn) external { // Use the actual transferred amount euint64 transferred = token.confidentialTransfer( address(this), amountIn ); // ✅ Work with what was actually transferred _executeSwap(transferred); // Or check balance first if exact amount is critical } ``` **Problem:** ```solidity theme={null} function badUnshield(uint64 amount) external { wrapper.unshield(msg.sender, msg.sender, amount); // ❌ Trying to claim without decryption proof! wrapper.claimUnshielded(someClaim, amount, ""); } ``` **Solution:** ```typescript theme={null} // ✅ Correct flow // 1. Request unshield const tx = await wrapper.unshield(userAddress, userAddress, amount); await tx.wait(); // 2. Get the claim's ctHash const claims = await wrapper.getUserClaims(userAddress); const latestClaim = claims[claims.length - 1]; // 3. Decrypt off-chain to get plaintext + proof const decryptResult = await client .decryptForTx(latestClaim.ctHash) .withoutPermit() .execute(); // 4. Claim with proof await wrapper.claimUnshielded( decryptResult.ctHash, decryptResult.decryptedValue, decryptResult.signature ); ``` **Problem:** ```javascript theme={null} // ❌ Expiration in milliseconds (wrong!) await token.setOperator( spender, Date.now() + 3600000 // JavaScript timestamp ); ``` **Solution:** ```javascript theme={null} // ✅ Expiration in seconds (Unix timestamp) const expirationTime = Math.floor(Date.now() / 1000) + 3600; await token.setOperator( spender, expirationTime ); // ✅ Or use block.timestamp from contract const currentBlock = await ethers.provider.getBlock('latest'); const until = currentBlock.timestamp + 3600; await token.setOperator(spender, until); ``` **Problem:** ```solidity theme={null} function badReceiver( address operator, address from, euint64 amount, bytes calldata data ) external override returns (ebool) { _processTokens(from, amount); // ❌ Always returns true, even if processing failed! return FHE.asEbool(true); } ``` **Solution:** ```solidity theme={null} function goodReceiver( address operator, address from, euint64 amount, bytes calldata data ) external override returns (ebool) { // ✅ Validate before accepting if (msg.sender != address(trustedToken)) { return FHE.asEbool(false); } if (!isValidSender(from)) { return FHE.asEbool(false); } // Process with error handling try this._processTokens(from, amount, data) { return FHE.asEbool(true); } catch { return FHE.asEbool(false); } } ``` *** ## Related Topics * Review [Core Features](/fhe-library/confidential-contracts/fherc20/core-features) for fundamental concepts * Learn about [Operators](/fhe-library/confidential-contracts/fherc20/operators) for permission management * Explore [Transfer Callbacks](/fhe-library/confidential-contracts/fherc20/transfer-callbacks) for safe transfers # Core Features Source: https://cofhe-docs.fhenix.zone/fhe-library/confidential-contracts/fherc20/core-features Deep dive into FHERC20's encrypted balances, confidential transfers, and balance queries ## Overview FHERC20's core functionality revolves around maintaining complete confidentiality for token balances and transfers while still enabling all the operations users expect from a token. This page explores the fundamental features that make FHERC20 work. *** ## Encrypted Balances ### Storage Structure FHERC20 maintains two parallel balance systems: ```solidity theme={null} // Confidential balances (the real balances) mapping(address account => euint64) private _confidentialBalances; // Indicator balances (for wallet compatibility) mapping(address account => uint16) internal _indicatedBalances; ``` **Type:** `euint64` Real token balances encrypted using FHE. Only accessible with proper permissions, operations performed entirely on encrypted data. **Type:** `uint16` Non-confidential activity indicators (0-9999) that provide visual feedback in wallets without revealing actual amounts. ### Balance Encryption When tokens are minted or transferred, the amounts are always encrypted: ```solidity theme={null} function _mint(address account, uint64 value) internal returns (euint64 transferred) { // Convert plaintext to encrypted euint64 amount = FHE.asEuint64(value); // Add to encrypted balance _confidentialBalances[account] = _confidentialBalances[account] + amount; // Update indicator (non-confidential) _indicatedBalances[account] = _incrementIndicator(_indicatedBalances[account]); return amount; } ``` The `euint64` type can store values up to 2^64 - 1, which is 18,446,744,073,709,551,615. For tokens with 6 decimals (recommended), this is equivalent to about 18.4 trillion tokens with full precision. *** ## Total Supply Like balances, total supply is maintained in both confidential and indicated forms: ```solidity theme={null} // Real total supply (encrypted) euint64 private _confidentialTotalSupply; // Indicated total supply (for display) uint16 internal _indicatedTotalSupply; ``` ### Querying Total Supply ```solidity Confidential Total Supply theme={null} // Returns encrypted total supply function confidentialTotalSupply() public view returns (euint64) { return _confidentialTotalSupply; } ``` ```solidity Indicated Total Supply theme={null} // Returns indicator value (ERC20 compatible) function totalSupply() public view returns (uint256) { return _indicatedTotalSupply; } ``` *** ## Confidential Transfers FHERC20 provides multiple transfer functions to handle different scenarios. ### Basic Transfer The fundamental transfer operation moves encrypted tokens from the caller to a recipient: ```solidity With Encrypted Input theme={null} function confidentialTransfer( address to, InEuint64 memory encryptedAmount ) external returns (euint64 transferred) { // Convert encrypted input to euint64 euint64 value = FHE.asEuint64(encryptedAmount); // Perform encrypted transfer return _transfer(msg.sender, to, value); } ``` ```solidity With Already-Encrypted Value theme={null} function confidentialTransfer( address to, euint64 amount ) external returns (euint64 transferred) { // Value is already encrypted - verify caller has access if (!FHE.isAllowed(amount, msg.sender)) { revert FHERC20UnauthorizedUseOfEncryptedAmount(amount, msg.sender); } return _transfer(msg.sender, to, amount); } ``` Use the `InEuint64` overload when accepting user input from off-chain. Use the `euint64` overload for contract-to-contract transfers where the value is already encrypted. Note: the `euint64` overload requires the caller to be authorized via the FHE ACL for the given amount. ### Transfer Implementation The internal `_transfer` function handles the actual movement: ```solidity theme={null} function _transfer( address from, address to, euint64 value ) internal returns (euint64 transferred) { if (from == address(0)) revert FHERC20InvalidSender(address(0)); if (to == address(0)) revert FHERC20InvalidReceiver(address(0)); return _update(from, to, value); } ``` ### The Update Function The `_update` function is the core of all balance changes. It uses `FHESafeMath` for overflow/underflow protection on encrypted values: ```solidity theme={null} function _update( address from, address to, euint64 value ) internal virtual returns (euint64 transferred) { // Handle transfers (not mints or burns) if (from != address(0)) { // Check if user has sufficient balance // If not, transfer zero instead (privacy-preserving) transferred = FHE.select( value.lte(_confidentialBalances[from]), value, FHE.asEuint64(0) ); // Subtract from sender using safe math _confidentialBalances[from] = FHE.sub(_confidentialBalances[from], transferred); _indicatedBalances[from] = _decrementIndicator(_indicatedBalances[from]); } else { // Minting transferred = value; } if (from == address(0)) { // Minting - update total supply _indicatedTotalSupply = _incrementIndicator(_indicatedTotalSupply); _confidentialTotalSupply = FHE.add(_confidentialTotalSupply, transferred); } if (to == address(0)) { // Burning - update total supply _indicatedTotalSupply = _decrementIndicator(_indicatedTotalSupply); _confidentialTotalSupply = FHE.sub(_confidentialTotalSupply, transferred); } else { // Normal transfer - add to recipient _confidentialBalances[to] = FHE.add(_confidentialBalances[to], transferred); _indicatedBalances[to] = _incrementIndicator(_indicatedBalances[to]); } // Update CoFHE Access Control List (ACL) if (euint64.unwrap(_confidentialBalances[from]) != 0) { FHE.allowThis(_confidentialBalances[from]); FHE.allow(_confidentialBalances[from], from); FHE.allow(transferred, from); } if (euint64.unwrap(_confidentialBalances[to]) != 0) { FHE.allowThis(_confidentialBalances[to]); FHE.allow(_confidentialBalances[to], to); FHE.allow(transferred, to); } // Allow the caller to access the transferred amount FHE.allow(transferred, msg.sender); // Hide totalSupply FHE.allowThis(_confidentialTotalSupply); // Emit events emit Transfer(from, to, _indicatorTick); emit ConfidentialTransfer(from, to, euint64.unwrap(transferred)); return transferred; } ``` **Zero-Replacement Behavior:** If a user attempts to transfer more than their balance, FHERC20 **does not revert**. Instead, it transfers zero tokens. This preserves privacy by not revealing whether the user had sufficient balance. *** ## Balance Queries ### Confidential Balance Returns the encrypted balance for an account: ```solidity theme={null} function confidentialBalanceOf(address account) public view returns (euint64) { return _confidentialBalances[account]; } ``` **Important:** The returned `euint64` is still encrypted! You cannot read its value directly. To use it: ```solidity In Smart Contract theme={null} // Use in FHE operations euint64 balance = token.confidentialBalanceOf(user); euint64 doubled = balance + balance; ``` ```typescript Off-Chain (with @cofhe/sdk) theme={null} // Request decryption for UI display const encBalance = await token.confidentialBalanceOf(userAddress); const result = await client .decryptForView(encBalance) .withPermit() .execute(); console.log(`Balance: ${result.decryptedValue}`); ``` ### Indicator Balance Returns the non-confidential indicator value: ```solidity theme={null} function balanceOf(address account) public view returns (uint256) { return _indicatedBalances[account]; } ``` This returns a value between 0 and 9999, representing the activity indicator, **not the real balance**. ### Balance Of Is Indicator Returns `true`, signalling that `balanceOf` returns an indicator, not a real balance: ```solidity theme={null} function balanceOfIsIndicator() external view returns (bool) { return true; } ``` This is part of the ERC-7984 standard and helps wallets and block explorers understand the nature of the returned value. *** ## Access Control for Balances As shown in the `_update` function above, access control is managed inline as part of each balance update: ```solidity theme={null} // Update CoFHE Access Control List (ACL) if (euint64.unwrap(_confidentialBalances[from]) != 0) { FHE.allowThis(_confidentialBalances[from]); // Contract can use balance FHE.allow(_confidentialBalances[from], from); // User can query balance FHE.allow(transferred, from); // User can see transferred amount } if (euint64.unwrap(_confidentialBalances[to]) != 0) { FHE.allowThis(_confidentialBalances[to]); FHE.allow(_confidentialBalances[to], to); FHE.allow(transferred, to); } // Allow the caller to decrypt the transferred amount FHE.allow(transferred, msg.sender); // Hide totalSupply (only contract has access) FHE.allowThis(_confidentialTotalSupply); ``` This ensures: * ✅ Users can access their own balances * ✅ The contract can perform operations on balances * ✅ Transfer participants (sender, receiver, and caller) can see the transferred amount * ✅ Total supply is only accessible by the contract Learn more about FHE access control in the [Access Control](/fhe-library/core-concepts/access-control) guide. *** ## Minting and Burning ### Minting New Tokens ```solidity theme={null} function _mint(address account, uint64 value) internal returns (euint64 transferred) { if (account == address(0)) { revert FHERC20InvalidReceiver(address(0)); } // Convert plaintext value to encrypted and mint // The _update function handles total supply updates when from == address(0) transferred = _update(address(0), account, FHE.asEuint64(value)); } ``` There's also a confidential mint variant that accepts already-encrypted values: ```solidity theme={null} function _confidentialMint(address account, euint64 value) internal returns (euint64 transferred) { if (account == address(0)) { revert FHERC20InvalidReceiver(address(0)); } // Value is already encrypted transferred = _update(address(0), account, value); } ``` ### Burning Tokens ```solidity theme={null} function _burn(address account, uint64 value) internal returns (euint64 transferred) { if (account == address(0)) { revert FHERC20InvalidSender(address(0)); } // The _update function handles total supply updates when to == address(0) transferred = _update(account, address(0), FHE.asEuint64(value)); } ``` There's also a confidential burn variant: ```solidity theme={null} function _confidentialBurn(address account, euint64 value) internal returns (euint64 transferred) { if (account == address(0)) { revert FHERC20InvalidSender(address(0)); } transferred = _update(account, address(0), value); } ``` Like transfers, burning uses the zero-replacement pattern. If you attempt to burn more than an account's balance, zero tokens are burned instead of reverting. *** ## Amount Disclosure FHERC20 includes optional disclosure functions for transparency when needed. Accounts with access to an encrypted amount can voluntarily reveal it on-chain. ### Requesting Disclosure ```solidity theme={null} function requestDiscloseEncryptedAmount(euint64 amount) external; ``` Initiates a disclosure request for an encrypted amount. The caller must have FHE access to the amount. ### Completing Disclosure ```solidity theme={null} function discloseEncryptedAmount(euint64 amount, uint64 cleartext, bytes memory proof) external; ``` Completes the disclosure by providing the cleartext value and a decryption proof. Emits: ```solidity theme={null} event AmountDisclosed(euint64 indexed encryptedAmount, uint64 amount); ``` *** ## The Indicator System in Detail ### Indicator Values Indicators range from `0` to `9999`, representing values from `0.0000` to `0.9999`: ```solidity theme={null} uint16 indicator = 7984; // Represents 0.7984 ``` ### Indicator Lifecycle New accounts start with an indicator of `0`. Upon first transfer (sent or received), the indicator initializes to `7984` (0.7984), referencing the ERC-7984 standard. Each time tokens are received, the indicator increases by `1` (0.0001). Each time tokens are sent, the indicator decreases by `1` (0.0001). When the indicator reaches `9999`, it wraps back to `0`. ### Indicator Functions ```solidity theme={null} // Increment indicator (add 0.0001) function _incrementIndicator(uint16 current) internal pure returns (uint16) { if (current == 0 || current == 9999) return 7984; return current + 1; } // Decrement indicator (subtract 0.0001) function _decrementIndicator(uint16 value) internal pure returns (uint16) { if (value == 0 || value == 1) return 7984; return value - 1; } ``` ### Indicator Tick The `indicatorTick` is the amount reported in `Transfer` events and is calculated during contract construction: ```solidity theme={null} constructor(string memory name_, string memory symbol_, uint8 decimals_) { _name = name_; _symbol = symbol_; _decimals = decimals_; // Calculate indicator tick based on decimals _indicatorTick = decimals_ <= 4 ? 1 : 10 ** (decimals_ - 4); } ``` You can query it with: ```solidity theme={null} function indicatorTick() public view returns (uint256) { return _indicatorTick; } ``` For a token with 6 decimals (recommended): * `_indicatorTick = 10^2 = 100` * This represents 0.0001 tokens ### Resetting Indicators Users can reset their indicator to zero for privacy: ```solidity theme={null} function resetIndicatedBalance() external { _indicatedBalances[msg.sender] = 0; } ``` *** ## Errors FHERC20 defines the following custom errors: ```solidity theme={null} /// @dev The given receiver is invalid for transfers. error FHERC20InvalidReceiver(address receiver); /// @dev The given sender is invalid for transfers. error FHERC20InvalidSender(address sender); /// @dev The holder is not authorized to spend on behalf of spender. error FHERC20UnauthorizedSpender(address holder, address spender); /// @dev The holder is trying to send tokens but has a balance of 0. error FHERC20ZeroBalance(address holder); /// @dev The caller does not have access to the encrypted amount. error FHERC20UnauthorizedUseOfEncryptedAmount(euint64 amount, address user); /// @dev The caller is not authorized for the current operation. error FHERC20UnauthorizedCaller(address caller); /// @dev Reverts when a cleartext ERC-20 function is called on a confidential token. error FHERC20IncompatibleFunction(); ``` *** ## Events FHERC20 emits standard ERC20 events with indicator values, plus confidential-specific events: ```solidity theme={null} // Standard ERC20 Transfer event (value is always indicatorTick) event Transfer(address indexed from, address indexed to, uint256 value); emit Transfer(from, to, indicatorTick()); // Confidential transfer event with encrypted amount handle event ConfidentialTransfer(address indexed from, address indexed to, euint64 indexed amount); // Amount disclosure event event AmountDisclosed(euint64 indexed encryptedAmount, uint64 amount); // Operator permission event event OperatorSet(address indexed holder, address indexed operator, uint48 until); ``` The `Transfer` event doesn't reveal the actual transfer amount—only that a transfer occurred. The `value` field always contains `indicatorTick` to maintain ERC20 compatibility while preserving privacy. *** ## ERC20 Incompatible Functions For privacy reasons, several standard ERC20 functions intentionally revert: ```solidity theme={null} // These functions are not supported function transfer(address, uint256) public pure returns (bool) { revert FHERC20IncompatibleFunction(); } function allowance(address, address) external pure returns (uint256) { revert FHERC20IncompatibleFunction(); } function approve(address, uint256) external pure returns (bool) { revert FHERC20IncompatibleFunction(); } function transferFrom(address, address, uint256) public pure returns (bool) { revert FHERC20IncompatibleFunction(); } ``` Instead, use: * `confidentialTransfer()` instead of `transfer()` * `setOperator()` instead of `approve()` * `confidentialTransferFrom()` instead of `transferFrom()` *** ## Complete Example Here's a full example showing core FHERC20 features: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; import { FHERC20 } from "fhenix-confidential-contracts/contracts/FHERC20/FHERC20.sol"; contract PrivacyToken is FHERC20 { constructor() FHERC20("Privacy Token", "PRIV", 6) {} // Mint tokens (owner only in practice) function mint(address to, uint64 amount) external { _mint(to, amount); } // Burn tokens function burn(uint64 amount) external { _burn(msg.sender, amount); } } ``` Usage: ```typescript theme={null} import { Encryptable } from '@cofhe/sdk'; // Deploy const token = await PrivacyToken.deploy(); // Mint tokens await token.mint(userAddress, 1000); // Check indicator (not real balance) const indicator = await token.balanceOf(userAddress); console.log(`Indicator: ${indicator}`); // e.g., 7984 // Encrypt amount off-chain const [encryptedAmount] = await cofheClient .encryptInputs([Encryptable.uint64(100n)]) .execute(); // Confidential transfer await token.confidentialTransfer(recipientAddress, encryptedAmount); // Check new indicator const newIndicator = await token.balanceOf(userAddress); console.log(`New indicator: ${newIndicator}`); // e.g., 7983 (decremented) ``` *** ## Related Topics * Learn about [Operators](/fhe-library/confidential-contracts/fherc20/operators) for delegated transfers * Explore [Transfer Callbacks](/fhe-library/confidential-contracts/fherc20/transfer-callbacks) for safe transfers * Understand [Access Control](/fhe-library/core-concepts/access-control) for FHE permissions # FHERC20 Wrappers Source: https://cofhe-docs.fhenix.zone/fhe-library/confidential-contracts/fherc20/fherc20-wrapper Shield standard ERC20 or native tokens into confidential FHERC20 tokens ## Overview FHERC20 Wrappers enable you to convert standard ERC20 tokens or native tokens (e.g., ETH) into confidential FHERC20 tokens and vice versa. This creates a privacy layer on top of existing tokens, allowing users to transact privately while maintaining interoperability with the broader DeFi ecosystem. Transform transparent ERC20 or native token balances into encrypted FHERC20 balances for confidential transactions. Unshield confidential tokens back to standard ERC20 or native tokens at any time through a secure claim process. Each shielded token is backed by the underlying token held in the wrapper contract, with a rate-based conversion for decimal normalization. Bridge between transparent DeFi protocols and confidential trading/transfers. *** ## Wrapper Types FHERC20 provides two wrapper contracts: | Wrapper | Purpose | Shield Function | Underlying | | ------------------------ | --------------------- | ----------------------------------------------------- | ----------------------------------- | | **FHERC20ERC20Wrapper** | Shields ERC20 tokens | `shield(to, amount)` | Any standard ERC20 | | **FHERC20NativeWrapper** | Shields native tokens | `shieldNative(to)` / `shieldWrappedNative(to, value)` | Native currency (e.g., ETH) or WETH | Both wrappers share the same unshielding flow via the `FHERC20WrapperClaimHelper`. *** ## Rate and Decimal Conversion Wrappers normalize token decimals to fit within the `euint64` type. The maximum confidential decimals default to **6** (configurable via `_maxDecimals()`). ```solidity theme={null} // Conversion rate between underlying and confidential precision function rate() external view returns (uint256); ``` For an ERC20 with 18 decimals: * `rate() = 10^(18 - 6) = 10^12` * Shielding `1,000,000,000,000` (1e12) underlying tokens produces `1` confidential token * Amounts are truncated to the nearest multiple of `rate()` during shielding Some non-standard tokens such as fee-on-transfer or other deflationary-type tokens are not supported by the wrapper. *** ## ERC20 Wrapper (FHERC20ERC20Wrapper) ### How It Works User deposits standard ERC20 tokens into the wrapper contract, which mints an equivalent amount of confidential FHERC20 tokens (divided by the rate). User can now transfer the shielded tokens confidentially using all FHERC20 features while balances remain encrypted. When ready to exit, user burns confidential tokens. The burned amount is marked as publicly decryptable via `FHE.allowPublic`, and a claim is created. The user (or anyone) requests decryption of the burned amount off-chain via `decryptForTx`, receiving the plaintext and a decryption proof. The user submits the plaintext and proof on-chain via `claimUnshielded`. The contract verifies the proof and transfers the underlying ERC20 tokens (multiplied by the rate). ### Shielding Tokens ```solidity theme={null} function shield(address to, uint256 amount) external returns (euint64); ``` **Parameters:** * `to`: Address to receive the shielded (confidential) tokens * `amount`: Amount of ERC20 tokens to shield (will be truncated to nearest multiple of `rate()`) **Returns:** The encrypted amount of confidential tokens minted. ```javascript theme={null} // 1. Approve wrapper to spend your tokens await erc20Token.approve(wrapperAddress, amount); // 2. Shield tokens (receives confidential tokens) await wrapper.shield(recipientAddress, amount); // 3. Check confidential balance (encrypted) const encBalance = await wrapper.confidentialBalanceOf(recipientAddress); // 4. Transfer confidentially const [encAmount] = await cofheClient .encryptInputs([Encryptable.uint64(100n)]) .execute(); await wrapper.confidentialTransfer(anotherAddress, encAmount); ``` ### ERC1363 Direct Shielding The ERC20 wrapper also implements the ERC1363 `onTransferReceived` callback, allowing users to shield tokens in a single transaction without a separate approval step: ```javascript theme={null} // Direct transfer-and-shield in one transaction (if the ERC20 supports ERC1363) await erc20Token.transferAndCall(wrapperAddress, amount, encodedRecipient); ``` *** ## Native Wrapper (FHERC20NativeWrapper) The native wrapper shields native tokens (e.g., ETH) or WETH into confidential FHERC20 tokens. ### Shield Native Tokens ```solidity theme={null} // Shield native currency directly (e.g., ETH) function shieldNative(address to) external payable returns (euint64); // Shield WETH tokens function shieldWrappedNative(address to, uint256 value) external returns (euint64); ``` **Parameters:** * `to`: Address to receive the shielded tokens * `value`: Amount of WETH to shield (for `shieldWrappedNative`) * `msg.value`: Amount of native currency to shield (for `shieldNative`) Amounts are truncated to the nearest multiple of `rate()`. Any dust below the threshold is refunded to the caller. ```javascript theme={null} // Shield ETH directly await nativeWrapper.shieldNative(recipientAddress, { value: ethers.parseEther("1.0") }); // Or shield WETH await weth.approve(nativeWrapperAddress, amount); await nativeWrapper.shieldWrappedNative(recipientAddress, amount); ``` *** ## Unshielding Tokens Unshielding is a three-step process shared by both wrapper types: burn on-chain, decrypt off-chain, then claim with proof. ### Step 1: Unshield (Burn and Create Claim) ```solidity theme={null} function unshield(address from, address to, uint64 amount) external returns (euint64); ``` **Parameters:** * `from`: Address whose confidential tokens to burn (caller must be `from` or an operator for `from`) * `to`: Address to receive the underlying tokens after claiming * `amount`: Amount of confidential tokens to unshield This function: 1. Burns the specified amount of confidential tokens from `from` 2. Calls `FHE.allowPublic(burned)` so anyone can request decryption of the burned amount 3. Creates a claim for the recipient ```javascript theme={null} // Unshield 100 confidential tokens await wrapper.unshield(myAddress, myAddress, 100); // A claim is created, but underlying tokens aren't sent yet // The burned amount is now publicly decryptable ``` Due to the [zero-replacement behavior](/fhe-library/confidential-contracts/fherc20/core-features#the-update-function), if you attempt to unshield more than your balance, zero tokens will be burned and you'll have a claim for zero tokens. ### Step 2: Decrypt Off-Chain Retrieve the claim's `ctHash` using `getUserClaims`, then request decryption via `decryptForTx`. Since `FHE.allowPublic` was called, no permit is needed: ```typescript theme={null} // Get the user's pending claims const claims = await wrapper.getUserClaims(myAddress); const claimCtHash = claims[0].ctHash; // Request decryption off-chain (no permit needed) const decryptResult = await client .decryptForTx(claimCtHash) .withoutPermit() .execute(); // decryptResult.decryptedValue — the plaintext amount // decryptResult.signature — the decryption proof ``` ### Step 3: Claim Unshielded Tokens Submit the plaintext and proof to the contract. The contract verifies the proof via `FHE.verifyDecryptResult` and transfers the underlying tokens (multiplied by the rate): ```solidity theme={null} function claimUnshielded( bytes32 unshieldRequestId, uint64 unshieldAmountCleartext, bytes calldata decryptionProof ) external; ``` **Parameters:** * `unshieldRequestId`: The ciphertext hash identifying the claim * `unshieldAmountCleartext`: The plaintext value returned by `decryptForTx` * `decryptionProof`: The proof proving the plaintext is authentic ```typescript theme={null} // Claim your underlying tokens by submitting the proof const tx = await wrapper.claimUnshielded( decryptResult.ctHash, decryptResult.decryptedValue, decryptResult.signature ); await tx.wait(); // Check underlying token balance const balance = await erc20Token.balanceOf(myAddress); ``` ### Batch Claiming You can claim multiple unshield requests in a single transaction: ```solidity theme={null} function claimUnshieldedBatch( bytes32[] memory unshieldRequestIds, uint64[] memory unshieldAmountCleartexts, bytes[] memory decryptionProofs ) external; ``` ```typescript theme={null} // Batch claim all pending unshields const claims = await wrapper.getUserClaims(myAddress); const ids = []; const amounts = []; const proofs = []; for (const claim of claims) { const result = await client .decryptForTx(claim.ctHash) .withoutPermit() .execute(); ids.push(result.ctHash); amounts.push(result.decryptedValue); proofs.push(result.signature); } await wrapper.claimUnshieldedBatch(ids, amounts, proofs); ``` *** ## Claim Management ### Claim Structure ```solidity theme={null} struct Claim { address to; // Recipient address bytes32 ctHash; // Ciphertext hash identifying the claim uint64 requestedAmount; // Original requested unshield amount uint64 decryptedAmount; // Actual decrypted amount (set after claim) bool claimed; // Whether underlying tokens have been claimed } ``` ### Getting Claim Information ```solidity theme={null} function getClaim(bytes32 ctHash) public view returns (Claim memory); ``` ```typescript theme={null} // Get claim info const claim = await wrapper.getClaim(ctHash); console.log(`To: ${claim.to}`); console.log(`Requested: ${claim.requestedAmount}`); console.log(`Decrypted: ${claim.decryptedAmount}`); console.log(`Claimed: ${claim.claimed}`); ``` ### Getting User Claims ```solidity theme={null} function getUserClaims(address user) public view returns (Claim[] memory); ``` Returns all pending (unclaimed) claims for a user: ```typescript theme={null} // Get all pending claims const claims = await wrapper.getUserClaims(myAddress); console.log(`You have ${claims.length} pending claims`); for (const claim of claims) { console.log(`Claim ${claim.ctHash} - requested: ${claim.requestedAmount}`); } ``` *** ## Events ```solidity theme={null} // Emitted when an unshield request is created event Unshielded(address indexed to, euint64 indexed amount); // Emitted when an unshield request is claimed event ClaimedUnshielded( address indexed to, bytes32 indexed unshieldRequestId, euint64 indexed unshieldAmount, uint64 unshieldAmountCleartext ); // Emitted when native tokens are shielded (NativeWrapper only) event ShieldedNative(address indexed from, address indexed to, uint256 value); ``` *** ## Complete Examples ### ERC20 Wrapper ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { FHERC20ERC20Wrapper } from "fhenix-confidential-contracts/contracts/FHERC20/extensions/FHERC20ERC20Wrapper.sol"; // Deploy a wrapper for an existing ERC20 token contract MyTokenWrapper is FHERC20ERC20Wrapper { constructor(IERC20 underlyingToken) FHERC20ERC20Wrapper(underlyingToken, "Shielded MTK", "sMTK") {} } ``` ### Native Wrapper ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; import { FHERC20NativeWrapper } from "fhenix-confidential-contracts/contracts/FHERC20/extensions/FHERC20NativeWrapper.sol"; // Deploy a wrapper for native ETH contract ShieldedETH is FHERC20NativeWrapper { constructor(address wethAddress) FHERC20NativeWrapper(wethAddress, "Shielded ETH", "sETH") {} } ``` *** ## Security Considerations FHERC20ERC20Wrapper only works with standard ERC20 tokens. It will NOT work with: * Rebasing tokens (token balance changes automatically) * Fee-on-transfer tokens (tokens that charge fees) * Already-encrypted FHERC20 tokens Always test with the specific token before deploying to production. Claiming requires completing the off-chain decryption step first: ```typescript theme={null} // 1. Unshield burns tokens and allows public decryption await wrapper.unshield(user.address, user.address, amount); // 2. Get the claim's ctHash const claims = await wrapper.getUserClaims(user.address); const ctHash = claims[0].ctHash; // 3. Decrypt off-chain const decryptResult = await client .decryptForTx(ctHash) .withoutPermit() .execute(); // 4. Claim with proof await wrapper.claimUnshielded( decryptResult.ctHash, decryptResult.decryptedValue, decryptResult.signature ); // ✅ Success ``` Implement proper UI feedback for the decryption step. If you unshield more than your balance, you get zero: ```javascript theme={null} // Balance: 100 confidential tokens const encBalance = await wrapper.confidentialBalanceOf(user.address); // Try to unshield 200 await wrapper.unshield(user.address, user.address, 200); // Claim will give you 0 underlying tokens await waitAndClaim(ctHash); const received = 0; // Not 100, not 200, but 0 ``` Always ensure sufficient balance before unshielding. Be aware of the rate conversion when shielding and unshielding: ```javascript theme={null} // For an ERC20 with 18 decimals, rate = 1e12 const rate = await wrapper.rate(); // Shielding 1.5 tokens (1.5e18 wei) // → 1.5e18 / 1e12 = 1,500,000 confidential units (1.5 with 6 decimals) await wrapper.shield(user.address, ethers.parseUnits("1.5", 18)); // Amounts not aligned to rate are truncated // Shielding 1.5000005e18 → only 1.5e18 is shielded ``` Users can accumulate multiple pending claims. Use batch claiming for efficiency: ```typescript theme={null} // Multiple unshields await wrapper.unshield(user.address, user.address, 50); // Claim 1 await wrapper.unshield(user.address, user.address, 30); // Claim 2 await wrapper.unshield(user.address, user.address, 20); // Claim 3 // Batch claim all at once const claims = await wrapper.getUserClaims(user.address); const ids = [], amounts = [], proofs = []; for (const claim of claims) { const result = await client .decryptForTx(claim.ctHash) .withoutPermit() .execute(); ids.push(result.ctHash); amounts.push(result.decryptedValue); proofs.push(result.signature); } await wrapper.claimUnshieldedBatch(ids, amounts, proofs); ``` Provide UI to track and manage multiple claims. *** ## Use Cases Shield tokens before trading on a confidential DEX, then unshield profits. Your trading activity and positions remain private. Shield stablecoins for confidential payments, then unshield to cash out to bank accounts or fiat on-ramps. Companies can shield tokens, distribute salaries confidentially, and employees unshield to receive standard tokens. Create pools where users deposit tokens for privacy, transact confidentially, and withdraw when desired. *** ## Related Topics * Learn about [Core Features](/fhe-library/confidential-contracts/fherc20/core-features) for confidential transfers * Review [Best Practices](/fhe-library/confidential-contracts/fherc20/best-practices) for secure implementations # Operators Source: https://cofhe-docs.fhenix.zone/fhe-library/confidential-contracts/fherc20/operators Understanding FHERC20's time-based operator permission system ## Overview FHERC20 introduces a new permission model called **operators** that replaces the traditional ERC20 allowance system. Instead of approving specific amounts (which would leak information about balances), FHERC20 uses time-based operator permissions that grant full access until an expiration timestamp. No amount-specific approvals means no information leakage about how much you're willing to let others spend. Operators have automatic expiration using Unix timestamps, reducing the need for explicit revocation. Operators can move any amount of tokens (up to your balance) without needing separate approvals for each transaction. One function to grant, extend, or revoke operator permissions with intuitive timestamp-based control. *** ## Why Operators Instead of Allowances? ### The Problem with Traditional Allowances Standard ERC20 uses the `approve()` function to grant spending permissions: ```solidity theme={null} // Standard ERC20 - LEAKS INFORMATION token.approve(spender, 1000); // Everyone can see you approved 1000 tokens ``` This approach has privacy issues for confidential tokens: * ❌ Reveals how much you're willing to let someone spend * ❌ Requires updating allowances frequently * ❌ Can leak information about your balance * ❌ Doesn't work well with encrypted amounts ### The Operator Solution FHERC20 operators grant permission without revealing amounts: ```solidity theme={null} // FHERC20 - NO INFORMATION LEAKAGE token.setOperator(spender, block.timestamp + 1 days); ``` This approach is privacy-preserving: * ✅ No amount information revealed * ✅ Time-based expiration is automatic * ✅ Simple on/off permission model * ✅ Works perfectly with encrypted values *** ## Setting Operators ### Function Signature ```solidity theme={null} function setOperator(address operator, uint48 until) external; ``` **Parameters:** * `operator`: Address to grant operator permissions to * `until`: Unix timestamp when the permission expires (uint48 supports dates until year 8921556) ### Basic Usage ```solidity Grant for 1 Day theme={null} // Grant operator permission for 24 hours token.setOperator( operatorAddress, uint48(block.timestamp + 1 days) ); ``` ```solidity Grant for 1 Hour theme={null} // Grant operator permission for 1 hour token.setOperator( operatorAddress, uint48(block.timestamp + 1 hours) ); ``` ```solidity Grant Indefinitely theme={null} // Grant operator permission far into the future token.setOperator( operatorAddress, type(uint48).max // Expires in year 8921556 ); ``` ```solidity Revoke Immediately theme={null} // Revoke operator permission token.setOperator( operatorAddress, uint48(block.timestamp) // Expires now ); ``` Use `uint48(block.timestamp + duration)` to calculate expiration times. The `uint48` type is large enough for practical use while being gas-efficient. *** ## Checking Operator Status ### Function Signature ```solidity theme={null} function isOperator(address holder, address spender) external view returns (bool); ``` **Parameters:** * `holder`: Address of the token holder * `spender`: Address to check operator status for **Returns:** * `true` if `spender` is currently an authorized operator for `holder` * `false` if not authorized or permission has expired ### Usage Examples ```solidity theme={null} // Check if address is an operator bool canOperate = token.isOperator(holderAddress, spenderAddress); if (canOperate) { // Spender can transfer holder's tokens token.confidentialTransferFrom(holder, recipient, encryptedAmount); } ``` ```javascript theme={null} // Off-chain checking const isAuthorized = await token.isOperator(holderAddress, operatorAddress); if (isAuthorized) { console.log("Operator is authorized"); } else { console.log("Operator permission expired or never granted"); } ``` *** ## Using Operator Permissions Once granted operator status, an address can use `confidentialTransferFrom()` to move tokens: ```solidity theme={null} function confidentialTransferFrom( address from, address to, InEuint64 memory inValue ) external returns (euint64 transferred); ``` ### Complete Example ```solidity theme={null} // 1. Token holder grants operator permission await token.connect(holder).setOperator( operatorAddress, Math.floor(Date.now() / 1000) + 86400 // 1 day from now ); // 2. Operator can now transfer on behalf of holder const [encryptedAmount] = await cofheClient .encryptInputs([Encryptable.uint64(100n)]) .execute(); await token.connect(operator).confidentialTransferFrom( holderAddress, recipientAddress, encryptedAmount ); // 3. After expiration, operator can no longer transfer // (automatically revoked when timestamp passes) ``` *** ## Internal Implementation ### Storage Operators are stored in a mapping with their expiration times: ```solidity theme={null} mapping(address holder => mapping(address spender => uint48 until)) private _operators; ``` ### Setting an Operator ```solidity theme={null} function setOperator(address operator, uint48 until) external { address holder = msg.sender; // Update or revoke operator permission _operators[holder][operator] = until; // Emit event (implementation specific) emit OperatorSet(holder, operator, until); } ``` ### Checking Operator Status ```solidity theme={null} function isOperator(address holder, address spender) external view returns (bool) { // Check if current time is before expiration return _operators[holder][spender] >= block.timestamp; } ``` ### Transfer From Check Before allowing a `confidentialTransferFrom`, the contract verifies operator status: ```solidity theme={null} function confidentialTransferFrom( address from, address to, euint64 value ) external returns (euint64 transferred) { // Verify operator permission if (!isOperator(from, msg.sender)) { revert FHERC20UnauthorizedSpender(from, msg.sender); } // Perform the transfer return _transfer(from, to, value); } ``` *** ## Operator Patterns ### Pattern 1: Short-Lived Permissions Grant operator permissions for specific transactions: ```solidity theme={null} // Grant permission for a specific operation function executeSwap(address tokenIn, uint64 amountIn) external { // Grant DEX operator permission for 5 minutes tokenIn.setOperator(dexAddress, uint48(block.timestamp + 5 minutes)); // Execute swap dex.swap(tokenIn, tokenOut, amountIn); // Permission automatically expires after 5 minutes } ``` *** ## Operator vs Allowance Comparison | Feature | ERC20 Allowance | FHERC20 Operator | | ------------------ | ------------------------------ | ---------------------------- | | **Privacy** | ❌ Reveals approved amount | ✅ No amount revealed | | **Expiration** | ⚠️ Manual revocation required | ✅ Automatic time-based | | **Flexibility** | ✅ Can approve specific amounts | ⚠️ All-or-nothing access | | **Gas Efficiency** | ⚠️ Multiple approvals costly | ✅ Single approval sufficient | | **Complexity** | ✅ Simple amount-based | ✅ Simple time-based | | **Use with FHE** | ❌ Doesn't work with encryption | ✅ Designed for FHE | *** ## Security Considerations An operator can transfer **all** of a holder's tokens, not just a specific amount. Only grant operator permissions to trusted addresses. ```solidity theme={null} // Operator can transfer entire balance euint64 balance = token.confidentialBalanceOf(holder); token.confidentialTransferFrom(holder, attacker, balance); ``` Best practices: * Use short expiration times when possible * Only authorize trusted contracts or addresses * Monitor operator grants in your UI * Consider implementing additional checks in contracts Operator permissions automatically expire based on blockchain timestamp: ```solidity theme={null} // Permission expires at specific timestamp uint48 expiresAt = uint48(block.timestamp + 1 hours); token.setOperator(operator, expiresAt); // After expiration, operator cannot act // No need for explicit revocation ``` **Advantages:** * Automatic cleanup * No gas cost for revocation * Predictable expiration **Considerations:** * Block timestamps can vary slightly * Account for clock skew in time calculations * Use buffer time for critical operations Operator changes are atomic and immediate: ```solidity theme={null} // This transaction either succeeds completely or reverts token.setOperator(newOperator, expirationTime); ``` Unlike ERC20's approve/transferFrom race condition, operator changes are safe from front-running because: * No amount is specified * Permission is binary (yes/no) * Time-based expiration is deterministic A holder can have multiple operators simultaneously: ```solidity theme={null} // Grant multiple operators token.setOperator(operatorA, uint48(block.timestamp + 1 days)); token.setOperator(operatorB, uint48(block.timestamp + 7 days)); token.setOperator(operatorC, uint48(block.timestamp + 30 days)); // All can operate independently ``` **Consider:** * Each operator has full access * Permissions are independent * Track all active operators * Implement operator limits if needed *** ## Related Topics * Explore [Transfer Callbacks](/fhe-library/confidential-contracts/fherc20/transfer-callbacks) for safe operator transfers * Review [Best Practices](/fhe-library/confidential-contracts/fherc20/best-practices) for secure operator management # FHERC20 Overview Source: https://cofhe-docs.fhenix.zone/fhe-library/confidential-contracts/fherc20/overview Introduction to the FHERC20 confidential token standard (ERC-7984) ## What is FHERC20? FHERC20 is a Fully Homomorphic Encryption (FHE) enabled token standard that provides **complete confidentiality** for token balances while maintaining compatibility with existing ERC20 infrastructure. Built on the Fhenix CoFHE protocol and implementing the [ERC-7984](https://eips.ethereum.org/EIPS/eip-7984) standard, FHERC20 allows users to transfer and manage tokens without revealing their balances or transaction amounts to anyone—not even other participants in the same smart contract. All balances and transfer amounts are encrypted using FHE, ensuring complete financial privacy on a public blockchain. Maintains compatibility with existing wallets and block explorers through an indicator system, while confidential operations use specialized functions. Perform computations on encrypted data without decryption, using homomorphic properties and FHESafeMath to maintain security throughout all operations. Modern operator system with time-based expiration for granular access control. *** ## Key Features ### 1. Encrypted Balances Unlike standard ERC20 tokens where balances are visible to everyone, FHERC20 stores all balances as encrypted values using `euint64` types: ```solidity theme={null} // In FHERC20.sol mapping(address account => euint64) private _confidentialBalances; ``` These encrypted balances: * Cannot be read by anyone (including the contract) * Can be operated on using FHE operations * Maintain their encrypted state throughout all computations * Are only revealed when explicitly disclosed with a valid proof ### 2. Confidential Transfers All token movements use encrypted amounts: ```solidity theme={null} // Transfer with encrypted input function confidentialTransfer(address to, InEuint64 memory encryptedAmount) external returns (euint64 transferred); // Transfer with already-encrypted value function confidentialTransfer(address to, euint64 amount) external returns (euint64 transferred); ``` FHERC20 provides two overloads for most functions: one accepting `InEuint64` (encrypted input from users) and one accepting `euint64` (already-encrypted values for contract-to-contract calls). ### 3. Operator System Instead of traditional ERC20 allowances (which leak information about approved amounts), FHERC20 uses a time-based operator system: ```solidity theme={null} // Grant operator permission until a specific timestamp function setOperator(address operator, uint48 until) external; // Check if an address is an authorized operator function isOperator(address holder, address spender) external view returns (bool); ``` Operators have **full access** to move tokens on behalf of the holder until the expiration time, without revealing specific amounts. ### 4. Transfer Callbacks FHERC20 supports safe transfers with callbacks: ```solidity theme={null} function confidentialTransferAndCall( address to, InEuint64 memory encryptedAmount, bytes calldata data ) external returns (euint64 transferred); ``` Recipients must implement the `IERC7984Receiver` interface to accept these transfers, enabling atomic token transfers with contract interactions. ### 5. Amount Disclosure FHERC20 supports optional disclosure of encrypted amounts for transparency when needed: ```solidity theme={null} function requestDiscloseEncryptedAmount(euint64 amount) external; function discloseEncryptedAmount(euint64 amount, uint64 cleartext, bytes memory proof) external; ``` This emits an `AmountDisclosed` event, allowing accounts with access to an encrypted amount to voluntarily reveal it on-chain. *** ## Architecture Users encrypt their transaction data (amounts, recipients) off-chain using the Client SDK (`@cofhe/sdk`) before submitting to the blockchain. The FHERC20 contract performs all operations (additions, subtractions, comparisons) on encrypted data without ever decrypting it. The contract manages permissions for who can access encrypted balances, using FHE access control mechanisms. For compatibility with standard wallets, FHERC20 maintains non-confidential "indicators" that show activity without revealing amounts. *** ## The Indicator System To maintain compatibility with existing ERC20 infrastructure (wallets, block explorers), FHERC20 implements an **indicator system**: Indicators are small, non-confidential numbers that represent account activity without revealing actual balances: * Range from `0.0000` to `0.9999` (stored as `0-9999`) * Start at `0` for accounts that have never interacted * Initialize at `0.7984` upon first interaction (referencing the ERC-7984 standard) * Increment by `0.0001` for each received transaction * Decrement by `0.0001` for each sent transaction **Example:** ```solidity theme={null} // Alice's indicator: 0.7990 // Bob's indicator: 0.7980 // These numbers provide visual feedback in wallets but reveal no actual balance info ``` Standard ERC20 functions like `balanceOf()` must return a `uint256`. For confidential tokens, we can't return the real balance, so we return the indicator instead. The `balanceOfIsIndicator()` function returns `true`, signalling to wallets and block explorers that `balanceOf` returns an indicator, not a real balance. This allows: * ✅ Wallets to display "activity" for the token * ✅ Block explorers to show transactions occurred * ✅ Basic compatibility with existing infrastructure * ❌ But doesn't reveal actual token amounts Users can opt out by calling `resetIndicatedBalance()` to set their indicator back to zero. The `indicatorTick` is the base unit for indicator increments: ```solidity theme={null} uint256 indicatorTick = 10^(decimals - 4); // For a token with 6 decimals (recommended): // indicatorTick = 10^2 = 100 ``` This value is returned in `Transfer` events to maintain ERC20 compatibility while hiding real amounts. *** ## Comparison with Standard ERC20 | Feature | Standard ERC20 | FHERC20 | | ---------------------- | ------------------------- | ------------------------------------------- | | **Balance Visibility** | Public, anyone can see | Encrypted, private | | **Transfer Amounts** | Public, visible in events | Encrypted | | **Allowances** | Specific amounts approved | Time-based operator system | | **Transfer Function** | `transfer(to, amount)` | `confidentialTransfer(to, encryptedAmount)` | | **Balance Query** | Returns actual balance | Returns indicator | | **Compatibility** | Native ERC20 | Indicator system for wallets | | **Privacy** | None | Complete | | **Standard** | ERC-20 | ERC-7984 + ERC-20 compatibility | *** ## Contract Variants The FHERC20 ecosystem includes several specialized contracts: **Base Implementation** Core confidential token with encrypted balances, confidential transfers, and operator system. **ERC20 Wrapper** Shields standard ERC20 tokens into confidential FHERC20 tokens with rate-based decimal conversion and handles unshielding with a claim system. **Native Token Wrapper** Shields native tokens (e.g., ETH) or WETH into confidential FHERC20 tokens. **Claim Management** Abstract contract providing claim lifecycle management for unshield operations and decryption verification. *** ## Quick Start Example Here's a minimal example showing FHERC20 in action: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; import { FHERC20 } from "fhenix-confidential-contracts/contracts/FHERC20/FHERC20.sol"; contract MyConfidentialToken is FHERC20 { constructor() FHERC20("My Confidential Token", "MCT", 6) {} // Mint confidential tokens function mint(address to, uint64 amount) external { _mint(to, amount); } } ``` The recommended number of decimals for FHERC20 tokens is **6**. This ensures compatibility with the wrapper contracts and avoids overflow issues with the `euint64` type. Using the token: ```solidity theme={null} // User encrypts amount off-chain, then submits InEuint64 memory encryptedAmount = cofhe.encrypt(100); token.confidentialTransfer(recipient, encryptedAmount); // Check indicator (not real balance!) uint256 indicator = token.balanceOf(user); // Returns indicator value // Check real encrypted balance (only accessible to user) euint64 encBalance = token.confidentialBalanceOf(user); ``` *** ## Next Steps Learn about encrypted balances, transfers, and the indicator system in detail. Understand the operator permission system and how it replaces traditional allowances. Implement safe transfers with callbacks using the IERC7984Receiver interface. Security considerations, gas optimization, and recommended patterns. *** ## Related Topics * Learn about FHE operations in [Encrypted Operations](/fhe-library/core-concepts/encrypted-operations) * Understand access control in [Access Control](/fhe-library/core-concepts/access-control) * Explore encryption with the [Client SDK](/client-sdk/introduction/overview) # Transfer Callbacks Source: https://cofhe-docs.fhenix.zone/fhe-library/confidential-contracts/fherc20/transfer-callbacks Safe transfers with callbacks using IERC7984Receiver interface ## Overview FHERC20 supports **safe transfers with callbacks**. When you use the `...AndCall` functions, the recipient contract is notified of the incoming transfer and can accept or reject it. This enables atomic operations where token transfers and contract logic execute together. Transfer tokens and execute contract logic in a single transaction, ensuring both succeed or both fail. Recipients must explicitly accept transfers by implementing IERC7984Receiver, preventing tokens from being locked in incompatible contracts. Recipients can execute arbitrary logic upon receiving tokens, enabling complex DeFi interactions. Pass arbitrary data along with transfers to provide context or instructions to the recipient. *** ## Transfer And Call Functions FHERC20 provides four functions that support callbacks: ### Confidential Transfer And Call Transfer tokens from caller to recipient with callback: ```solidity With Encrypted Input theme={null} function confidentialTransferAndCall( address to, InEuint64 memory inValue, bytes calldata data ) external returns (euint64 transferred); ``` ```solidity With Already-Encrypted Value theme={null} function confidentialTransferAndCall( address to, euint64 value, bytes calldata data ) external returns (euint64 transferred); ``` ### Confidential Transfer From And Call Transfer tokens from a third party to recipient with callback (requires operator permission): ```solidity With Encrypted Input theme={null} function confidentialTransferFromAndCall( address from, address to, InEuint64 memory inValue, bytes calldata data ) external returns (euint64 transferred); ``` ```solidity With Already-Encrypted Value theme={null} function confidentialTransferFromAndCall( address from, address to, euint64 value, bytes calldata data ) external returns (euint64 transferred); ``` **Parameters:** * `to`: Recipient address (must implement IERC7984Receiver if it's a contract) * `from`: Source address (only for `...From...` variants) * `inValue`/`value`: Encrypted amount to transfer * `data`: Arbitrary data to pass to recipient **Returns:** * `transferred`: The actual encrypted amount transferred (may be zero if insufficient balance) *** ## The IERC7984Receiver Interface Any contract that wants to receive tokens via `...AndCall` functions **must** implement the `IERC7984Receiver` interface: ```solidity theme={null} interface IERC7984Receiver { function onConfidentialTransferReceived( address operator, address from, euint64 amount, bytes calldata data ) external returns (ebool); } ``` ### Function Parameters * `operator`: The address that initiated the transfer (msg.sender of the ...AndCall function) * `from`: The address tokens are being transferred from * `amount`: The encrypted amount being transferred (euint64) * `data`: Arbitrary data passed along with the transfer ### Return Value The function must return an `ebool` (encrypted boolean): * `FHE.asEbool(true)`: Accept the transfer * `FHE.asEbool(false)`: Reject the transfer (tokens will be returned to sender) If `onConfidentialTransferReceived` returns encrypted `false`, the entire transfer is **reversed**. The tokens return to the sender as if the transfer never happened. *** ## How It Works User calls `confidentialTransferAndCall` or `confidentialTransferFromAndCall` with recipient address, encrypted amount, and optional data. FHERC20 performs the normal confidential transfer, updating balances and access controls. If the recipient is a contract (code size > 0), FHERC20 checks if it implements IERC7984Receiver. If implemented, FHERC20 calls `onConfidentialTransferReceived` on the recipient contract. The recipient returns encrypted true (accept) or false (reject). FHERC20 evaluates this response. If accepted, the transfer is complete. If rejected, tokens are returned to sender. *** ## Implementing IERC7984Receiver ### Basic Implementation Here's a minimal receiver that accepts all transfers: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; import "fhenix-confidential-contracts/contracts/interfaces/IERC7984Receiver.sol"; import "@fhenixprotocol/cofhe-contracts/FHE.sol"; contract BasicReceiver is IERC7984Receiver { event TokensReceived( address indexed operator, address indexed from, bytes32 amount, bytes data ); function onConfidentialTransferReceived( address operator, address from, euint64 amount, bytes calldata data ) external override returns (ebool) { // Log the receipt emit TokensReceived(operator, from, euint64.unwrap(amount), data); // Accept all transfers return FHE.asEbool(true); } } ``` ### Conditional Acceptance Accept transfers only under certain conditions: ```solidity theme={null} contract ConditionalReceiver is IERC7984Receiver { address public immutable trustedToken; mapping(address => bool) public approvedSenders; constructor(address _trustedToken) { trustedToken = _trustedToken; } function onConfidentialTransferReceived( address operator, address from, euint64 amount, bytes calldata data ) external override returns (ebool) { // Only accept from trusted token if (msg.sender != trustedToken) { return FHE.asEbool(false); } // Only accept from approved senders if (!approvedSenders[from]) { return FHE.asEbool(false); } // Accept the transfer return FHE.asEbool(true); } function approveSender(address sender, bool approved) external { approvedSenders[sender] = approved; } } ``` ### With Custom Logic Execute logic upon receiving tokens: ```solidity theme={null} contract StakingPool is IERC7984Receiver { IFHERC20 public immutable stakingToken; mapping(address => euint64) public stakedBalances; constructor(address _stakingToken) { stakingToken = IFHERC20(_stakingToken); } function onConfidentialTransferReceived( address operator, address from, euint64 amount, bytes calldata data ) external override returns (ebool) { // Only accept our staking token if (msg.sender != address(stakingToken)) { return FHE.asEbool(false); } // Update staked balance stakedBalances[from] = stakedBalances[from] + amount; // Grant access to the balance FHE.allowThis(stakedBalances[from]); FHE.allow(stakedBalances[from], from); // Process any additional data if (data.length > 0) { _processStakingData(from, data); } // Accept the transfer return FHE.asEbool(true); } function _processStakingData(address staker, bytes calldata data) internal { // Custom logic based on data parameter // e.g., parse lock duration, referral codes, etc. } } ``` *** ## Using Transfer Callbacks ### Basic Usage ```typescript theme={null} // Off-chain: Prepare encrypted amount const [encryptedAmount] = await cofheClient .encryptInputs([Encryptable.uint64(1000n)]) .execute(); // Call with empty data await token.confidentialTransferAndCall( receiverAddress, encryptedAmount, "0x" // Empty data ); ``` ### Passing Data You can encode arbitrary data to pass to the recipient: ```javascript theme={null} // Encode some parameters const lockDuration = 30 * 24 * 60 * 60; // 30 days const referralCode = ethers.utils.formatBytes32String("REF123"); const data = ethers.utils.defaultAbiCoder.encode( ["uint256", "bytes32"], [lockDuration, referralCode] ); // Transfer with data const [encryptedAmount] = await cofheClient .encryptInputs([Encryptable.uint64(1000n)]) .execute(); await token.confidentialTransferAndCall( stakingPoolAddress, encryptedAmount, data ); ``` The recipient can decode this data: ```solidity theme={null} function onConfidentialTransferReceived( address operator, address from, euint64 amount, bytes calldata data ) external override returns (ebool) { // Decode the data (uint256 lockDuration, bytes32 referralCode) = abi.decode( data, (uint256, bytes32) ); // Use the decoded parameters _processStake(from, amount, lockDuration, referralCode); return FHE.asEbool(true); } ``` *** ## Reversion Behavior ### When Does It Revert? The transfer will revert if: ```solidity theme={null} // 1. Recipient is a contract but doesn't implement IERC7984Receiver contract NoReceiver { // Missing onConfidentialTransferReceived } // 2. Recipient's callback reverts function onConfidentialTransferReceived(...) external override returns (ebool) { revert("Not accepting transfers"); } // 3. Recipient's callback returns encrypted false function onConfidentialTransferReceived(...) external override returns (ebool) { return FHE.asEbool(false); // Transfer will be reversed } ``` ### When Does It Succeed? The transfer succeeds if: ```solidity theme={null} // 1. Recipient is an EOA (not a contract) await token.confidentialTransferAndCall(eoaAddress, amount, data); // 2. Recipient is a contract implementing IERC7984Receiver that returns true function onConfidentialTransferReceived(...) external override returns (ebool) { return FHE.asEbool(true); } ``` *** ## Security Considerations The callback happens **after** the balance transfer but **before** the transaction completes. Implement reentrancy guards if your receiver makes external calls. ```solidity theme={null} import "@openzeppelin/contracts/security/ReentrancyGuard.sol"; contract SafeReceiver is IERC7984Receiver, ReentrancyGuard { function onConfidentialTransferReceived( address operator, address from, euint64 amount, bytes calldata data ) external override nonReentrant returns (ebool) { // Protected against reentrancy return FHE.asEbool(true); } } ``` Callback execution is subject to gas limits. Keep logic simple: ```solidity theme={null} function onConfidentialTransferReceived( address operator, address from, euint64 amount, bytes calldata data ) external override returns (ebool) { // ✅ Simple logic is fine stakedBalances[from] += amount; // ❌ Avoid complex operations // for (uint i = 0; i < 1000; i++) { ... } return FHE.asEbool(true); } ``` Your receiver will be called by anyone who transfers tokens to you. Validate the sender: ```solidity theme={null} function onConfidentialTransferReceived( address operator, address from, euint64 amount, bytes calldata data ) external override returns (ebool) { // Validate token if (msg.sender != address(trustedToken)) { return FHE.asEbool(false); } // Validate sender if needed if (!isAllowedSender(from)) { return FHE.asEbool(false); } return FHE.asEbool(true); } ``` Always validate the `data` parameter before using it: ```solidity theme={null} function onConfidentialTransferReceived( address operator, address from, euint64 amount, bytes calldata data ) external override returns (ebool) { // Validate data length if (data.length > 0) { // Safely decode with try-catch try this.decodeData(data) returns (uint256 value) { // Use decoded value _processValue(value); } catch { // Invalid data, reject transfer return FHE.asEbool(false); } } return FHE.asEbool(true); } function decodeData(bytes calldata data) external pure returns (uint256) { return abi.decode(data, (uint256)); } ``` *** ## Best Practices Minimize gas usage in your `onConfidentialTransferReceived` implementation. Complex logic should be moved to separate functions. Always validate the token sender (`msg.sender`), the transfer initiator (`operator`), and the source (`from`) before accepting transfers. Return `FHE.asEbool(false)` to reject transfers rather than reverting, when possible. This provides better UX. Test both acceptance and rejection scenarios, including edge cases like zero transfers and malformed data. *** ## Related Topics * Learn about [Operators](/fhe-library/confidential-contracts/fherc20/operators) for delegated transfers * Explore [Core Features](/fhe-library/confidential-contracts/fherc20/core-features) for basic transfers * Review [Best Practices](/fhe-library/confidential-contracts/fherc20/best-practices) for secure implementations # Access Control Source: https://cofhe-docs.fhenix.zone/fhe-library/core-concepts/access-control Understanding and managing permissions for encrypted data in confidential smart contracts ## Motivation Consider the following scenario: Your contract receives an encrypted input that should remain confidential. ```solidity theme={null} // Contract A function submitSecretBid(InEuint32 bid) public { euint32 handle = FHE.asEuint32(bid); // Perform some operations on the encrypted input } ``` If there were no access control mechanisms, someone could observe the handle value used above and reuse it by initializing a local variable with the same value. ```solidity theme={null} // Contract B function attackBid(euint32 seenHandle, uint32 plaintext, bytes calldata signature) public { FHE.publishDecryptResult(seenHandle, plaintext, signature); // try to expose the value } ``` To prevent misuse, all FHE operations verify that the caller has explicit permission to use the ciphertext handle. *** ## How Access Control Works In practice, the code above will revert with an `ACLNotAllowed` error because the calling contract doesn't have permission for that ciphertext handle. Any FHE operation will fail if the caller lacks permission for all input handles. ### Example: Unauthorized Operations Fail ```solidity theme={null} FHE.add(notAllowedCt, allowedCt); // -> will revert with ACLNotAllowed ``` By default, newly created ciphertext handles are accessible to the contract that created them, but **only for the duration of the transaction**. Any additional access must be explicitly granted. *** ## Granting Access CoFHE provides six methods to grant access to ciphertext handles: **`FHE.allowThis(CIPHERTEXT_HANDLE)`** Allows the current contract access to the handle. Use this when you want the contract itself to retain access to a ciphertext beyond the current transaction. ```solidity theme={null} euint32 secretValue = FHE.asEuint32(input); FHE.allowThis(secretValue); // Contract can access this later ``` **`FHE.allowSender(CIPHERTEXT_HANDLE)`** Allows the transaction sender (`msg.sender`) access to the handle. Use this when you want to grant the caller of the function access to the ciphertext. ```solidity theme={null} euint32 secretValue = FHE.asEuint32(input); FHE.allowSender(secretValue); // Grant access to msg.sender ``` **`FHE.allow(CIPHERTEXT_HANDLE, ADDRESS)`** Allows a specific address persistent access to the handle. Use this when you want to grant permanent access to another contract or user. ```solidity theme={null} euint32 secretValue = FHE.asEuint32(input); FHE.allow(secretValue, recipientAddress); // Grant access to specific address ``` **`FHE.allowTransient(CIPHERTEXT_HANDLE, ADDRESS)`** Allows a specific address temporary access to the handle for the duration of the transaction only. Use this for cross-contract calls within the same transaction. ```solidity theme={null} euint32 secretValue = FHE.asEuint32(input); FHE.allowTransient(secretValue, otherContract); // Temporary access for this tx ``` **`FHE.allowPublic(CIPHERTEXT_HANDLE)`** Marks a ciphertext handle as eligible for public decryption. Anyone can then request decryption of this value off-chain via `decryptForTx` and publish or verify the result on-chain. Use this when a value is intended to become public — for example, the amount being unshielded in an FHERC20 unwrap flow. ```solidity theme={null} euint32 burnedAmount = FHE.asEuint32(input); FHE.allowPublic(burnedAmount); // Anyone can decrypt and publish this value ``` `allowPublic` does not reveal the value immediately. It only grants permission for anyone to request decryption. The value is revealed only when someone submits the plaintext and signature on-chain via `FHE.publishDecryptResult` or `FHE.verifyDecryptResult`. *** ## Decryption and Access Control Decryption is a multi-step process: a client requests the plaintext and a threshold signature off-chain via `decryptForTx`, then publishes or verifies the result on-chain. Access control governs who can request decryption: * If the ciphertext was marked with `FHE.allowPublic()`, anyone can request decryption without a permit (`.withoutPermit()`). * Otherwise, only addresses with explicit permission on the handle can request decryption, and must provide a valid permit (`.withPermit()`). If the requester does not have permission on the ciphertext handle, the decryption request will be denied by the access control system. Grant appropriate permissions before attempting to decrypt — use `FHE.allowPublic()` for values intended to become public, or `FHE.allow()` / `FHE.allowSender()` for restricted access. *** ## Behind the Scenes Every blockchain integrating CoFHE includes a deployed `ACL.sol` contract. This contract manages ownership records for each ciphertext, ensuring that only authorized owners can perform operations on their encrypted data. ### ACL Storage Structure The ACL contract contains the following mapping which tracks the ownership of each ciphertext handle: ```solidity theme={null} mapping(uint128 handle => mapping(address account => bool isAllowed)) persistedAllowedPairs; ``` This two-level mapping allows efficient lookup of whether a specific address has permission to access a specific ciphertext handle. *** ## Best Practices Only grant access to addresses that genuinely need it. Avoid using `allowPublic()` unless the data is truly meant to be public. When calling other contracts within a transaction, use `allowTransient()` instead of permanent access to limit exposure. Keep track of which addresses have access to which ciphertexts, especially in complex multi-contract systems. Consider the lifecycle of your encrypted data and whether permissions should be revoked after certain operations. *** ## Practical Examples For detailed examples on how to explicitly manage ciphertext allowances in contracts, see the [ACL Usage Examples](/tutorials/acl-usage-examples) guide. ### Quick Example: Token Transfer ```solidity theme={null} function transfer(address to, InEuint32 memory inAmount) public { euint32 amount = FHE.asEuint32(inAmount); euint32 fromBalance = _balances[msg.sender]; euint32 toBalance = _balances[to]; euint32 newFromBalance = FHE.sub(fromBalance, amount); euint32 newToBalance = FHE.add(toBalance, amount); // Grant contract access to store balances FHE.allowThis(newFromBalance); FHE.allowThis(newToBalance); // Optionally grant users access to their own balances FHE.allow(newFromBalance, msg.sender); FHE.allow(newToBalance, to); _balances[msg.sender] = newFromBalance; _balances[to] = newToBalance; } ``` # Common Errors Source: https://cofhe-docs.fhenix.zone/fhe-library/core-concepts/common-errors Critical limitations and important considerations when working with CoFHE, including common issues and Solidity error references ## Overview This page documents critical limitations and important considerations when working with CoFHE. Understanding these common issues and error messages will help you troubleshoot problems and build more robust FHE-enabled smart contracts. Always verify you're using compatible component versions. Many errors can be resolved by ensuring you're using the latest versions of all CoFHE components. Encountering cryptic `execution reverted: 0x...` errors? Use the **@fhenixprotocol/cofhe-errors** package to decode them instantly: ```bash theme={null} npx cofhe-errors 0x118cdaa7 ``` See the [CoFHE Errors Package](/fhe-library/reference/cofhe-errors) documentation for full usage instructions and the [Error Reference](/fhe-library/reference/cofhe-errors-reference) for a complete list of all 53 errors. ## Common Issues ### Missing Revert Data If you encounter a `Missing revert data` error, verify that you're using the latest `cofhe-contracts` version. Verify the version of `cofhe-contracts` in your project: ```bash npm theme={null} npm list @fhenixprotocol/cofhe-contracts ``` ```bash yarn theme={null} yarn list --pattern "@fhenixprotocol/cofhe-contracts" ``` ```bash pnpm theme={null} pnpm list @fhenixprotocol/cofhe-contracts ``` Check the [Compatibility](/get-started/introduction/compatibility) page to ensure you're using a supported version. If your version is outdated, update to the latest compatible version: ```bash npm theme={null} npm install @fhenixprotocol/cofhe-contracts@latest ``` ```bash yarn theme={null} yarn add @fhenixprotocol/cofhe-contracts@latest ``` ```bash pnpm theme={null} pnpm add @fhenixprotocol/cofhe-contracts@latest ``` This section will be expanded over time as new issues arise. If you encounter an issue not documented here, please report it to the Fhenix team. ## Possible Errors from Solidity The following table lists common Solidity errors you may encounter when working with CoFHE contracts. Each error includes a description to help you understand what went wrong and how to fix it. | Error | Description | | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | **InvalidInputsAmount** | Operation requires a specific number of inputs. Occurs when an operation receives the wrong number of arguments | | **InvalidOperationInputs** | Operation inputs must be valid for the operation. Thrown when inputs violate operation requirements | | **TooManyInputs** | Operations have maximum input limits. Error when input count exceeds the operation's maximum | | **InvalidBytesLength** | Byte arrays must match expected length. Occurs when byte array length doesn't match the required size | | **InvalidTypeOrSecurityZone** | Operations must use compatible types and security zones. Occurs when an operation violates type or security zone constraints | | **InvalidInputType** | Input must match the expected type. Error when input type doesn't match function requirements | | **InvalidInputForFunction** | Function inputs must match defined parameters. Thrown when a function receives an incompatible input type | | **InvalidSecurityZone** | Operations must stay within defined security zones. Error when an operation violates security zone constraints | | **InvalidSignature** | Cryptographic signatures must be valid. Occurs with signature verification failures | | **InvalidSigner** | Signer must match the expected authorized address. Error when transaction signer doesn't match the required address | | **InvalidAddress** | Address must be valid and non-zero. Error when an invalid address is provided | | **OnlyOwnerAllowed** | Function restricted to contract owner. Error includes the address of the unauthorized caller | | **OnlyAggregatorAllowed** | Function restricted to authorized aggregator. Error includes the address of the unauthorized caller | | **AlreadyDelegated** | Delegatee contract is already a delegatee for sender and delegator addresses. Error when attempting duplicate delegation | | **SenderCannotBeDelegateeAddress** | Sender cannot be the delegatee address. Error when sender tries to delegate to themselves | | **SenderNotAllowed** | Sender address not authorized for allow operations. Error includes the address of the unauthorized sender | | **DirectAllowForbidden** | Direct handle allowance not permitted. Must use Task Manager. Error includes the address attempting direct allow | ### Understanding Error Types **Input Validation Errors:** * `InvalidInputsAmount`, `InvalidOperationInputs`, `TooManyInputs`, `InvalidBytesLength` * These errors indicate problems with how data is passed to FHE operations * **Solution**: Verify that you're passing the correct number and type of arguments **Type and Security Zone Errors:** * `InvalidTypeOrSecurityZone`, `InvalidInputType`, `InvalidInputForFunction`, `InvalidSecurityZone` * These errors occur when encrypted types don't match expected types or security zones * **Solution**: Ensure encrypted values use compatible types (e.g., `euint32` matches `euint32`) and are in the correct security zone **Authentication Errors:** * `InvalidSignature`, `InvalidSigner`, `InvalidAddress` * These errors relate to cryptographic verification and address validation * **Solution**: Verify signatures are valid and addresses are properly formatted **Authorization Errors:** * `OnlyOwnerAllowed`, `OnlyAggregatorAllowed`, `SenderNotAllowed`, `DirectAllowForbidden` * These errors indicate permission or access control violations * **Solution**: Ensure the correct account is calling the function and that proper permissions are set **Delegation Errors:** * `AlreadyDelegated`, `SenderCannotBeDelegateeAddress` * These errors occur when delegation operations are invalid * **Solution**: Check that delegation hasn't already occurred and that sender and delegatee addresses are different ## Troubleshooting Tips When encountering errors: 1. **Check error messages carefully**: The error name and description provide clues about what went wrong 2. **Verify input types**: Ensure encrypted values match expected types 3. **Check permissions**: Verify that `FHE.allowThis()` or `FHE.allowSender()` have been called where necessary 4. **Review component versions**: Ensure all CoFHE components are up to date 5. **Test in mock environment**: Use the mock environment to debug issues without network delays Many errors can be prevented by following [best practices](/fhe-library/introduction/best-practices) and ensuring proper access control management. ## Next Steps * Use the [CoFHE Errors Package](/fhe-library/reference/cofhe-errors) to decode error selectors * View the complete [Error Reference](/fhe-library/reference/cofhe-errors-reference) with all 53 errors * Review the [Compatibility](/get-started/introduction/compatibility) page for version requirements * Learn about [access control](/fhe-library/core-concepts/access-control) to prevent authorization errors * Check [best practices](/fhe-library/introduction/best-practices) for secure FHE development # Conditions (if .. else) Source: https://cofhe-docs.fhenix.zone/fhe-library/core-concepts/conditions Understanding why if..else isn't possible with FHE and exploring the alternatives ## Overview Writing smart contracts with Fully Homomorphic Encryption (FHE) changes how you handle conditionals. Since all data is encrypted, you can't use traditional `if...else` statementsthere's no way to view the values being compared. Moreover, conditionals in FHE must evaluate both branches simultaneously. This is similar to constant-time cryptographic programming, where branching can leak information through timing attacksfor example, if one path takes longer to execute, an observer could infer which condition was true. Using traditional `if...else` on encrypted data might result in **unexpected behavior** and leak information about your encrypted values. *** ## The Select Function To handle encrypted conditionals, Fhenix uses a concept called a **selector**a function that takes an encrypted condition and two possible values, returning one based on the encrypted result. In practice, this is done with the `select` function. It behaves like a ternary operator (`condition ? a : b`) but works entirely on encrypted data. ### How It Works `FHE.select` takes the encrypted `ebool` returned by comparison operations like `gt`. If the condition represents encrypted true, it returns the first value; otherwise, it returns the second valueall without revealing which path was taken. *** ## Quick Start ```solidity Don't Do This theme={null} euint32 a = FHE.asEuint32(10); euint32 b = FHE.asEuint32(20); euint32 max; // This won't work as expected! if (a.gt(b)) { // gt returns encrypted boolean (ebool) max = a; // Traditional if..else leaks information } else { max = b; } ``` ```solidity  Do This Instead theme={null} euint32 a = FHE.asEuint32(10); euint32 b = FHE.asEuint32(20); // Use select for encrypted conditionals ebool isHigher = a.gt(b); euint32 max = FHE.select(isHigher, a, b); ``` *** ## Key Points to Remember All operations take place on encrypted data, so the actual values and comparison results stay concealed from observers. Traditional `if...else` statements on encrypted data leak information through execution paths and timing. The `select` function is the only way to handle conditional execution in FHE without leaking information. Both branches are evaluated, then the correct result is selected based on the encrypted condition. *** ## Common Use Cases Here are some common scenarios where you'll use `select`: ### 1. Maximum/Minimum Operations Find the larger or smaller of two encrypted values: ```solidity theme={null} // Maximum euint32 max = FHE.select(a.gt(b), a, b); // Minimum euint32 min = FHE.select(a.lt(b), a, b); ``` ### 2. Conditional Updates Update a value only when a condition is met: ```solidity theme={null} ebool shouldUpdate = checkCondition(); euint32 newValue = FHE.select(shouldUpdate, updatedValue, currentValue); // Store the result (either updated or unchanged) _storedValue = newValue; ``` ### 3. Threshold Checks Cap values at a certain threshold: ```solidity theme={null} ebool isAboveThreshold = value.gt(threshold); euint32 result = FHE.select(isAboveThreshold, threshold, value); // Result will be capped at threshold if value exceeds it ``` ### 4. Conditional Access Control Grant different permissions based on encrypted conditions: ```solidity theme={null} ebool hasPermission = userRole.eq(ADMIN_ROLE); euint32 accessLevel = FHE.select(hasPermission, FULL_ACCESS, LIMITED_ACCESS); ``` ### 5. Fee Calculations Apply different rates based on encrypted criteria: ```solidity theme={null} ebool isPremiumUser = userTier.gt(STANDARD_TIER); euint32 feeRate = FHE.select(isPremiumUser, PREMIUM_FEE, STANDARD_FEE); euint32 totalFee = FHE.mul(amount, feeRate); ``` *** ## Best Practices Never try to implement branching logic with traditional `if...else` statements on encrypted data. Always use `select` to ensure constant-time execution and prevent information leakage. ```solidity theme={null} // Good euint32 result = FHE.select(condition, valueA, valueB); // Bad - don't publish decrypted values for branching logic! FHE.publishDecryptResult(condition, plaintext, signature); // Don't do this! if (plaintext > 0) { result = valueA; } else { result = valueB; } ``` Complex nested conditions should be broken down into simpler operations. Each `select` can only choose between two values, so chain them carefully. ```solidity theme={null} // For multiple conditions, chain select operations ebool condition1 = a.gt(b); ebool condition2 = c.lt(d); euint32 temp = FHE.select(condition1, valueA, valueB); euint32 final = FHE.select(condition2, temp, valueC); ``` Every value, comparison, and result remains encrypted throughout the entire process. The blockchain never sees plaintext values. ```solidity theme={null} ebool condition = encryptedValue.gt(encryptedThreshold); // Encrypted comparison euint32 result = FHE.select(condition, encA, encB); // Encrypted selection // Both condition and result remain encrypted ``` Since both branches of a `select` are always evaluated, complex operations in both paths will always execute. Structure your code to minimize unnecessary computations. ```solidity theme={null} // Both computations happen regardless of condition euint32 expensiveA = complexOperation(a); euint32 expensiveB = complexOperation(b); euint32 result = FHE.select(condition, expensiveA, expensiveB); // Consider pre-computing when possible ``` *** ## Complete Example: Auction Bid Here's a practical example showing how to handle encrypted bids in an auction: ```solidity theme={null} contract EncryptedAuction { euint32 public highestBid; address public highestBidder; function placeBid(InEuint32 memory encryptedBid) public { euint32 bid = FHE.asEuint32(encryptedBid); // Compare new bid with current highest (encrypted comparison) ebool isHigher = bid.gt(highestBid); // Update highest bid using select euint32 newHighestBid = FHE.select(isHigher, bid, highestBid); // Grant contract access to the new highest bid FHE.allowThis(newHighestBid); highestBid = newHighestBid; // Update highest bidder (note: address is not encrypted) // In production, you'd handle this more carefully if (/* some non-encrypted condition */) { highestBidder = msg.sender; } } function revealWinner(euint32 ctHash, uint32 plaintext, bytes calldata signature) public { // Verify and publish the decrypted highest bid on-chain FHE.publishDecryptResult(ctHash, plaintext, signature); } } ``` In the example above, the actual bid amounts remain encrypted throughout the auction. To reveal the winner, a client calls `decryptForTx` off-chain to obtain the plaintext and a threshold signature, then submits them on-chain via `revealWinner` for verification. *** ## Related Topics * Learn more about comparison operations in [FHE Encrypted Operations](/fhe-library/core-concepts/encrypted-operations) * Understand how to manage access in [Access Control](/fhe-library/core-concepts/access-control) # Data Evaluation Source: https://cofhe-docs.fhenix.zone/fhe-library/core-concepts/data-evaluation Understanding how FHE operations communicate with off-chain compute engines ## Sending Computation Requests The blockchain that you write Smart Contracts on (for example, Arbitrum One) does not natively support FHE computation. This is why CoFHE is mostly an **off-chain system**, performing all the FHE heavy lifting asynchronously. All the logic happening on-chain is **giving instructions** for the off-chain component, CoFHE's **FHE Engine**, on what to compute. This concept is commonly referred to as [Symbolic Execution](https://en.wikipedia.org/wiki/Symbolic_execution). ### How On-Chain Smart Contracts Communicate with the Off-Chain Engine Through **Events**. Every FHE operation exposed in `FHE.sol` that requires an FHE computation emits an event. For example: ```solidity theme={null} res = FHE.sub(first, second); ``` This code snippet computes subtraction between two numbers. Behind the scenes, the function `FHE.sub()` is **emitting an event**, basically broadcasting "Hey FheOS! you need to compute `first - second`!". CoFHE then picks up this event, and forwards it to FheOS (the compute engine) for execution. ### More Examples **Creating a trivially encrypted value:** ```solidity theme={null} euint8 res = FHE.asEuint8(42); ``` This command emits an event saying "Create a trivially encrypted ciphertext representing the plaintext number `42`". **Adding encrypted values:** ```solidity theme={null} balance = FHE.add(amount, balance); ``` This command emits an event saying "Compute the encrypted result of adding the encrypted variables `balance` and `amount`". But how does CoFHE know how to connect two variables (e.g. `balance` and `amount`) to the underlying encrypted data to calculate the result? To understand this, you need to understand how encrypted data is represented in smart contracts. *** ## Data Representation In the context of a Smart Contract, most FHE operations result in a new ciphertext. Let's look at an example: ```solidity theme={null} function addNumbers() public view returns (euint32) { euint32 a = FHE.asEuint32(10); // Creating two trivially-encrypted ciphertexts euint32 b = FHE.asEuint32(20); euint32 result = FHE.add(a, b); // Add them together return result; } ``` In the example above, you are: 1. Creating two trivially-encrypted 32-bit ciphertexts using `FHE.asEuint32()` 2. Performing an FHE-addition, calculating the encrypted sum of both, using `FHE.add()` 3. Returning the result The result of every operation is a value of type `euint32`, which represents a new 32-bit ciphertext. But what does `euint32` represent exactly? Let's look at the type's declaration: ```solidity theme={null} type euint32 is uint128; ``` The `euint32` type is actually a `uint128` wrapper, not the encrypted data itself. ### Understanding Ciphertext Handles The actual ciphertext values of FHE-encrypted integers are too big to be stored directly in the blockchain, or emitted in an event. That's why in your smart contracts, the ciphertexts are represented by a 128-bit handle regardless of their encrypted type. You can think of this handle as an ID, or a pointer to the ciphertext stored off-chain. This handle is the identifier of said ciphertext. In practice, CoFHE actually stores full ciphertexts in an off-chain Data Availability (DA) layer. So, when evaluating the following statement: ```solidity theme={null} ebool isBigger = FHE.gt(newBid, currentBid); ``` `FHE.sol` is actually emitting the following event: "Check which number is bigger: `0xab12...` or `0xcd34..`". The result's handle (or identifier) will be stored in the variable `isBigger`, of type `ebool`. Wondering what to do with `ebool isBigger`? Check out the page on [Conditionals](/fhe-library/core-concepts/conditions). *** ## Deep Dive: Handle Determination Since computation is executed asynchronously, you might wonder: how can you know the ciphertext's handle in real time? In fact, the ciphertext's handle is determined regardless of its value. It basically represents the operation that needs to be performed to create this value. **Example:** ```solidity theme={null} euint64 num = FHE.asEuint64(31); euint64 meaning = FHE.add(num, FHE.asEuint64(11)); ``` The handle of `num` is a numerical representation of "trivially-encrypted `31`", while the handle of `meaning` is a similar representation of "result of addition between `num` and trivially-encrypted `11`". The actual encrypted value is, as mentioned before, evaluated asynchronously. This design allows smart contracts to continue executing without waiting for expensive FHE computations to complete. *** ## Key Concepts Summary All FHE operations emit events that instruct the off-chain engine what to compute. This enables symbolic execution of encrypted operations. Encrypted values are represented by 128-bit handles that act as identifiers. The actual ciphertext data is stored off-chain in a DA layer. FHE computations happen asynchronously off-chain. Handles are determined immediately based on the operation, not the result value. By using handles instead of storing full ciphertexts on-chain, CoFHE dramatically reduces gas costs and blockchain storage requirements. # Decryption Operations Source: https://cofhe-docs.fhenix.zone/fhe-library/core-concepts/decryption-operations Understanding how to decrypt encrypted data in FHE smart contracts ## Overview Decryption is the process of converting encrypted data back into its original form. In the context of Fully Homomorphic Encryption (FHE), decryption allows for the retrieval of results after performing computations on encrypted data. Decryption in CoFHE is a multi-step process that involves both off-chain and on-chain components: 1. A client requests decryption off-chain and receives the plaintext along with a Threshold Network signature. 2. The plaintext and signature are submitted on-chain, where the contract publishes or verifies the result. Learn more about our unique MPC decryption threshold network in the [Threshold Network](/deep-dive/cofhe-components/threshold-network) guide. *** ## Decryption Methods: Transaction vs View CoFHE provides two primary ways to perform decryption, each suited for different use cases: ### 1. Decrypt for Transaction (`decryptForTx`) The client calls `decryptForTx(ctHash)` off-chain to obtain the plaintext and a Threshold Network signature. These are then submitted on-chain via `FHE.publishDecryptResult` or `FHE.verifyDecryptResult`, making the result verifiable by the contract. **Common examples:** * **Unshield a confidential token**: reveal the encrypted amount so the contract can finalize the public transfer. * **Finalize a private auction / game move**: bids or moves are submitted encrypted, and the winner is revealed later in a verifiable way. ### 2. Decrypt for View (`decryptForView`) The client calls `decryptForView` off-chain to obtain the plaintext for display in a UI. No on-chain transaction or signature is needed. **Common examples:** * Displaying a user's confidential balance in a wallet UI. * Showing the current state of an encrypted value without revealing it on-chain. Use `decryptForTx` when you need to act on the decrypted value in a smart contract. Use `decryptForView` when you only need to display the value in a UI. ### 3. Client-Published Decryption (Signature-Verified) The client decrypts off-chain via `decryptForTx`, receives the plaintext result along with an **ECDSA signature** from the Threshold Network's Dispatcher, and then publishes the result on-chain by calling `FHE.publishDecryptResult()`. The TaskManager verifies the signature on-chain before storing the result. This combines the best of both worlds: the client controls when the result lands on-chain, while the contract can still use the decrypted value. The signature cryptographically proves the result came from the authorized Threshold Network. No trust in the publisher is required — anyone holding a valid signature can submit it. ### Comparison Table | Method | Visibility | Gas Cost | Smart Contract Usable | Best For | | -------------------------------- | -------------------------------- | ----------------------------- | --------------------- | ------------------------------------------------- | | **`decryptForTx`** | Public (once published on-chain) | Gas for the publish/verify tx | Yes | Public results, contract logic | | **`decryptForView`** | Private (off-chain only) | None | No | UI display, confidential data | | **Client-Published (signature)** | Public (on-chain) | Medium | Yes | Client-driven settlement, permissionless delivery | *** ## The Decryption Flow ### Step 1: Grant decryption permissions (on-chain) Before anyone can request decryption, the ciphertext handle must have the appropriate ACL permissions. Use one of the following in your contract: * `FHE.allowPublic(ctHash)` — anyone can request decryption (common for unshield flows) * `FHE.allow(ctHash, address)` — only a specific address can request decryption * `FHE.allowSender(ctHash)` — only `msg.sender` can request decryption ```solidity theme={null} // Example: allow anyone to decrypt when the auction closes function closeBidding() external onlyAuctioneer { FHE.allowPublic(highestBid); auctionClosed = true; } ``` See [Access Control](/fhe-library/core-concepts/access-control) for the full list of permission methods, including `FHE.allowPublic()`. ### Step 2: Request decryption off-chain (client-side) The client calls `decryptForTx(ctHash)` to obtain the plaintext and a Threshold Network signature. Choose the permit mode that matches the contract's ACL policy: ```typescript No permit (allowPublic) theme={null} const decryptResult = await client .decryptForTx(ctHash) .withoutPermit() .execute(); // decryptResult.ctHash — the ciphertext handle // decryptResult.decryptedValue — the plaintext (bigint) // decryptResult.signature — the Threshold Network signature ``` ```typescript With permit (restricted access) theme={null} const decryptResult = await client .decryptForTx(ctHash) .withPermit() .execute(); ``` `decryptForTx` always returns the plaintext as a `bigint`. Your contract determines whether that value is interpreted as `uint32`, `uint64`, etc. ### Step 3: Publish or verify on-chain Submit the plaintext and signature to your contract. You have two options: #### Option A: `FHE.publishDecryptResult` Publishes the decrypted value on-chain, making it available for any contract to read. ```solidity Solidity theme={null} import "@fhenixprotocol/cofhe-contracts/FHE.sol"; function revealWinner(euint64 ctHash, uint64 plaintext, bytes calldata signature) external onlyAuctioneer { FHE.publishDecryptResult(ctHash, plaintext, signature); winningBid = plaintext; emit RevealedWinningBid(highestBidder, plaintext); } ``` ```typescript TypeScript theme={null} const tx = await myContract.revealWinner( decryptResult.ctHash, decryptResult.decryptedValue, decryptResult.signature ); await tx.wait(); ``` #### Option B: `FHE.verifyDecryptResult` Verifies the signature without publishing the result globally. Use this when your contract only needs to confirm the plaintext is authentic. ```solidity theme={null} import "@fhenixprotocol/cofhe-contracts/FHE.sol"; function unshield(bytes32 ctHash, uint32 plaintext, bytes calldata signature) external { require(FHE.verifyDecryptResult(ctHash, plaintext, signature), "Invalid decrypt signature"); // ...continue with protocol logic... } ``` *** ## Full Example Contract Here's a complete example showing the new decryption flow in an auction contract: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@fhenixprotocol/cofhe-contracts/FHE.sol"; contract EncryptedAuction { euint64 public highestBid; address public highestBidder; address public auctioneer; bool public auctionClosed; uint64 public winningBid; event BidPlaced(address indexed bidder); event AuctionClosed(); event RevealedWinningBid(address indexed winner, uint64 amount); modifier onlyAuctioneer() { require(msg.sender == auctioneer, "Only auctioneer can call this"); _; } constructor() { auctioneer = msg.sender; } // Place an encrypted bid function placeBid(InEuint64 memory encryptedBid) external { require(!auctionClosed, "Auction is closed"); euint64 bid = FHE.asEuint64(encryptedBid); ebool isHigher = bid.gt(highestBid); // Update highest bid if this bid is higher euint64 newHighestBid = FHE.select(isHigher, bid, highestBid); FHE.allowThis(newHighestBid); highestBid = newHighestBid; highestBidder = msg.sender; emit BidPlaced(msg.sender); } // Close the auction and allow public decryption of the winning bid function closeBidding() external onlyAuctioneer { require(!auctionClosed, "Auction already closed"); FHE.allowPublic(highestBid); auctionClosed = true; emit AuctionClosed(); } // Reveal the winner by publishing the decrypted result with proof function revealWinner(euint64 ctHash, uint64 plaintext, bytes calldata signature) external { require(auctionClosed, "Auction must be closed first"); FHE.publishDecryptResult(ctHash, plaintext, signature); winningBid = plaintext; emit RevealedWinningBid(highestBidder, plaintext); } } ``` The client-side flow to reveal the winner: ```typescript theme={null} // 1. Read the encrypted highest bid from the contract const ctHash = await auctionContract.highestBid(); // 2. Request decryption off-chain (no permit needed since allowPublic was used) const decryptResult = await client .decryptForTx(ctHash) .withoutPermit() .execute(); // 3. Submit the result on-chain const tx = await auctionContract.revealWinner( decryptResult.ctHash, decryptResult.decryptedValue, decryptResult.signature ); await tx.wait(); ``` *** ## Batch Client-Published Decryption ```solidity theme={null} function publishMultipleResults( uint256[] memory ctHashes, uint256[] memory results, bytes[] memory signatures ) external { FHE.publishDecryptResultBatch(ctHashes, results, signatures); } ``` *** ## Signature Verification Functions When using client-published decryption, two verification functions are available: | Function | Behavior on Invalid Signature | | -------------------------------------------------------- | ----------------------------- | | `FHE.verifyDecryptResult(ctHash, result, signature)` | Reverts | | `FHE.verifyDecryptResultSafe(ctHash, result, signature)` | Returns `false` | Both functions accept type-specific overloads for `ebool`, `euint8`, `euint16`, `euint32`, `euint64`, `euint128`, and `eaddress`. *** ## Best Practices When a value is intended to become public (e.g. unshielding, auction reveals), use `FHE.allowPublic()` so anyone can trigger the decryption without needing a permit. Ensure only authorized parties can request decryption. Use `FHE.allow()` or `FHE.allowSender()` for restricted access. See [Access Control](/fhe-library/core-concepts/access-control). Use `FHE.publishDecryptResult` when you want the result stored publicly on-chain. Use `FHE.verifyDecryptResult` when you only need to confirm the plaintext is authentic without publishing it. If you only need to display a value in your UI and don't need an on-chain-verifiable signature, use `decryptForView` instead of `decryptForTx` to avoid unnecessary on-chain transactions. *** ## Common Pitfalls * **Missing ACL permissions**: If no `allow*` was called for the ciphertext handle, decryption requests will be denied. Make sure to grant permissions before the client requests decryption. * **Permit mode must be selected**: When using `decryptForTx`, you must call exactly one of `.withPermit(...)` or `.withoutPermit()` before `.execute()`. * **Wrong chain/account**: Permits are scoped to `chainId + account`. If you get an ACL/permit error, double-check you're connected to the expected chain and account. * **Type mismatch**: `decryptedValue` is always a `bigint`. If your Solidity function expects a smaller integer type (e.g. `uint32`), make sure the value is within range. *** ## Related Topics * Learn about access control requirements in [Access Control](/fhe-library/core-concepts/access-control) * Understand asynchronous operations in [Data Evaluation](/fhe-library/core-concepts/data-evaluation) * Explore the decryption request flow in [Decryption Request Flow](/deep-dive/data-flows/decryption-request-flow) # FHE Encrypted Operations Source: https://cofhe-docs.fhenix.zone/fhe-library/core-concepts/encrypted-operations Complete guide to FHE types and operations for confidential smart contracts ## Overview The library exposes utility functions for FHE operations. The goal of the library is to provide a seamless developer experience for writing smart contracts that can operate on confidential data. ## Types The library provides a type system that is checked both at compile time and at run time. The structure and operations related to these types are described in this section. We currently support encrypted integers of bit length up to 128 bits and special types such as `ebool` and `eaddress`. The encrypted integers behave as much as possible as Solidity's integer types. However, behavior such as "revert on overflow" is not supported as this would leak some information about the encrypted integers. Therefore, arithmetic on `euint` types is [unchecked](https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic), i.e. there is wrap-around on overflow. In the back-end, encrypted integers are FHE ciphertexts. The library abstracts away the ciphertexts and presents pointers to ciphertexts, or ciphertext handles, to the smart contract developer. The `euint`, `ebool` and `eaddress` types are *wrappers* over these handles. ### Supported Types | Name | Bit Size | Usage | | ---------- | -------- | ------- | | `euint8` | 8 | Compute | | `euint16` | 16 | Compute | | `euint32` | 32 | Compute | | `euint64` | 64 | Compute | | `euint128` | 128 | Compute | | `ebool` | 8 | Compute | | `eaddress` | 160 | Compute | | Name | Bit Size | Usage | | ------------ | -------- | ----- | | `InEuint8` | 8 | Input | | `InEuint16` | 16 | Input | | `InEuint32` | 32 | Input | | `InEuint64` | 64 | Input | | `InEuint128` | 128 | Input | | `InEbool` | 8 | Input | | `InEaddress` | 160 | Input | The `ebool` type is not a real boolean type. It is implemented as a `euint8` for compatibility with FHE operations. *** ## Operations There are two ways to perform operations with FHE.sol: ### Using Direct Function Calls Direct function calls are the most straightforward way to perform operations with FHE.sol. For example, if you want to add two encrypted 8-bit integers (euint8), you can do so as follows: ```solidity theme={null} euint8 result = FHE.add(lhs, rhs); ``` Here, `lhs` and `rhs` are your `euint8` variables, and `result` will store the outcome of the addition. ### Using Library Bindings FHE.sol also provides library bindings, allowing for a more natural syntax. To use this, you first need to include the library for your specific data type. For `euint8`, the usage would look like this: ```solidity theme={null} euint8 result = lhs.add(rhs); ``` In this example, `lhs.add(rhs)` performs the addition using the library function implicitly. *** ## Supported Operations Complete documentation of every function in FHE.sol (including inputs and outputs) can be found in the [FHE.sol API Reference](/fhe-library/reference/fhe-sol). All operations supported by FHE.sol are listed in the table below. Note that all functions are supported in both direct function calls and library bindings. | Name | FHE.sol function | Operator | euint8 | euint16 | euint32 | euint64 | euint128 | ebool | eaddress | | --------------------- | ---------------- | :------: | :----: | :-----: | :-----: | :-----: | :------: | :---: | :------: | | Addition | `add` | `+` | Yes | Yes | Yes | Yes | Yes | No | No | | Subtraction | `sub` | `-` | Yes | Yes | Yes | Yes | Yes | No | No | | Multiplication | `mul` | `*` | Yes | Yes | Yes | Yes | Yes | No | No | | Bitwise And | `and` | `&` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Bitwise Or | `or` | `\|` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Bitwise Xor | `xor` | `^` | Yes | Yes | Yes | Yes | Yes | Yes | No | | Division | `div` | `/` | Yes | Yes | Yes | Yes | Yes | No | No | | Remainder | `rem` | `%` | Yes | Yes | Yes | Yes | Yes | No | No | | Square | `square` | n/a | Yes | Yes | Yes | Yes | Yes | No | No | | Shift Right | `shr` | n/a | Yes | Yes | Yes | Yes | Yes | No | No | | Shift Left | `shl` | n/a | Yes | Yes | Yes | Yes | Yes | No | No | | Rotate Right | `ror` | n/a | Yes | Yes | Yes | Yes | Yes | No | No | | Rotate Left | `rol` | n/a | Yes | Yes | Yes | Yes | Yes | No | No | | Equal | `eq` | n/a | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Not equal | `ne` | n/a | Yes | Yes | Yes | Yes | Yes | Yes | Yes | | Greater than or equal | `gte` | n/a | Yes | Yes | Yes | Yes | Yes | No | No | | Greater than | `gt` | n/a | Yes | Yes | Yes | Yes | Yes | No | No | | Less than or equal | `lte` | n/a | Yes | Yes | Yes | Yes | Yes | No | No | | Less than | `lt` | n/a | Yes | Yes | Yes | Yes | Yes | No | No | | Min | `min` | n/a | Yes | Yes | Yes | Yes | Yes | No | No | | Max | `max` | n/a | Yes | Yes | Yes | Yes | Yes | No | No | | Not | `not` | n/a | Yes | Yes | Yes | Yes | Yes | Yes | No | | Select | `select` | n/a | Yes | Yes | Yes | Yes | Yes | Yes | Yes | **Division and Remainder by `0`**: These operations will output an encrypted representation of the maximal value of the uint type being used (e.g., encrypted 255 for `euint8`). This behavior prevents information leakage about the divisor. *** ## Operation Examples ### Arithmetic Operations ```solidity theme={null} // Addition euint32 sum = FHE.add(a, b); // or using library binding euint32 sum = a.add(b); // Multiplication euint32 product = FHE.mul(a, b); ``` ### Comparison Operations ```solidity theme={null} // Returns an encrypted boolean (ebool) ebool isGreater = FHE.gt(amount, threshold); ebool isEqual = FHE.eq(value1, value2); ``` ### Bitwise Operations ```solidity theme={null} // Bitwise AND euint16 masked = FHE.and(flags, mask); // Bitwise shifts euint32 shifted = FHE.shl(value, 4); // Shift left by 4 bits ``` ### Control Flow ```solidity theme={null} // Select between two values based on encrypted condition euint32 result = FHE.select(condition, valueIfTrue, valueIfFalse); ``` Learn more about using encrypted conditionals in the [Select vs If-Else](/fhe-library/core-concepts/conditions) guide. # Gas Costs and Benchmarks Source: https://cofhe-docs.fhenix.zone/fhe-library/core-concepts/gas-and-benchmarks Performance metrics and gas cost analysis for FHE operations **Coming Soon!** This page is currently under development. Check back soon for detailed information about: * Gas costs for different FHE operations * Performance benchmarks across encrypted types * Optimization strategies for gas-efficient contracts * Comparative analysis of operation costs ## What to Expect This documentation will provide comprehensive insights into the computational costs of FHE operations, helping you: * **Optimize your contracts** by understanding the relative costs of different operations * **Plan gas budgets** for your confidential smart contract applications * **Make informed decisions** about which encrypted types and operations to use * **Benchmark performance** against your requirements Stay tuned for updates! # Inputs Source: https://cofhe-docs.fhenix.zone/fhe-library/core-concepts/inputs Learn how to handle encrypted user inputs in confidential smart contracts ## Overview One of the key aspects of writing confidential smart contracts is receiving encrypted inputs from users: ```solidity theme={null} function transfer( address to, InEuint32 memory inAmount // <------ encrypted input here ) public virtual returns (euint32 transferred) { euint32 amount = FHE.asEuint32(inAmount); } ``` Notice in the example above the distinction between **`InEuint32`** and **`euint32`**. ## Input Types Conversion The **input types** `InEuintxx` (and `InEbool`, `InEaddress`) are special encrypted types that represent **user input**. Input types contain additional information required to authenticate and validate ciphertexts. For more on that, read about the [ZK-Verifier](/deep-dive/cofhe-components/zk-verifier). Before you can use an encrypted input, you need to convert it to a regular **encrypted type**: ```solidity theme={null} euint32 amount = FHE.asEuint32(inAmount); ``` Avoid storing encrypted input types in contract state. These types carry extra metadata, which increases gas costs and may cause unexpected behavior. Always convert them using `FHE.asE...()`. Now that `amount` is of type `euint32`, you can store or manipulate it: ```solidity theme={null} toBalance = FHE.sub(toBalance, amount); ``` Read more about the available FHE types and operations in the [FHE Encrypted Operations](/fhe-library/core-concepts/encrypted-operations) guide. ## Full Example Here's a complete example showing how to handle encrypted inputs in a transfer function: ```solidity theme={null} function transfer( address to, InEuint32 memory inAmount ) public virtual returns (euint32 transferred) { euint32 amount = FHE.asEuint32(inAmount); toBalance = _balances[to]; fromBalance = _balances[msg.sender]; _updateBalance(to, FHE.add(toBalance, amount)); _updateBalance(from, FHE.sub(fromBalance, amount)); } ``` For the example above to work correctly, you will also need to manage access to the newly created ciphertexts in the `_updateBalance()` function. Learn more about access control in the [ACL Mechanism](/fhe-library/core-concepts/access-control) guide. ## Additional Examples ### Voting in a Poll ```solidity theme={null} function castEncryptedVote(address poll, InEbool calldata encryptedVote) public { _submitVote(poll, FHE.asEbool(encryptedVote)); } ``` ### Setting Encrypted User Preferences ```solidity theme={null} function updateUserSetting(address user, InEuint8 calldata encryptedSetting) public { _applyUserSetting(user, FHE.asEuint8(encryptedSetting)); } ``` # Randomness Source: https://cofhe-docs.fhenix.zone/fhe-library/core-concepts/randomness Generating encrypted random values in confidential smart contracts ## Overview The FHE library provides cryptographically secure random number generation that produces encrypted random values. The values are generated in encrypted form—no one can see the plaintext until it is explicitly decrypted. ## Random Functions | Function | Return Type | | ---------------------- | ----------- | | `FHE.randomEuint8()` | `euint8` | | `FHE.randomEuint16()` | `euint16` | | `FHE.randomEuint32()` | `euint32` | | `FHE.randomEuint64()` | `euint64` | | `FHE.randomEuint128()` | `euint128` | ## Usage ```solidity theme={null} import { FHE, euint8, euint32, ebool } from "@fhenixprotocol/cofhe-contracts/FHE.sol"; contract RandomExample { euint32 private storedRandom; // Basic random generation function generateRandom() public { euint32 random = FHE.randomEuint32(); FHE.allowThis(random); storedRandom = random; } // Random in range [0, 99] function randomInRange() public returns (euint32) { euint32 random = FHE.randomEuint32(); euint32 result = FHE.rem(random, FHE.asEuint32(100)); FHE.allowThis(result); return result; } // Efficient random boolean (coin flip) function randomBool() public returns (ebool) { euint8 random = FHE.randomEuint8(); // AND with 1 forces the value into {0,1}, ensuring a uniform 50/50 distribution. ebool result = FHE.asEbool(FHE.and(random, FHE.asEuint8(1))); FHE.allowThis(result); return result; } } ``` Call `FHE.allowThis()` on random values if you need to store or use them later in your contract. # Require Source: https://cofhe-docs.fhenix.zone/fhe-library/core-concepts/require Understanding require statements with encrypted data in FHE ## Overview The `require` statement in FHE contracts works similarly to [Conditions (if .. else)](/fhe-library/core-concepts/conditions) because both involve conditional logic on encrypted data. Just like you can't use traditional `if...else` statements with encrypted values, you also can't use standard `require` statements that depend on encrypted conditions. Traditional `require` statements that check encrypted conditions will leak information about your encrypted values through execution paths. *** ## Why Require is Like Conditions Both `require` and conditional statements face the same fundamental challenge in FHE: The condition being checked is encrypted, so the contract can't directly evaluate it to decide whether to revert. If a transaction reverts based on an encrypted condition, observers can infer information about the encrypted data. *** ## The Solution Instead of using `require` with encrypted conditions, you should: 1. **Use `FHE.select`** to handle the conditional logic (see [Conditions (if .. else)](/fhe-library/core-concepts/conditions)) 2. **Only use `require` for non-encrypted conditions** like access control checks, address validation, or other plaintext values ```solidity theme={null} // Good - require on plaintext condition require(msg.sender == owner, "Not authorized"); // Good - use select for encrypted logic euint32 result = FHE.select(encryptedCondition, valueA, valueB); // Bad - require on encrypted condition is not possible! // FHE.decrypt was removed — decryption is now off-chain only // require(FHE.decrypt(encryptedValue.gt(threshold)), "Value too low"); ``` *** ## Learn More For detailed guidance on handling conditional logic with encrypted data, see the [Conditions (if .. else)](/fhe-library/core-concepts/conditions) page. # Trivial Encryption Source: https://cofhe-docs.fhenix.zone/fhe-library/core-concepts/trivial-encryption Understanding the difference between trivially encrypted numbers and encrypted inputs ## Overview In FHE-enabled smart contracts, you often need to perform operations between encrypted values and regular plaintext values. **Trivial encryption** is the operation of converting a plaintext value into an encrypted format that can interact with other encrypted data. This conversion is done using the `FHE.asEuint` family of functions, which take standard Solidity types and transform them into their encrypted counterparts. ```solidity theme={null} uint16 number = 2; euint16 encrypted_number = FHE.asEuint16(number); ``` ## Privacy Considerations **Trivially encrypted values are not confidential** - they are merely a tool to enable interaction between encrypted and non-encrypted types. The original plaintext value remains visible to anyone observing the blockchain, just in a different format. This is a crucial concept to understand when developing confidential contracts. Any value that is trivially encrypted (e.g. `encrypted_number`) should be treated as public information, even though it uses the same data type as truly encrypted values. `euints` are only confidential when they are formed from encrypted `inEuint` inputs, which are encrypted off-chain. Learn more in the [Data Evaluation](/fhe-library/core-concepts/data-evaluation) guide. When two trivially-encrypted numbers are combined in an FHE operation, the result is still not confidential, because an observer can keep track of the calculations. ## Example ```solidity theme={null} function doSomeCalculations(InEuint16 calldata input) { // public euint16 number2 = FHE.asEuint16(2); euint16 number3 = FHE.asEuint16(3); euint16 number5 = FHE.add(number2, number3); // confidential euint16 encInput = FHE.asEuint16(input); euint16 encMul = FHE.mul(encInput, number5); // public euint16 eFalse = FHE.asEbool(false); // Observer knows that result is encInput, but not what the value is euint16 result = FHE.select(eFalse, encMul, encInput); } ``` # Encrypted Auction Example Source: https://cofhe-docs.fhenix.zone/fhe-library/examples/auction-example A complete example of building a confidential auction system using FHE ## Overview This example demonstrates how to build a fully confidential auction system where bids remain encrypted throughout the bidding process. Only when the auction closes can the winner be revealed, ensuring that bidders cannot see or react to each other's bids. ### What You'll Learn In this example, you'll see practical implementations of: * **Encrypted input handling** - Processing encrypted bid amounts * **Encrypted comparisons** - Finding the highest bid without revealing values * **Conditional logic with `select`** - Updating the highest bidder based on encrypted conditions * **Access control management** - Properly managing permissions for encrypted data * **Decrypt-with-proof pattern** - Using `decryptForTx` off-chain and `publishDecryptResult` on-chain to reveal the winner *** ## How It Works The auction follows this flow: The auctioneer deploys the contract, which initializes the auction with zero bid and address values, both encrypted. Participants submit bids by sending plaintext amounts that are immediately encrypted. Each bid is compared against the current highest bid using encrypted comparison (`FHE.gt`), and the highest bid and bidder are updated accordingly. The auctioneer closes the auction and calls `FHE.allowPublic` on the highest bid and bidder, making them eligible for public decryption. Anyone can call `decryptForTx` off-chain to obtain the plaintext values and Threshold Network signatures for the winning bid and bidder. The decrypted values and signatures are submitted on-chain via `revealWinner`, which calls `FHE.publishDecryptResult` to verify the proofs and store the results. *** ## Key Concepts Demonstrated ### 1. Encrypted State Variables The contract stores the highest bid and bidder as encrypted values: ```solidity theme={null} euint64 private highestBid; // Encrypted bid amount eaddress private highestBidder; // Encrypted bidder address ``` These values remain encrypted throughout the entire auction, preventing anyone from seeing the current highest bid. ### 2. Encrypted Comparisons and Updates When a new bid comes in, the contract uses encrypted operations to update the highest bid: ```solidity theme={null} euint64 emount = FHE.asEuint64(amount); // Encrypt the bid ebool isHigher = FHE.gt(emount, highestBid); // Compare encrypted values highestBid = FHE.max(emount, highestBid); // Take the maximum highestBidder = FHE.select(isHigher, newBidder, currentBidder); // Update bidder ``` ### 3. Decrypt-with-Proof Pattern The contract demonstrates the new decryption flow: **Step 1:** Close auction and allow public decryption (on-chain) ```solidity theme={null} FHE.allowPublic(highestBid); FHE.allowPublic(highestBidder); ``` **Step 2:** Request decryption off-chain (client-side) ```typescript theme={null} const bidResult = await client.decryptForTx(bidCtHash).withoutPermit().execute(); const bidderResult = await client.decryptForTx(bidderCtHash).withoutPermit().execute(); ``` **Step 3:** Publish results on-chain with proof ```solidity theme={null} FHE.publishDecryptResult(highestBid, plaintext, signature); FHE.publishDecryptResult(highestBidder, plaintextAddress, bidderSignature); ``` *** ## Complete Contract Code Here's the full implementation of the encrypted auction contract: ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity >=0.8.19 <0.9.0; import "@fhenixprotocol/cofhe-contracts/FHE.sol"; contract AuctionExample { address private auctioneer; euint64 private highestBid; eaddress private highestBidder; uint64 public winningBid; address public winningBidder; bool public auctionClosed; event BidPlaced(address indexed bidder); event AuctionClosed(); event RevealedWinningBid(address winner, uint64 amount); modifier onlyAuctioneer() { require( msg.sender == auctioneer, "Only the auctioneer can call this function" ); _; } constructor() { auctioneer = msg.sender; // Set deployer as auctioneer auctionClosed = false; highestBid = FHE.asEuint64(0); highestBidder = FHE.asEaddress(address(0)); // Preserve ownership for further access FHE.allowThis(highestBid); FHE.allowThis(highestBidder); } function bid(uint256 amount) external { require(!auctionClosed, "Auction is closed"); euint64 emount = FHE.asEuint64(amount); ebool isHigher = FHE.gt(emount, highestBid); highestBid = FHE.max(emount, highestBid); highestBidder = FHE.select( isHigher, FHE.asEaddress(msg.sender), // Encrypt the sender's address highestBidder ); // Preserve ownership for further access FHE.allowThis(highestBid); FHE.allowThis(highestBidder); emit BidPlaced(msg.sender); } // Close the auction and allow public decryption function closeBidding() external onlyAuctioneer { require(!auctionClosed, "Auction is already closed"); FHE.allowPublic(highestBid); FHE.allowPublic(highestBidder); auctionClosed = true; emit AuctionClosed(); } // Reveal the winner by publishing decrypted results with proof function revealWinner( euint64 bidCtHash, uint64 bidPlaintext, bytes calldata bidSignature, eaddress bidderCtHash, address bidderPlaintext, bytes calldata bidderSignature ) external { require(auctionClosed, "Auction isn't closed"); FHE.publishDecryptResult(bidCtHash, bidPlaintext, bidSignature); FHE.publishDecryptResult(bidderCtHash, bidderPlaintext, bidderSignature); winningBid = bidPlaintext; winningBidder = bidderPlaintext; emit RevealedWinningBid(bidderPlaintext, bidPlaintext); } } ``` *** ## Code Walkthrough ### Constructor The constructor initializes the auction with encrypted zero values: ```solidity theme={null} constructor() { auctioneer = msg.sender; auctionClosed = false; highestBid = FHE.asEuint64(0); highestBidder = FHE.asEaddress(address(0)); // Grant contract access to these encrypted values FHE.allowThis(highestBid); FHE.allowThis(highestBidder); } ``` The `FHE.allowThis()` calls are crucial - they grant the contract permission to access these encrypted values in future transactions. ### Bidding Function The `bid()` function handles incoming bids: ```solidity theme={null} function bid(uint256 amount) external { require(!auctionClosed, "Auction is closed"); // 1. Encrypt the bid amount euint64 emount = FHE.asEuint64(amount); // 2. Check if this bid is higher (encrypted comparison) ebool isHigher = FHE.gt(emount, highestBid); // 3. Update highest bid using max highestBid = FHE.max(emount, highestBid); // 4. Update highest bidder using select highestBidder = FHE.select( isHigher, FHE.asEaddress(msg.sender), highestBidder ); // 5. Grant contract access to new encrypted values FHE.allowThis(highestBid); FHE.allowThis(highestBidder); emit BidPlaced(msg.sender); } ``` Notice how the contract never reveals the current highest bid to bidders. All comparisons and updates happen on encrypted data, maintaining complete confidentiality throughout the bidding process. ### Closing the Auction The auctioneer closes the auction and allows public decryption of the winning bid and bidder: ```solidity theme={null} function closeBidding() external onlyAuctioneer { require(!auctionClosed, "Auction is already closed"); // Allow anyone to request decryption of the results FHE.allowPublic(highestBid); FHE.allowPublic(highestBidder); auctionClosed = true; emit AuctionClosed(); } ``` Since `FHE.allowPublic` is used, anyone can request decryption off-chain without needing a permit. The values are not revealed until someone submits the proof on-chain. ### Revealing the Winner The `revealWinner` function accepts the decrypted values and their Threshold Network signatures, then publishes them on-chain: ```solidity theme={null} function revealWinner( euint64 bidCtHash, uint64 bidPlaintext, bytes calldata bidSignature, eaddress bidderCtHash, address bidderPlaintext, bytes calldata bidderSignature ) external { require(auctionClosed, "Auction isn't closed"); // Verify and publish both decrypted results FHE.publishDecryptResult(bidCtHash, bidPlaintext, bidSignature); FHE.publishDecryptResult(bidderCtHash, bidderPlaintext, bidderSignature); winningBid = bidPlaintext; winningBidder = bidderPlaintext; emit RevealedWinningBid(bidderPlaintext, bidPlaintext); } ``` `FHE.publishDecryptResult` verifies the Threshold Network signature before accepting the plaintext. If the signature is invalid, the transaction reverts. *** ## Usage Flow ### 1. Deploy the Contract ```typescript theme={null} const auction = await AuctionExample.deploy(); await auction.waitForDeployment(); ``` ### 2. Place Bids ```typescript theme={null} await auction.connect(bidder1).bid(1000); await auction.connect(bidder2).bid(1500); await auction.connect(bidder3).bid(1200); ``` ### 3. Close the Auction ```typescript theme={null} await auction.connect(auctioneer).closeBidding(); ``` ### 4. Decrypt Off-Chain and Reveal the Winner ```typescript theme={null} // Read the encrypted handles from the contract const bidCtHash = await auction.highestBid(); const bidderCtHash = await auction.highestBidder(); // Request decryption off-chain (no permit needed since allowPublic was used) const bidResult = await client .decryptForTx(bidCtHash) .withoutPermit() .execute(); const bidderResult = await client .decryptForTx(bidderCtHash) .withoutPermit() .execute(); // Submit the proofs on-chain to reveal the winner await auction.revealWinner( bidResult.ctHash, bidResult.decryptedValue, bidResult.signature, bidderResult.ctHash, bidderResult.decryptedValue, bidderResult.signature ); // Check the results const winner = await auction.winningBidder(); const amount = await auction.winningBid(); console.log(`Winner: ${winner}, Bid: ${amount}`); ``` *** ## Key Takeaways Bids remain completely encrypted during the auction. No one can see the current highest bid or react to other bids. The contract uses `FHE.gt()`, `FHE.max()`, and `FHE.select()` to update the highest bid without decrypting values. Every encrypted value created must have permissions granted via `FHE.allowThis()` for the contract to access it later. Use `FHE.allowPublic()` when values are ready to be revealed. Decryption happens off-chain via `decryptForTx`, and results are verified on-chain via `FHE.publishDecryptResult` with a Threshold Network signature. *** ## Related Examples * Learn about conditional logic in [Conditions](/fhe-library/core-concepts/conditions) * Understand decryption methods in [Decryption Operations](/fhe-library/core-concepts/decryption-operations) * Explore access control in [Access Control](/fhe-library/core-concepts/access-control) # Best Practices Source: https://cofhe-docs.fhenix.zone/fhe-library/introduction/best-practices Key best practices for developing secure and efficient FHE smart contracts with CoFHE ## Overview This guide outlines key best practices for developing with CoFHE, based on recommendations from our development team. Following these practices will help you build secure, efficient, and maintainable FHE-enabled smart contracts. ## Prerequisites Before reading this guide, you should: * Have completed the [Quick Start](/fhe-library/introduction/quick-start) setup * Understand basic FHE concepts and encrypted types * Be familiar with Solidity smart contract development ## Security Considerations ### Publish Decrypted Data Carefully Decryption is a multi-step process: the client requests a plaintext + signature off-chain via `decryptForTx`, then publishes or verifies the result on-chain using `FHE.publishDecryptResult` or `FHE.verifyDecryptResult`. Once published, the plaintext is visible to everyone on the blockchain. **Key principles:** * **Evaluate information leakage**: Before publishing a decrypted value on-chain, consider what information you're exposing and what an observer might learn from it * **Minimize published values**: Only publish decrypted results when your protocol truly requires the plaintext on-chain. Use `decryptForView` if you only need to display the value in a UI * **Use `verifyDecryptResult` when possible**: If your contract only needs to confirm a value without storing it publicly, prefer `FHE.verifyDecryptResult` over `FHE.publishDecryptResult` Publishing a decrypted value on-chain makes it permanently visible to all observers. Always consider whether you need `decryptForTx` (on-chain proof) or `decryptForView` (UI-only) for your use case. ### Always update permissions Remember to call `FHE.allowThis()` after modifying any encrypted value that needs to be accessed later: ```solidity theme={null} counter = FHE.add(counter, FHE.asEuint32(1)); FHE.allowThis(counter); ``` Without calling `FHE.allowThis()`, your contract won't be able to access the encrypted value in subsequent operations. This is a common source of errors in FHE development. ### Avoid code branching based on encrypted data **Remember: there is no secure code branching with FHE.** Decrypting to make branching decisions is generally a bad practice and can leak information. **Best practices:** * **Use constant-time algorithms**: Design your code to follow the same execution path regardless of encrypted values * **Prefer FHE.select over conditional logic**: Use built-in selection operations rather than decrypting for if/else decisions **Example:** ```solidity Don't Do This theme={null} // Don't reveal encrypted values to make branching decisions FHE.publishDecryptResult(condition, plaintext, signature); if (plaintext > 0) { result = a; } else { result = b; } ``` ```solidity Recommended Approach theme={null} // Use FHE.select instead result = FHE.select(condition, a, b); ``` Since conditional branching doesn't work with encrypted values, always use `FHE.select()` for conditional logic. This ensures your code follows a constant execution path regardless of the encrypted values. ## Performance Optimization ### Optimize computational efficiency FHE operations are computationally expensive. Optimize your contracts to minimize overhead: **Key strategies:** * **Minimize FHE operations**: Each operation adds computational overhead, so reduce the number of operations where possible * **Use the minimum bit-width necessary**: Choose the smallest integer type that can safely represent your data ```solidity ❌ Less Efficient theme={null} euint64 counter; // Using 64 bits when 32 would suffice ``` ```solidity ✅ More Efficient theme={null} euint32 counter; // Using 32 bits when that's sufficient ``` ### Reuse Encrypted Constants Encrypt constant values once and reuse them to save gas: ```solidity theme={null} // Good practice: Encrypt once, reuse many times euint32 ONE = FHE.asEuint32(1); FHE.allowThis(ONE); // Later in the code counter = FHE.add(counter, ONE); counter = FHE.add(counter, ONE); // Reusing the same encrypted constant ``` Reusing encrypted constants is a gas optimization technique. Encrypt frequently used values (like 0, 1, or common thresholds) once at contract initialization and reuse them throughout your contract's lifetime. ### Plan for Asynchronous Operations CoFHE operations are asynchronous by nature. Design your application to handle this gracefully: **UI considerations:** * **Implement loading indicators**: Show spinners, progress bars, or status messages to inform users when operations are in progress * **Use progress indicators**: Provide feedback during encryption and decryption operations * **Consider state management**: Design your application to handle pending states gracefully **Example UI pattern:** Use `.onStep(callback)` to track encryption progress. The callback fires at the start and end of each step, receiving the current `EncryptStep` enum value and a context object with `isStart`, `isEnd`, and `duration` (milliseconds, only meaningful on `isEnd`). ```typescript theme={null} import { createCofheClient, createCofheConfig } from '@cofhe/sdk/web'; import { Encryptable, EncryptStep } from '@cofhe/sdk'; import { chains } from '@cofhe/sdk/chains'; const [isEncrypting, setIsEncrypting] = useState(false); const [currentStep, setCurrentStep] = useState(null); const handleEncrypt = async () => { setIsEncrypting(true); try { const result = await cofheClient .encryptInputs([Encryptable.uint32(5n)]) .onStep((step, ctx) => { if (ctx?.isStart) setCurrentStep(step); if (ctx?.isEnd) console.log(`${step} done in ${ctx.duration}ms`); }) .execute(); // Handle result } finally { setIsEncrypting(false); setCurrentStep(null); } }; ``` The `EncryptStep` enum values fire in order: `InitTfhe` → `FetchKeys` → `Pack` → `Prove` → `Verify`. CoFHE operations may take time to complete, especially on testnets. Always provide user feedback during these operations to improve user experience. ## Development Workflow ### Start with Mock Environment Begin development using mock contracts for faster iteration: * **Faster feedback loop**: Mock environment provides immediate results without network delays * **Easier debugging**: Plaintext values are visible in mock contracts, making debugging simpler * **No external dependencies**: Test locally without connecting to testnets ### Test Thoroughly Write comprehensive tests covering: * **Both environments**: Test in mock and testnet environments * **Edge cases**: Handle zero values, maximum values, and boundary conditions * **Error scenarios**: Test what happens when operations fail or permissions are missing ### Gas Optimization Be aware of gas costs: * **Mock environments simulate higher costs**: Gas costs in mock environments are higher than production * **Test on testnet for accurate estimates**: Always test gas consumption on testnet before deployment * **Optimize before deployment**: Review and optimize your contract's gas usage ## Summary Following these best practices will help you: * **Build secure contracts**: Proper permission management and avoiding information leakage * **Optimize performance**: Minimize operations and reuse encrypted constants * **Improve user experience**: Handle asynchronous operations gracefully * **Develop efficiently**: Use mock environments for rapid iteration By following these best practices, you'll create more secure, efficient, and maintainable FHE-enabled smart contracts. ## Next Steps * Learn about [encrypted data types](/fhe-library) and their use cases * Explore [FHE operations](/fhe-library) available in the library * Review [access control mechanisms](/fhe-library) for managing permissions * Check out [common pitfalls](/fhe-library) to avoid # Overview Source: https://cofhe-docs.fhenix.zone/fhe-library/introduction/overview A Solidity framework for secure, private computation on encrypted data within smart contracts ## Overview The CoFHE library is a Solidity framework that enables secure, private computation on encrypted data within smart contracts. This library allows developers to perform operations on encrypted values without revealing the underlying plaintext data, preserving privacy while maintaining the transparency and trustlessness of blockchain technology. *** ## Core Components The FHE library consists of the following key components: ### 1. Encrypted Data Types The library supports multiple encrypted data types, each representing an encrypted version of a standard Solidity type: | Type | Description | Plaintext Equivalent | | ---------- | ---------------------------------- | -------------------- | | `ebool` | Encrypted boolean value | `bool` | | `euint8` | Encrypted 8-bit unsigned integer | `uint8` | | `euint16` | Encrypted 16-bit unsigned integer | `uint16` | | `euint32` | Encrypted 32-bit unsigned integer | `uint32` | | `euint64` | Encrypted 64-bit unsigned integer | `uint64` | | `euint128` | Encrypted 128-bit unsigned integer | `uint128` | | `eaddress` | Encrypted Ethereum address | `address` | ### 2. Encrypted Input Structures `ICofhe.sol` defines various input structures that enable secure data submission: * **`EncryptedInput`**: The core structure containing: * Ciphertext hash: A unique hash representing the encrypted data, used to reference it across the system. * Security zone parameter: Defines the trust context or boundary in which the encrypted data is valid and accessible. * Type indicator: Specifies the data type of the encrypted value (e.g. euint8, euint16) to ensure correct handling. * Cryptographic signature: A signature proving that the data and its metadata were generated and verified by an authorized entity. * **Type-specific input structures:** * `InEuint8`, `InEuint16`, `InEuint32` * `InEuint64`, `InEuint128` * `InEbool`, `InEaddress` *** ### 3. Core Functionality (FHE.sol) The `FHE` library provides a comprehensive set of operations for encrypted data manipulation: #### 1. Arithmetic Operations Enables basic math (FHE.add) directly on encrypted integers. #### 2. Bitwise Operations Supports bitwise logic (AND, OR, XOR, shifts) on encrypted data. #### 3. Comparison Operations Performs encrypted comparisons (eq, gt, lt, etc.) that return an eboolan encrypted boolean value that contains the result of the comparison. #### 4. Control Flow Includes conditionals like `select` to allowing encrypted branching without revealing decision paths. #### 5. Data and Access Management Provides functions for sealing outputs, decrypting values securely, and managing user access via permits, ensuring only authorized parties can access decrypted data. *** ### 4. Task Management The library interacts with a `TaskManager` contract that coordinates: * **Execution** of FHE operations * **Access control** for encrypted data * **Decryption request** processing # Quick Start Source: https://cofhe-docs.fhenix.zone/fhe-library/introduction/quick-start Set up your local development environment for building FHE smart contracts with CoFHE ## Overview This guide helps you set up your local development environment for building FHE (Fully Homomorphic Encryption) smart contracts with CoFHE. The Fhenix development starter kit provides everything you need to develop, test, and deploy FHE contracts both locally and on test networks. ## Supported Networks Fhenix CoFHE currently supports the following testnet networks: * **Ethereum Sepolia** - Main Ethereum testnet for FHE development * **Arbitrum Sepolia** - Layer 2 testnet with lower gas costs * **Base Sepolia** - Coinbase's Layer 2 testnet You can develop and test locally using mock contracts, then deploy to any of these supported testnets when ready. Production mainnet support is coming soon. The Fhenix development environment consists of several key components: * **cofhe-hardhat-starter**: Template Hardhat project to clone and use as a starting point * **cofhe-hardhat-plugin**: Hardhat plugin that deploys mock contracts and exposes utilities * **@cofhe/sdk**: JavaScript library for interacting with FHE contracts and the CoFHE coprocessor * **cofhe-mock-contracts**: Mock contracts that mimic CoFHE coprocessor behavior for local testing * **cofhe-contracts**: Solidity libraries and smart contracts for FHE operations ## Prerequisites Before starting, ensure you have: * **Node.js** (v20 or later) * **pnpm** (recommended package manager) * Basic familiarity with Hardhat and Solidity If you don't have pnpm installed, you can install it globally with `npm install -g pnpm`. ## Installation Clone the `cofhe-hardhat-starter` repository: ```bash theme={null} git clone https://github.com/fhenixprotocol/cofhe-hardhat-starter.git cd cofhe-hardhat-starter ``` You should now have the starter project in your local directory. Install all required dependencies using pnpm: ```bash theme={null} pnpm install ``` All dependencies are installed and ready to use. ## Project Structure The starter kit provides a well-organized directory structure to help you get started quickly: * **`contracts/`**: Contains all your Solidity smart contract source files * `Counter.sol`: An example FHE-enabled counter contract demonstrating basic FHE operations * **`test/`**: Houses tests that utilize `@cofhe/sdk` and utilities to interact with FHE-enabled contracts * **`tasks/`**: Tasks to deploy and interact with the Counter contract on Arbitrum Sepolia * **`hardhat.config.ts`**: Imports the CoFHE Hardhat plugin to deploy mock contracts ## Local Development Workflow ### Writing FHE Smart Contracts FHE contracts use special encrypted types and operations from the FHE library. Here's a basic example: ```solidity contracts/Counter.sol theme={null} import "@fhenixprotocol/cofhe-contracts/FHE.sol"; contract Counter { euint32 public count; // Encrypted uint32 function increment() public { count = FHE.add(count, FHE.asEuint32(1)); FHE.allowThis(count); FHE.allowSender(count); } } ``` Key FHE concepts you'll use: * `euint32`, `ebool` - Encrypted data types * `FHE.add`, `FHE.sub` - FHE operations on encrypted values * `FHE.allowThis`, `FHE.allowSender` - Access control management ### Testing with Mock Environment For rapid development, use the mock environment to test your contracts: ```bash theme={null} pnpm test ``` This runs your tests with mock FHE operations, allowing quick iteration without external dependencies. ```typescript test/Counter.test.ts theme={null} it('Should increment the counter', async function () { const { counter, bob } = await loadFixture(deployCounterFixture) // Check initial value const count = await counter.count() await mock_expectPlaintext(bob.provider, count, 0n) // Increment counter await counter.connect(bob).increment() // Check new value const count2 = await counter.count() await mock_expectPlaintext(bob.provider, count2, 1n) }) ``` ```typescript Full Client SDK Flow theme={null} it('Full Client SDK flow', async function () { const [bob] = await hre.ethers.getSigners() const CounterFactory = await hre.ethers.getContractFactory('Counter') const counter = await CounterFactory.connect(bob).deploy() // Create a connected client (batteries included) const client = await hre.cofhe.createClientWithBatteries(bob) // Encrypt a value const [encryptedInput] = await client .encryptInputs([Encryptable.uint32(5n)]) .onStep((step) => console.log(`Encrypt step - ${step}`)) .execute() // Use the encrypted input to reset the counter await counter.connect(bob).reset(encryptedInput) // Fetch the hash of the encrypted counter value const encryptedValue = await counter.count() // Decrypt the value (view decryption) const result = await client .decryptForView(encryptedValue, FheTypes.Uint32) .withPermit() .execute() // Check the decrypted result expect(result).equal(5n) }) ``` ### Deploying to Testnet When ready for more realistic testing, deploy to a Sepolia testnet: Create a `.env` file with your private key and RPC URLs: ```bash theme={null} PRIVATE_KEY=your_private_key_here SEPOLIA_RPC_URL=your_sepolia_rpc_url ARBITRUM_SEPOLIA_RPC_URL=your_arbitrum_sepolia_rpc_url ``` Never commit your `.env` file to version control. Add it to your `.gitignore` file. Deploy your contract to the testnet: ```bash Ethereum Sepolia theme={null} pnpm eth-sepolia:deploy-counter ``` ```bash Arbitrum Sepolia theme={null} pnpm arb-sepolia:deploy-counter ``` Use tasks to interact with your deployed contract: ```bash Ethereum Sepolia theme={null} pnpm eth-sepolia:increment-counter ``` ```bash Arbitrum Sepolia theme={null} pnpm arb-sepolia:increment-counter ``` ## Key Components ### cofhe-hardhat-plugin This plugin provides essential tools for developing FHE contracts: * **Network Configuration**: Automatically configures supported networks * **Testing Utilities**: Helpers for testing FHE contracts * **Mock Integration**: Sets up mock contracts for local testing ### Client SDK (`@cofhe/sdk`) The JavaScript library for working with FHE contracts: * **Encrypt/Decrypt**: Encrypt data to send to contracts and decrypt results * **Querying**: Fetch encrypted values from FHE contracts * **Authentication**: Uses Permits to authenticate connected users when requesting confidential data ### cofhe-mock-contracts These contracts provide mock implementations for FHE functionality: * Allows testing without actual FHE operations * Simulates the behavior of the real FHE environment * Stores plaintext values on-chain for testing purposes In the mock environment, gas costs are higher than in production due to the additional operations needed to simulate FHE behavior. This is especially noticeable when logging is enabled. ### cofhe-contracts Package of Solidity libraries and smart contracts for FHE operations: * **FHE.sol Library**: The only import you need to start using FHE functionality in your contracts * Complete API reference for all available functions and types ## Development Environments CoFHE supports multiple development environments: ### MOCK Environment * Fastest development cycle * No external dependencies * Uses mock contracts to simulate FHE operations ### Sepolia Testnet * Public testnet for real FHE operations * Requires ETH from the Sepolia faucet * Available on Ethereum Sepolia and Arbitrum Sepolia Environment detection happens automatically in most cases. When testing on the Hardhat network with mocks deployed (e.g., in unit tests), `hre.cofhe.createClientWithBatteries` will detect the Hardhat network and configure the client for the mock environment. When using tasks that connect to networks like `arb-sepolia`, the environment will automatically be set to testnet mode. This environment setting is crucial as it determines how the SDK handles encryption and decryption operations. ## Creating Custom Tasks You can create custom Hardhat tasks for your contracts in the `tasks/` directory: ```typescript tasks/increment-counter.ts theme={null} task("increment-counter", "Increment the counter on the deployed contract") .setAction(async (_, hre: HardhatRuntimeEnvironment) => { const { ethers, network } = hre; // Get the signer const [signer] = await ethers.getSigners(); console.log(`Using account: ${signer.address}`); await hre.cofhe.createClientWithBatteries(signer); // Interact with your contract // ... }) ``` ## Development Guidelines ### Start with Mock Environment Begin development using mock contracts for faster iteration and debugging. This approach eliminates external dependencies and provides immediate feedback. ### Test Thoroughly Write comprehensive tests covering both mock and testnet environments. Ensure your application behaves consistently across environments and handles edge cases properly. ### Permission Management Always set proper permissions with `FHE.allowThis()` and `FHE.allowSender()` to control which contracts and addresses can access encrypted data. Proper permission management is crucial for maintaining privacy and security in FHE applications. ### Error Handling Implement robust error handling for FHE operations. Be prepared for potential decryption delays and use appropriate retry mechanisms. ### Gas Optimization Be aware that FHE operations cost more gas than standard operations. Mock environments will simulate higher gas consumption than actual production environments. For accurate gas estimation, always test on the testnet before deployment. ## Next Steps Now that you have your development environment set up, you can: * Explore the [FHE Library documentation](/fhe-library) to learn about encrypted data types and operations * Review the [Counter contract example](https://github.com/FhenixProtocol/cofhe-hardhat-starter/blob/main/contracts/Counter.sol) to see FHE in action * Check out the [Client SDK documentation](/client-sdk/introduction/overview) for client-side integration * Learn about [access control mechanisms](/fhe-library) for managing encrypted data permissions ## Resources * [Fhenix Documentation](https://docs.fhenix.zone) * [Client SDK GitHub](https://github.com/FhenixProtocol/cofhesdk) * [CoFHE Contracts GitHub](https://github.com/FhenixProtocol/cofhe-contracts) * [cofhe-hardhat-starter GitHub](https://github.com/fhenixprotocol/cofhe-hardhat-starter) # CoFHE Errors Package Source: https://cofhe-docs.fhenix.zone/fhe-library/reference/cofhe-errors Decode Solidity smart contract errors and resolve 'execution reverted' messages instantly with @fhenixprotocol/cofhe-errors ## Overview When working with Solidity smart contracts, you've probably encountered cryptic error messages like: ``` Error: execution reverted: 0x118cdaa7 Transaction reverted without a reason string execution reverted ``` These errors are frustrating because you only get a 4-byte error selector (e.g., `0x118cdaa7`) with no human-readable message. The `@fhenixprotocol/cofhe-errors` package provides **instant, human-readable error decoding** for CoFHE smart contracts. * **53 custom errors** from **13 smart contracts** * Complete error signatures with parameter types * Source contract information * Fast CLI lookup tool * Programmatic JavaScript/TypeScript API ## Quick Start **No installation required!** Use `npx` to decode errors immediately: ```bash theme={null} npx cofhe-errors 0x118cdaa7 ``` **Output:** ``` Name: OwnableUnauthorizedAccount Selector: 0x118cdaa7 Signature: OwnableUnauthorizedAccount(address) Source: ACL Inputs: address ``` ## Installation (Optional) Only install if you need to import the error database programmatically in your code: ```bash npm theme={null} npm install @fhenixprotocol/cofhe-errors ``` ```bash yarn theme={null} yarn add @fhenixprotocol/cofhe-errors ``` ```bash pnpm theme={null} pnpm add @fhenixprotocol/cofhe-errors ``` ## CLI Usage ### Decode an Error Selector When you see "execution reverted: 0x...", decode it instantly: ```bash theme={null} npx cofhe-errors 0x118cdaa7 ``` ### Search Errors by Name Find errors when you know part of the error name: ```bash theme={null} npx cofhe-errors --name Permission ``` **Output:** ``` 0x4c40eccb PermissionInvalid_IssuerSignature (ACL) 0x8e143bf7 PermissionInvalid_RecipientSignature (ACL) 0xcbd3a966 PermissionInvalid_Disabled (ACL) 0xed0764a1 PermissionInvalid_Expired (ACL) ``` ### List All Known Errors Browse the complete error database: ```bash theme={null} npx cofhe-errors --list ``` ### JSON Output Get JSON output for scripting and automation: ```bash theme={null} npx cofhe-errors --json 0x118cdaa7 ``` **Output:** ```json theme={null} { "name": "OwnableUnauthorizedAccount", "selector": "0x118cdaa7", "signature": "OwnableUnauthorizedAccount(address)", "source": "ACL", "inputs": ["account"], "inputTypes": ["address"] } ``` ## Programmatic API ### JavaScript/TypeScript Usage ```javascript theme={null} const errors = require('@fhenixprotocol/cofhe-errors/errors.json'); // Find error by selector const error = errors.find(e => e.selector === '0x118cdaa7'); console.log(`Error: ${error.name}`); console.log(`Signature: ${error.signature}`); // Search by name const permissionErrors = errors.filter(e => e.name.toLowerCase().includes('permission') ); // Get all errors from a specific contract const aclErrors = errors.filter(e => e.source === 'ACL'); ``` ### TypeScript with Types ```typescript theme={null} import errors from '@fhenixprotocol/cofhe-errors/errors.json'; interface SolidityError { name: string; selector: string; signature: string; source: string; inputs: string[]; inputTypes: string[]; } const error = errors.find(e => e.selector === '0x118cdaa7') as SolidityError; ``` ### Error Monitoring Example ```javascript theme={null} const errors = require('@fhenixprotocol/cofhe-errors/errors.json'); // Decode error from blockchain event function decodeError(errorData) { const selector = errorData.slice(0, 10); // First 4 bytes const error = errors.find(e => e.selector === selector); if (error) { console.log(`Error: ${error.name}`); console.log(`Contract: ${error.source}`); console.log(`Signature: ${error.signature}`); } } ``` ## Error Coverage The package includes errors from the following contracts: | Contract | Error Count | | ----------------------- | ----------- | | TaskManager | 16 | | ACL | 8 | | SafeCast | 4 | | ERC1967Utils | 4 | | Errors | 4 | | Ownable2StepUpgradeable | 4 | | ECDSA | 3 | | Strings | 3 | | Common | 2 | | UUPSUpgradeable | 2 | | Address | 1 | | FHE | 1 | | PlaintextsStorage | 1 | ## How Error Selectors Work Solidity custom errors use a 4-byte selector computed as: ``` selector = keccak256("ErrorName(type1,type2,...)").slice(0, 4) ``` **Example:** ```solidity theme={null} error OwnableUnauthorizedAccount(address account); ``` Becomes: * Signature: `OwnableUnauthorizedAccount(address)` * Selector: `keccak256("OwnableUnauthorizedAccount(address)") = 0x118cdaa7...` * First 4 bytes: `0x118cdaa7` ## Next Steps * View the [complete error reference](/fhe-library/reference/cofhe-errors-reference) with all 53 errors * Learn about [common errors](/fhe-library/core-concepts/common-errors) and troubleshooting * Review [best practices](/fhe-library/introduction/best-practices) for FHE development # Error Reference Source: https://cofhe-docs.fhenix.zone/fhe-library/reference/cofhe-errors-reference Complete reference of all 53 CoFHE smart contract errors with selectors, signatures, and parameters ## Complete Error Database This page contains all 53 custom errors from the CoFHE smart contracts. Use this reference to understand error messages you encounter during development. Use the `@fhenixprotocol/cofhe-errors` package to decode errors instantly: ```bash theme={null} npx cofhe-errors 0x118cdaa7 ``` See the [CoFHE Errors Package](/fhe-library/reference/cofhe-errors) page for installation and usage instructions. ## Errors by Contract ### ACL (8 errors) Access Control List errors related to permissions and delegation. | Selector | Name | Signature | | ------------ | ------------------------------------- | ---------------------------------------- | | `0x30dc9203` | SenderCannotBeDelegateeAddress | `SenderCannotBeDelegateeAddress()` | | `0x3809a243` | DirectAllowForbidden | `DirectAllowForbidden(address)` | | `0x4c40eccb` | PermissionInvalid\_IssuerSignature | `PermissionInvalid_IssuerSignature()` | | `0x8e143bf7` | PermissionInvalid\_RecipientSignature | `PermissionInvalid_RecipientSignature()` | | `0xcbd3a966` | PermissionInvalid\_Disabled | `PermissionInvalid_Disabled()` | | `0xd0d25976` | SenderNotAllowed | `SenderNotAllowed(address)` | | `0xd1860468` | AlreadyDelegated | `AlreadyDelegated()` | | `0xed0764a1` | PermissionInvalid\_Expired | `PermissionInvalid_Expired()` | ### TaskManager (16 errors) Errors from the TaskManager contract that handles FHE operations. | Selector | Name | Signature | | ------------ | -------------------------- | --------------------------------------------- | | `0x24cbcf36` | InvalidSecurityZone | `InvalidSecurityZone(int32,int32,int32)` | | `0x2b0399d5` | TooManyInputs | `TooManyInputs(string,uint256,uint256)` | | `0x4d13139e` | ACLNotAllowed | `ACLNotAllowed(uint256,address)` | | `0x52b50ae1` | InvalidTypeOrSecurityZone | `InvalidTypeOrSecurityZone(string)` | | `0x70cf6554` | DecryptionResultNotReady | `DecryptionResultNotReady(uint256)` | | `0x7ba5ffb5` | InvalidSigner | `InvalidSigner(address,address)` | | `0x884a0e9d` | InvalidInputType | `InvalidInputType(uint8,uint8)` | | `0x8baa579f` | InvalidSignature | `InvalidSignature()` | | `0x91b4b378` | InvalidInputForFunction | `InvalidInputForFunction(string,uint8)` | | `0x98e08ab0` | RandomFunctionNotSupported | `RandomFunctionNotSupported()` | | `0x9a84351c` | InvalidInputsAmount | `InvalidInputsAmount(string,uint256,uint256)` | | `0xa974a0fe` | OnlyAggregatorAllowed | `OnlyAggregatorAllowed(address)` | | `0xb31612aa` | InvalidOperationInputs | `InvalidOperationInputs(string)` | | `0xcabe5ce4` | UnsupportedType | `UnsupportedType(uint256)` | | `0xd8aba367` | CofheIsUnavailable | `CofheIsUnavailable()` | | `0xe6c4247b` | InvalidAddress | `InvalidAddress()` | ### Ownable2StepUpgradeable (4 errors) Errors related to contract ownership and initialization. | Selector | Name | Signature | | ------------ | -------------------------- | ------------------------------------- | | `0x118cdaa7` | OwnableUnauthorizedAccount | `OwnableUnauthorizedAccount(address)` | | `0x1e4fbdf7` | OwnableInvalidOwner | `OwnableInvalidOwner(address)` | | `0xd7e6bcf8` | NotInitializing | `NotInitializing()` | | `0xf92ee8a9` | InvalidInitialization | `InvalidInitialization()` | ### ERC1967Utils (4 errors) Errors from the ERC1967 proxy utilities. | Selector | Name | Signature | | ------------ | ---------------------------- | --------------------------------------- | | `0x4c9c8ce3` | ERC1967InvalidImplementation | `ERC1967InvalidImplementation(address)` | | `0x62e77ba2` | ERC1967InvalidAdmin | `ERC1967InvalidAdmin(address)` | | `0x64ced0ec` | ERC1967InvalidBeacon | `ERC1967InvalidBeacon(address)` | | `0xb398979f` | ERC1967NonPayable | `ERC1967NonPayable()` | ### SafeCast (4 errors) Errors from safe type casting operations. | Selector | Name | Signature | | ------------ | ------------------------------ | ----------------------------------------------- | | `0x24775e06` | SafeCastOverflowedUintToInt | `SafeCastOverflowedUintToInt(uint256)` | | `0x327269a7` | SafeCastOverflowedIntDowncast | `SafeCastOverflowedIntDowncast(uint8,int256)` | | `0x6dfcc650` | SafeCastOverflowedUintDowncast | `SafeCastOverflowedUintDowncast(uint8,uint256)` | | `0xa8ce4432` | SafeCastOverflowedIntToUint | `SafeCastOverflowedIntToUint(int256)` | ### Errors (4 errors) General error utilities. | Selector | Name | Signature | | ------------ | ------------------- | -------------------------------------- | | `0x42b01bce` | MissingPrecompile | `MissingPrecompile(address)` | | `0xb06ebf3d` | FailedDeployment | `FailedDeployment()` | | `0xcf479181` | InsufficientBalance | `InsufficientBalance(uint256,uint256)` | | `0xd6bda275` | FailedCall | `FailedCall()` | ### ECDSA (3 errors) Errors from ECDSA signature verification. | Selector | Name | Signature | | ------------ | --------------------------- | -------------------------------------- | | `0xd78bce0c` | ECDSAInvalidSignatureS | `ECDSAInvalidSignatureS(bytes32)` | | `0xf645eedf` | ECDSAInvalidSignature | `ECDSAInvalidSignature()` | | `0xfce698f7` | ECDSAInvalidSignatureLength | `ECDSAInvalidSignatureLength(uint256)` | ### Strings (3 errors) Errors from string utility operations. | Selector | Name | Signature | | ------------ | ---------------------------- | ----------------------------------------------- | | `0x1d15ae44` | StringsInvalidAddressFormat | `StringsInvalidAddressFormat()` | | `0x94e2737e` | StringsInvalidChar | `StringsInvalidChar()` | | `0xe22e27eb` | StringsInsufficientHexLength | `StringsInsufficientHexLength(uint256,uint256)` | ### Common (2 errors) Common utility errors. | Selector | Name | Signature | | ------------ | ----------------------- | -------------------------------- | | `0x01d4fab6` | InvalidHexCharacter | `InvalidHexCharacter(bytes1)` | | `0x8f568bf8` | SecurityZoneOutOfBounds | `SecurityZoneOutOfBounds(int32)` | ### UUPSUpgradeable (2 errors) Errors from UUPS proxy upgradeability. | Selector | Name | Signature | | ------------ | ---------------------------- | --------------------------------------- | | `0xaa1d49a4` | UUPSUnsupportedProxiableUUID | `UUPSUnsupportedProxiableUUID(bytes32)` | | `0xe07c8dba` | UUPSUnauthorizedCallContext | `UUPSUnauthorizedCallContext()` | ### Address (1 error) Address utility errors. | Selector | Name | Signature | | ------------ | ---------------- | --------------------------- | | `0x9996b315` | AddressEmptyCode | `AddressEmptyCode(address)` | ### FHE (1 error) Core FHE operation errors. | Selector | Name | Signature | | ------------ | --------------------- | ------------------------------------ | | `0x67cf3071` | InvalidEncryptedInput | `InvalidEncryptedInput(uint8,uint8)` | ### PlaintextsStorage (1 error) Errors from the plaintext storage contract. | Selector | Name | Signature | | ------------ | ---------------------- | --------------------------------- | | `0xdce3ec0a` | OnlyTaskManagerAllowed | `OnlyTaskManagerAllowed(address)` | *** ## All Errors (Alphabetical) | Selector | Name | Contract | Signature | | ------------ | ------------------------------------- | ----------------------- | ----------------------------------------------- | | `0x4d13139e` | ACLNotAllowed | TaskManager | `ACLNotAllowed(uint256,address)` | | `0x9996b315` | AddressEmptyCode | Address | `AddressEmptyCode(address)` | | `0xd1860468` | AlreadyDelegated | ACL | `AlreadyDelegated()` | | `0xd8aba367` | CofheIsUnavailable | TaskManager | `CofheIsUnavailable()` | | `0x70cf6554` | DecryptionResultNotReady | TaskManager | `DecryptionResultNotReady(uint256)` | | `0x3809a243` | DirectAllowForbidden | ACL | `DirectAllowForbidden(address)` | | `0x62e77ba2` | ERC1967InvalidAdmin | ERC1967Utils | `ERC1967InvalidAdmin(address)` | | `0x64ced0ec` | ERC1967InvalidBeacon | ERC1967Utils | `ERC1967InvalidBeacon(address)` | | `0x4c9c8ce3` | ERC1967InvalidImplementation | ERC1967Utils | `ERC1967InvalidImplementation(address)` | | `0xb398979f` | ERC1967NonPayable | ERC1967Utils | `ERC1967NonPayable()` | | `0xf645eedf` | ECDSAInvalidSignature | ECDSA | `ECDSAInvalidSignature()` | | `0xfce698f7` | ECDSAInvalidSignatureLength | ECDSA | `ECDSAInvalidSignatureLength(uint256)` | | `0xd78bce0c` | ECDSAInvalidSignatureS | ECDSA | `ECDSAInvalidSignatureS(bytes32)` | | `0xd6bda275` | FailedCall | Errors | `FailedCall()` | | `0xb06ebf3d` | FailedDeployment | Errors | `FailedDeployment()` | | `0xcf479181` | InsufficientBalance | Errors | `InsufficientBalance(uint256,uint256)` | | `0xe6c4247b` | InvalidAddress | TaskManager | `InvalidAddress()` | | `0x67cf3071` | InvalidEncryptedInput | FHE | `InvalidEncryptedInput(uint8,uint8)` | | `0x01d4fab6` | InvalidHexCharacter | Common | `InvalidHexCharacter(bytes1)` | | `0xf92ee8a9` | InvalidInitialization | Ownable2StepUpgradeable | `InvalidInitialization()` | | `0x91b4b378` | InvalidInputForFunction | TaskManager | `InvalidInputForFunction(string,uint8)` | | `0x884a0e9d` | InvalidInputType | TaskManager | `InvalidInputType(uint8,uint8)` | | `0x9a84351c` | InvalidInputsAmount | TaskManager | `InvalidInputsAmount(string,uint256,uint256)` | | `0xb31612aa` | InvalidOperationInputs | TaskManager | `InvalidOperationInputs(string)` | | `0x24cbcf36` | InvalidSecurityZone | TaskManager | `InvalidSecurityZone(int32,int32,int32)` | | `0x8baa579f` | InvalidSignature | TaskManager | `InvalidSignature()` | | `0x7ba5ffb5` | InvalidSigner | TaskManager | `InvalidSigner(address,address)` | | `0x52b50ae1` | InvalidTypeOrSecurityZone | TaskManager | `InvalidTypeOrSecurityZone(string)` | | `0x42b01bce` | MissingPrecompile | Errors | `MissingPrecompile(address)` | | `0xd7e6bcf8` | NotInitializing | Ownable2StepUpgradeable | `NotInitializing()` | | `0xa974a0fe` | OnlyAggregatorAllowed | TaskManager | `OnlyAggregatorAllowed(address)` | | `0xdce3ec0a` | OnlyTaskManagerAllowed | PlaintextsStorage | `OnlyTaskManagerAllowed(address)` | | `0x1e4fbdf7` | OwnableInvalidOwner | Ownable2StepUpgradeable | `OwnableInvalidOwner(address)` | | `0x118cdaa7` | OwnableUnauthorizedAccount | Ownable2StepUpgradeable | `OwnableUnauthorizedAccount(address)` | | `0xcbd3a966` | PermissionInvalid\_Disabled | ACL | `PermissionInvalid_Disabled()` | | `0xed0764a1` | PermissionInvalid\_Expired | ACL | `PermissionInvalid_Expired()` | | `0x4c40eccb` | PermissionInvalid\_IssuerSignature | ACL | `PermissionInvalid_IssuerSignature()` | | `0x8e143bf7` | PermissionInvalid\_RecipientSignature | ACL | `PermissionInvalid_RecipientSignature()` | | `0x98e08ab0` | RandomFunctionNotSupported | TaskManager | `RandomFunctionNotSupported()` | | `0x327269a7` | SafeCastOverflowedIntDowncast | SafeCast | `SafeCastOverflowedIntDowncast(uint8,int256)` | | `0xa8ce4432` | SafeCastOverflowedIntToUint | SafeCast | `SafeCastOverflowedIntToUint(int256)` | | `0x6dfcc650` | SafeCastOverflowedUintDowncast | SafeCast | `SafeCastOverflowedUintDowncast(uint8,uint256)` | | `0x24775e06` | SafeCastOverflowedUintToInt | SafeCast | `SafeCastOverflowedUintToInt(uint256)` | | `0x8f568bf8` | SecurityZoneOutOfBounds | Common | `SecurityZoneOutOfBounds(int32)` | | `0x30dc9203` | SenderCannotBeDelegateeAddress | ACL | `SenderCannotBeDelegateeAddress()` | | `0xd0d25976` | SenderNotAllowed | ACL | `SenderNotAllowed(address)` | | `0x1d15ae44` | StringsInvalidAddressFormat | Strings | `StringsInvalidAddressFormat()` | | `0x94e2737e` | StringsInvalidChar | Strings | `StringsInvalidChar()` | | `0xe22e27eb` | StringsInsufficientHexLength | Strings | `StringsInsufficientHexLength(uint256,uint256)` | | `0x2b0399d5` | TooManyInputs | TaskManager | `TooManyInputs(string,uint256,uint256)` | | `0xaa1d49a4` | UUPSUnsupportedProxiableUUID | UUPSUpgradeable | `UUPSUnsupportedProxiableUUID(bytes32)` | | `0xe07c8dba` | UUPSUnauthorizedCallContext | UUPSUpgradeable | `UUPSUnauthorizedCallContext()` | | `0xcabe5ce4` | UnsupportedType | TaskManager | `UnsupportedType(uint256)` | ## Next Steps * Learn how to use the [CoFHE Errors Package](/fhe-library/reference/cofhe-errors) CLI and API * Review [common errors](/fhe-library/core-concepts/common-errors) troubleshooting guide * Check [best practices](/fhe-library/introduction/best-practices) for FHE development # Access Control Source: https://cofhe-docs.fhenix.zone/fhe-library/reference/fhe-sol/access-control allow, allowPublic, allowThis, allowSender, allowTransient, isAllowed, isPubliclyAllowed All access control functions work on `ebool | euint8 | euint16 | euint32 | euint64 | euint128 | eaddress`. *** ### allow Grants permission to a specific address. ```solidity theme={null} FHE.allow(encryptedValue, userAddress); ``` ### allowThis Grants permission to the current contract (`address(this)`). Call `allowThis` after modifying encrypted state variables so the contract can use them in future transactions. ```solidity theme={null} euint32 counter = FHE.add(counter, FHE.asEuint32(1)); FHE.allowThis(counter); ``` ### allowPublic Grants public permission — anyone can request decryption off-chain via `decryptForTx` without a permit. Once called, the value can be decrypted by anyone. Only use this when you intend to reveal the value publicly (e.g., after an auction closes or when unwrapping tokens). ```solidity theme={null} FHE.allowPublic(highestBid); ``` ### allowSender Grants permission to `msg.sender`. ```solidity theme={null} FHE.allowSender(encryptedValue); ``` ### allowTransient Grants temporary permission to a specific address for the current transaction only. ```solidity theme={null} FHE.allowTransient(encryptedValue, otherContract); ``` *** ### isAllowed Checks if an address has permission. ```solidity theme={null} bool hasAccess = FHE.isAllowed(encryptedValue, userAddress); ``` ### isPubliclyAllowed Checks if the value has been granted public access via `allowPublic`. ```solidity theme={null} bool isPublic = FHE.isPubliclyAllowed(encryptedValue); ``` # Arithmetic Operations Source: https://cofhe-docs.fhenix.zone/fhe-library/reference/fhe-sol/arithmetic add, sub, mul, div, rem, square — encrypted math operations All arithmetic operations work on `euint8 | euint16 | euint32 | euint64 | euint128`. Both operands must be the same type. ### add ```solidity theme={null} euint32 sum = FHE.add(a, b); ``` ### sub ```solidity theme={null} euint32 diff = FHE.sub(a, b); ``` ### mul ```solidity theme={null} euint32 product = FHE.mul(a, b); ``` ### div Division by zero will cause the operation to revert. ```solidity theme={null} euint32 quotient = FHE.div(a, b); ``` ### rem ```solidity theme={null} euint32 remainder = FHE.rem(a, b); ``` ### square ```solidity theme={null} euint32 squared = FHE.square(a); ``` # Bindings (Dot Notation) Source: https://cofhe-docs.fhenix.zone/fhe-library/reference/fhe-sol/bindings Use encrypted types with dot notation instead of FHE.* prefix The FHE library provides binding libraries that enable dot notation for all operations. Import them alongside FHE.sol — no extra setup required. ```solidity theme={null} // Without bindings euint8 sum = FHE.add(a, b); // With bindings euint8 sum = a.add(b); ``` ## Full Example ```solidity theme={null} InEuint8 encryptedInputA; InEuint8 encryptedInputB; euint8 a = FHE.asEuint8(encryptedInputA); euint8 b = FHE.asEuint8(encryptedInputB); // Arithmetic euint8 sum = a.add(b); euint8 diff = a.sub(b); euint8 product = a.mul(b); euint8 quotient = a.div(b); euint8 remainder = a.rem(b); euint8 squared = a.square(); // Bitwise euint8 bitwiseAnd = a.and(b); euint8 bitwiseOr = a.or(b); euint8 bitwiseXor = a.xor(b); euint8 bitwiseNot = a.not(); euint8 shiftLeft = a.shl(b); euint8 shiftRight = a.shr(b); euint8 rotateLeft = a.rol(b); euint8 rotateRight = a.ror(b); // Comparison ebool isEqual = a.eq(b); ebool isNotEqual = a.ne(b); ebool isLessThan = a.lt(b); ebool isLessEqual = a.lte(b); ebool isGreaterThan = a.gt(b); ebool isGreaterEqual = a.gte(b); // Min/Max euint8 minimum = a.min(b); euint8 maximum = a.max(b); // Type conversion ebool converted = a.toBool(); euint16 toU16 = a.toU16(); euint32 toU32 = a.toU32(); euint64 toU64 = a.toU64(); euint128 toU128 = a.toU128(); // Utility bool initialized = a.isInitialized(); bytes32 handle = a.unwrap(); // Access control a.allow(address); a.allowThis(); a.allowPublic(); a.allowSender(); a.allowTransient(address); bool hasAccess = a.isAllowed(address); ``` # Bitwise Operations Source: https://cofhe-docs.fhenix.zone/fhe-library/reference/fhe-sol/bitwise and, or, xor, not, shl, shr, rol, ror — encrypted bitwise operations Bitwise logic operations work on `ebool | euint8 | euint16 | euint32 | euint64 | euint128`. Shift and rotate operations work on `euint8 | euint16 | euint32 | euint64 | euint128` only. ## Logic ### and ```solidity theme={null} euint8 result = FHE.and(a, b); ebool result = FHE.and(x, y); ``` ### or ```solidity theme={null} euint8 result = FHE.or(a, b); ebool result = FHE.or(x, y); ``` ### xor ```solidity theme={null} euint8 result = FHE.xor(a, b); ebool result = FHE.xor(x, y); ``` ### not ```solidity theme={null} euint8 result = FHE.not(a); ebool result = FHE.not(x); ``` ## Shift & Rotate ### shl Shift left. ```solidity theme={null} euint32 result = FHE.shl(value, shiftAmount); ``` ### shr Shift right. ```solidity theme={null} euint32 result = FHE.shr(value, shiftAmount); ``` ### rol Rotate left. ```solidity theme={null} euint32 result = FHE.rol(value, rotateAmount); ``` ### ror Rotate right. ```solidity theme={null} euint32 result = FHE.ror(value, rotateAmount); ``` # Comparison Operations Source: https://cofhe-docs.fhenix.zone/fhe-library/reference/fhe-sol/comparison eq, ne, lt, lte, gt, gte, min, max — encrypted comparison and selection All comparison operations return `ebool`. Equality checks (`eq`, `ne`) work on all encrypted types including `eaddress`. Ordering checks (`lt`, `lte`, `gt`, `gte`) and `min`/`max` work on `euint8 | euint16 | euint32 | euint64 | euint128`. ## Equality ### eq ```solidity theme={null} ebool isEqual = FHE.eq(a, b); ebool isEqual = FHE.eq(address1, address2); // eaddress supported ``` ### ne ```solidity theme={null} ebool isNotEqual = FHE.ne(a, b); ``` ## Ordering ### lt ```solidity theme={null} ebool isLess = FHE.lt(a, b); ``` ### lte ```solidity theme={null} ebool isLessOrEqual = FHE.lte(a, b); ``` ### gt ```solidity theme={null} ebool isGreater = FHE.gt(a, b); ``` ### gte ```solidity theme={null} ebool isGreaterOrEqual = FHE.gte(a, b); ``` ## Min / Max ### min ```solidity theme={null} euint32 minimum = FHE.min(a, b); ``` ### max ```solidity theme={null} euint32 maximum = FHE.max(a, b); ``` ## Select Conditionally selects between two encrypted values based on an encrypted boolean. Works on all encrypted types including `eaddress`. Use `select` instead of `if/else` statements when working with encrypted values. Conditional branching doesn't work with encrypted data. ```solidity theme={null} euint8 result = FHE.select(condition, a, b); eaddress result = FHE.select(condition, addr1, addr2); ``` # Decryption Source: https://cofhe-docs.fhenix.zone/fhe-library/reference/fhe-sol/decryption publishDecryptResult, verifyDecryptResult, getDecryptResult — on-chain decryption result handling Decryption in CoFHE is a two-phase process: 1. **On-chain**: Mark a value as decryptable with `FHE.allowPublic(ctHash)` 2. **Off-chain**: Client calls `decryptForTx(ctHash)` via the SDK to get `{ plaintext, signature }` 3. **On-chain**: Submit the result via `publishDecryptResult` or `verifyDecryptResult` *** ## Publishing Results ### publishDecryptResult Publishes a decrypted result on-chain by verifying the Threshold Network signature. The plaintext is stored and can be read via `getDecryptResultSafe`. Ciphertext handle Decrypted value from `decryptForTx` Threshold Network signature Reverts if the signature is invalid. The value must have been granted public access via `allowPublic` before decryption was requested off-chain. ```solidity theme={null} FHE.publishDecryptResult(encryptedBid, bidPlaintext, bidSignature); ``` ### publishDecryptResultBatch Publishes multiple results in a single call. Typed overloads exist for every encrypted type: | Handle array type | Result array type | | ----------------- | ----------------- | | `ebool[]` | `bool[]` | | `euint8[]` | `uint8[]` | | `euint16[]` | `uint16[]` | | `euint32[]` | `uint32[]` | | `euint64[]` | `uint64[]` | | `euint128[]` | `uint128[]` | | `eaddress[]` | `address[]` | ```solidity theme={null} euint64[] memory handles = new euint64[](2); handles[0] = encryptedBid1; handles[1] = encryptedBid2; uint64[] memory values = new uint64[](2); values[0] = bid1Plaintext; values[1] = bid2Plaintext; bytes[] memory sigs = new bytes[](2); sigs[0] = sig1; sigs[1] = sig2; FHE.publishDecryptResultBatch(handles, values, sigs); ``` The compiler resolves the overload from the handle array's element type. There is also a "raw" overload that takes `uint256[]` ctHashes if you only have raw handles to hand. *** ## Verifying Results ### verifyDecryptResult Verifies a Threshold Network signature **without** storing the plaintext on-chain. Returns `bool`. Use this when you only need to act on the decrypted value within the transaction without making it permanently public. ```solidity theme={null} require( FHE.verifyDecryptResult(encryptedAmount, amount, signature), "Invalid decrypt proof" ); ``` ### verifyDecryptResultSafe Like `verifyDecryptResult`, but returns `false` instead of reverting on invalid signature. ```solidity theme={null} bool valid = FHE.verifyDecryptResultSafe(encryptedAmount, amount, signature); if (valid) { // proceed } ``` ### verifyDecryptResultBatch Verifies multiple signatures in a single call. Returns `true` only if every entry is valid; reverts if any signature fails to recover (consistent with the single-entry `verifyDecryptResult`). Typed overloads exist for every encrypted type — same handle/result type table as [`publishDecryptResultBatch`](#publishdecryptresultbatch). ```solidity theme={null} bool allValid = FHE.verifyDecryptResultBatch(handles, values, sigs); ``` ### verifyDecryptResultBatchSafe Returns a `bool[]` indicating which entries are valid instead of reverting. Typed overloads cover every encrypted type — same handle/result type table as [`publishDecryptResultBatch`](#publishdecryptresultbatch). ```solidity theme={null} bool[] memory results = FHE.verifyDecryptResultBatchSafe(handles, values, sigs); ``` The batch verify functions (`verifyDecryptResultBatch`, `verifyDecryptResultBatchSafe`) were added in `cofhe-contracts@v0.1.2`. Earlier versions only exposed the single-entry `verifyDecryptResult` / `verifyDecryptResultSafe`. *** ## Reading Results ### getDecryptResult Retrieves a published decryption result. Reverts if not yet available. ```solidity theme={null} uint256 result = FHE.getDecryptResult(ctHash); ``` ### getDecryptResultSafe Non-reverting version. Returns a tuple with the result and a boolean flag. ```solidity theme={null} (uint256 result, bool decrypted) = FHE.getDecryptResultSafe(ctHash); if (decrypted) { // use result } ``` # FHE.sol Overview Source: https://cofhe-docs.fhenix.zone/fhe-library/reference/fhe-sol/overview Encrypted data types, imports, and general usage of the FHE Solidity library The FHE library provides functions for working with Fully Homomorphic Encryption (FHE) in Solidity smart contracts. It enables computations on encrypted data without decrypting it, ensuring privacy throughout your contract's execution. ## Import ```solidity theme={null} import '@fhenixprotocol/cofhe-contracts/FHE.sol'; ``` All functions are prefixed with `FHE.` when called. For example: `FHE.add(a, b)` or `FHE.allowPublic(value)`. ## Encrypted Data Types | Type | Description | | ---------- | ---------------------------------- | | `ebool` | Encrypted boolean value | | `euint8` | Encrypted 8-bit unsigned integer | | `euint16` | Encrypted 16-bit unsigned integer | | `euint32` | Encrypted 32-bit unsigned integer | | `euint64` | Encrypted 64-bit unsigned integer | | `euint128` | Encrypted 128-bit unsigned integer | | `eaddress` | Encrypted Ethereum address | ## Input Types Each encrypted type has a corresponding input struct used when receiving encrypted data from the client SDK: | Input Type | Encrypted Type | | ------------ | -------------- | | `InEbool` | `ebool` | | `InEuint8` | `euint8` | | `InEuint16` | `euint16` | | `InEuint32` | `euint32` | | `InEuint64` | `euint64` | | `InEuint128` | `euint128` | | `InEaddress` | `eaddress` | ## Security Considerations 1. **Initialization**: All FHE functions check if their inputs are initialized and set them to 0 if not. 2. **Decryption**: Decryption is a two-phase process — mark values with `allowPublic` on-chain, decrypt off-chain via the Client SDK, then publish/verify the result with `publishDecryptResult` or `verifyDecryptResult`. Only reveal values when absolutely necessary. 3. **Security Zones**: Some functions accept a `securityZone` parameter to isolate different encrypted computations. FHE operations can only be performed between ciphertexts that share the same security zone. 4. **Access Control**: The library provides fine-grained access control through the `allow*` functions. Always set proper permissions before accessing encrypted values. 5. **Type Safety**: Ensure encrypted values use compatible types when performing operations. Type mismatches will cause errors. # Type Conversion Source: https://cofhe-docs.fhenix.zone/fhe-library/reference/fhe-sol/type-conversion Convert between plaintext, encrypted input structs, and encrypted types ## From Plaintext (Trivial Encryption) Convert plaintext values to encrypted types. An optional `securityZone` parameter isolates encrypted computations. ### asEbool ```solidity theme={null} ebool encrypted = FHE.asEbool(true); ebool encryptedWithZone = FHE.asEbool(true, 1); ``` ### asEuint8 / asEuint16 / asEuint32 / asEuint64 / asEuint128 ```solidity theme={null} euint8 encrypted = FHE.asEuint8(42); euint16 encrypted = FHE.asEuint16(1000); euint32 encrypted = FHE.asEuint32(50000); euint64 encrypted = FHE.asEuint64(1000000000); euint128 encrypted = FHE.asEuint128(1000000000000000000); // With security zone euint32 encrypted = FHE.asEuint32(50000, 1); ``` ### asEaddress ```solidity theme={null} eaddress encrypted = FHE.asEaddress(0x1234567890123456789012345678901234567890); ``` *** ## From Encrypted Input Structs Convert client-side encrypted inputs (received as function parameters) into encrypted types. ```solidity theme={null} function deposit(InEuint64 calldata encryptedAmount) external { euint64 amount = FHE.asEuint64(encryptedAmount); } ``` All input types follow the same pattern: ```solidity theme={null} ebool val = FHE.asEbool(inEbool); euint8 val = FHE.asEuint8(inEuint8); euint16 val = FHE.asEuint16(inEuint16); euint32 val = FHE.asEuint32(inEuint32); euint64 val = FHE.asEuint64(inEuint64); euint128 val = FHE.asEuint128(inEuint128); eaddress val = FHE.asEaddress(inEaddress); ``` *** ## Between Encrypted Types Cast any encrypted type to another. The full cross-cast matrix is supported: ```solidity theme={null} // ebool from integers ebool result = FHE.asEbool(euint8Value); ebool result = FHE.asEbool(euint16Value); ebool result = FHE.asEbool(euint32Value); ebool result = FHE.asEbool(euint64Value); ebool result = FHE.asEbool(euint128Value); ebool result = FHE.asEbool(eaddressValue); // euint8 from other types euint8 result = FHE.asEuint8(eboolValue); euint8 result = FHE.asEuint8(euint16Value); euint8 result = FHE.asEuint8(euint32Value); euint8 result = FHE.asEuint8(euint64Value); euint8 result = FHE.asEuint8(euint128Value); euint8 result = FHE.asEuint8(eaddressValue); // euint16 from other types euint16 result = FHE.asEuint16(eboolValue); euint16 result = FHE.asEuint16(euint8Value); euint16 result = FHE.asEuint16(euint32Value); euint16 result = FHE.asEuint16(euint64Value); euint16 result = FHE.asEuint16(euint128Value); euint16 result = FHE.asEuint16(eaddressValue); // euint32 from other types euint32 result = FHE.asEuint32(eboolValue); euint32 result = FHE.asEuint32(euint8Value); euint32 result = FHE.asEuint32(euint16Value); euint32 result = FHE.asEuint32(euint64Value); euint32 result = FHE.asEuint32(euint128Value); euint32 result = FHE.asEuint32(eaddressValue); // euint64 from other types euint64 result = FHE.asEuint64(eboolValue); euint64 result = FHE.asEuint64(euint8Value); euint64 result = FHE.asEuint64(euint16Value); euint64 result = FHE.asEuint64(euint32Value); euint64 result = FHE.asEuint64(euint128Value); euint64 result = FHE.asEuint64(eaddressValue); // euint128 from other types euint128 result = FHE.asEuint128(eboolValue); euint128 result = FHE.asEuint128(euint8Value); euint128 result = FHE.asEuint128(euint16Value); euint128 result = FHE.asEuint128(euint32Value); euint128 result = FHE.asEuint128(euint64Value); euint128 result = FHE.asEuint128(eaddressValue); // eaddress from euint128 eaddress result = FHE.asEaddress(euint128Value); ``` # Utility Functions Source: https://cofhe-docs.fhenix.zone/fhe-library/reference/fhe-sol/utility isInitialized, unwrap, wrap — handle inspection and conversion utilities ## isInitialized Checks whether an encrypted value has been initialized (i.e., is not a zero/empty handle). Works on all encrypted types. ```solidity theme={null} if (FHE.isInitialized(encryptedBalance)) { // safe to use } ``` ## unwrap Extracts the raw `bytes32` handle from a typed encrypted value. ```solidity theme={null} bytes32 handle = FHE.unwrap(encryptedValue); ``` ## wrap Wraps a raw `bytes32` handle into a typed encrypted value. One function per type: ```solidity theme={null} ebool val = FHE.wrapEbool(handle); euint8 val = FHE.wrapEuint8(handle); euint16 val = FHE.wrapEuint16(handle); euint32 val = FHE.wrapEuint32(handle); euint64 val = FHE.wrapEuint64(handle); euint128 val = FHE.wrapEuint128(handle); eaddress val = FHE.wrapEaddress(handle); ``` Use `wrap` when you have a raw handle from storage or an event and need to convert it back to a typed encrypted value. **Renamed in `cofhe-contracts@v0.1.3`.** These functions used to be exposed as `FHE.asEbool(bytes32)`, `FHE.asEuint*(bytes32)`, and `FHE.asEaddress(bytes32)`. They were renamed to `wrap*` to remove a Solidity overload ambiguity with `asEuint*(0)` (where the integer literal `0` could resolve to either the plaintext-trivial-encryption overload or the raw-handle overload). If you're upgrading from `<=0.1.2`, search for `FHE.asE*()` call sites and rewrite them as `FHE.wrap*`. ## Random Number Generation Generate random encrypted values. An optional `securityZone` parameter is supported. ```solidity theme={null} euint8 rand = FHE.randomEuint8(); euint16 rand = FHE.randomEuint16(); euint32 rand = FHE.randomEuint32(); euint64 rand = FHE.randomEuint64(); euint128 rand = FHE.randomEuint128(); // With security zone euint32 rand = FHE.randomEuint32(1); ``` # NEO - Fhenix AI Assistant Source: https://cofhe-docs.fhenix.zone/get-started/build-with-ai/ai-assistant > **AI Training Materials for Fully Homomorphic Encryption (FHE) Smart Contract Development using Fhenix** NEO is a comprehensive training resource that helps you transform AI assistants (Claude, ChatGPT, Gemini, etc.) into expert FHE developers. By loading NEO's reference materials into your AI assistant, you'll get expert-level guidance on building confidential smart contracts with Fhenix. ## Getting Started 1. **Clone or Access the Repository** Visit the [FHE Assistant repository](https://github.com/marronjo/fhe-assistant) on GitHub 2. **Load Core.md into Your AI** Copy the contents of `core.md` into your AI assistant's context window 3. **Start Building** Use the proven prompts above to generate FHE-compatible smart contracts ### Loading into AI Platforms **Claude Code:** ``` claude "Read the fhe-assistant/core.md file and help me build FHE smart contracts using these patterns" ``` **Other AI Platforms (ChatGPT, Gemini, etc.):** Copy-paste the `core.md` file and say: *"This is FHE reference material. Help me build encrypted smart contracts."* ## Proven AI Prompts Once you've loaded NEO's reference materials, try these proven prompts: ### Code Generation * *"Build me a \[voting/auction/gaming] contract using FHE patterns"* * *"Create an encrypted token with private balances"* ### Code Review * *"Review this FHE contract against the security checklist"* * *"Fix this contract that has FHE access control errors"* ### Learning * *"Explain FHE access control and show me working examples"* * *"Why can't I use ebool in if statements? Show me the right way"* ## What's Inside The NEO repository includes: **📚 `core.md`** → Comprehensive FHE library reference (EVERYTHING YOU NEED) **📖 `README.md`** → Quick start guide ### Core.md Contents The comprehensive reference file includes: * 🔢 **All encrypted data types** - ebool, euint8-128, eaddress * ➕ **Complete operation reference** - Arithmetic, comparison, logical operations * 🔐 **Access control patterns** - FHE.allow\*, critical for security * 🔄 **Conditional operations** - FHE.select - the only way to use ebool * 🔓 **Decryption workflows** - Multi-transaction patterns * 🚨 **Common mistakes** - Debugging guide and pitfalls to avoid * ✅ **Working code templates** - Ready-to-use examples ## Why Use NEO? Building with FHE requires understanding specific patterns and security considerations that differ from standard Solidity development. NEO provides: * **Complete Reference** - All FHE types and operations in one place * **Security Patterns** - Access control and encryption best practices * **Working Examples** - Real code templates you can use immediately * **Common Pitfalls** - Learn from mistakes others have made * **Fhenix-Specific** - All patterns target Fhenix protocol directly # Build with AI Source: https://cofhe-docs.fhenix.zone/get-started/build-with-ai/build-with-ai Learn how to use AI coding assistants effectively when building with Fhenix # Developer's Guide to Building with AI > Learn practical AI prompting techniques to build confidential smart contracts and integrate FHE into your development workflow. This guide helps developers leverage AI tools effectively when building with Fhenix. Whether you're using Cursor, GitHub Copilot, or other AI assistants, these strategies will help you get better results and integrate AI smoothly into your FHE development process. ## Understanding Context Windows ### Why Context Matters AI coding assistants have what's called a "context window" - the amount of text they can "see" and consider when generating responses. Think of it as the AI's working memory: * Most modern AI assistants can process thousands of tokens (roughly 4-5 words per token) * Everything you share and everything the AI responds with consumes this limited space * Once the context window fills up, parts of your conversational history may be lost This is why providing relevant context upfront is crucial - the AI can only work with what it can "see" in its current context window. ### Optimizing for Context Windows To get the most out of AI assistants when building with Fhenix: * **Prioritize relevant information**: Focus on sharing the most important details about your FHE use case first * **Remove unnecessary content**: Avoid pasting irrelevant code or documentation * **Structure your requests**: Use clear sections and formatting to make information easy to process * **Reference Fhenix docs**: Share specific documentation links or code snippets relevant to your task * **Create a project summary**: For larger projects, maintain a central documentation file that summarizes key FHE patterns and encryption strategies ## Setting Up AI Tools ### Configuring Cursor Rules Cursor Rules allow you to provide consistent context to Cursor AI, making it more effective at understanding your Fhenix codebase and providing relevant suggestions. ### Creating Cursor Rules 1. Open the Command Palette in Cursor: * Mac: `Cmd + Shift + P` * Windows/Linux: `Ctrl + Shift + P` 2. Search for "Cursor Rules" and select the option to create or edit rules 3. Add project-specific rules that help Cursor understand your Fhenix project: * Specify that you're using Fhenix for confidential smart contracts * Include your preferred Solidity version and patterns * Note any specific FHE operations you'll be using 4. Save your rules file and Cursor will apply these rules to its AI suggestions ## Creating Project Documentation A comprehensive instructions file helps AI tools understand your Fhenix project better. This should be created early in your project and updated regularly. **Ready-to-Use Prompt for Creating Instructions.md:** ``` Create a detailed instructions.md file for my Fhenix project with the following sections: 1. Overview: Summarize the project goals, problem statements, and core FHE functionality 2. Tech Stack: List all technologies, libraries, frameworks with versions (including Fhenix SDK version) 3. Project Structure: Document the file organization with explanations 4. FHE Patterns: Document encryption/decryption patterns and FHE operations used 5. Coding Standards: Document style conventions, linting rules, and patterns 6. User Stories: Key functionality from the user perspective 7. APIs and Integrations: External services and how they connect ``` ## Effective Prompting Strategies ### Be Specific and Direct Start with clear commands and be specific about what you want. AI tools respond best to clear, direct instructions. **Example:** ❌ "Help me with my Fhenix code" ✅ "Create a confidential voting smart contract using Fhenix FHE that encrypts votes and allows tallying without revealing individual votes" ### Provide Context for Complex Tasks **Ready-to-Use Prompt:** ``` I'm working on a Fhenix project for [your use case]. I need your help with: 1. Problem: [describe specific FHE/encryption issue] 2. Current approach: [explain what you've tried] 3. Constraints: [mention any technical limitations or requirements] 4. Expected outcome: [describe what success looks like] Here's the relevant Fhenix documentation: [link or paste relevant docs] ``` ### Ask for Iterations Start simple and refine through iterations rather than trying to get everything perfect in one go. **Ready-to-Use Prompt:** ``` Let's approach this FHE implementation step by step: 1. First, implement a basic version of [feature] with minimal encryption functionality 2. Then, we'll review and identify areas for improvement 3. Next, let's add error handling and edge cases 4. Finally, we'll optimize for gas costs and performance Please start with step 1 now. ``` ## Working with Fhenix ### Leveraging Fhenix Documentation When building with Fhenix, it's important to provide AI assistants with the right context about FHE operations and patterns. **Example FHE Implementation Prompt:** ``` I'm implementing a confidential token transfer using Fhenix. Here's what I need: 1. Encrypt token amounts before transfer 2. Allow transfers between encrypted balances 3. Prevent front-running by keeping amounts private 4. Include proper error handling for encryption failures Based on Fhenix documentation, please show me: - How to encrypt amounts using the Fhenix SDK - How to perform encrypted operations - How to handle decryption for authorized users ``` ### Component Integration Example **Ready-to-Use Prompt for Confidential Balance Display:** ``` I need to implement a new feature in my Fhenix project that: 1. Shows the connected wallet's encrypted balance 2. Allows users to decrypt their own balance 3. Updates when the balance changes 4. Handles loading and error states appropriately 5. Follows our project's coding standards Please update our instructions.md to reflect this new implementation. ``` ## Debugging with AI ### Effective Debugging Prompts **Ready-to-Use Prompt for Bug Analysis:** ``` I'm encountering an issue with my FHE implementation: 1. Expected behavior: [what should happen] 2. Actual behavior: [what's happening instead] 3. Error messages: [include any errors] 4. Relevant code: [paste the problematic code] Please analyze this situation step by step and help me: 1. Identify potential causes of this issue (especially related to encryption/decryption) 2. Suggest debugging steps to isolate the problem 3. Propose possible solutions ``` **Ready-to-Use Prompt for Adding Debug Logs:** ``` I need to debug the following FHE function. Please add comprehensive logging statements that will help me trace: 1. Input values and their types (encrypted vs plaintext) 2. Function execution flow 3. Intermediate state changes 4. Output values or errors 5. Encryption/decryption operations Here's my code: [paste your code] ``` ### When You're Stuck If you're uncertain how to proceed: **Ready-to-Use Clarification Prompt:** ``` I'm unsure how to proceed with [specific FHE task]. Here's what I know: 1. [context about the problem] 2. [what you've tried] 3. [specific areas where you need guidance about FHE] What additional information would help you provide better assistance? ``` ## Advanced Prompting Techniques Modern AI assistants have capabilities that you can leverage with these advanced techniques: **1. Step-by-step reasoning:** Ask the AI to work through FHE problems systematically ``` Please analyze this FHE encryption code step by step and identify potential security issues or gas optimizations. ``` **2. Format specification:** Request specific formats for clarity ``` Please structure your response as a tutorial with: - Code examples for encrypting and decrypting data - Explanations of each FHE operation - Best practices for gas optimization ``` **3. Length guidance:** Indicate whether you want brief or detailed responses ``` Please provide a concise explanation in 2-3 paragraphs about how FHE maintains privacy during computation. ``` **4. Clarify ambiguities:** Help resolve unclear points when you receive multiple options ``` I notice you suggested two approaches for encryption. To clarify, I'd prefer to use the first approach with Solidity native types. ``` ## Best Practices Summary * **Understand context limitations**: Recognize that AI tools have finite context windows and prioritize information accordingly * **Provide relevant context**: Share Fhenix-specific code snippets, encryption patterns, and project details that matter for your specific question * **Be specific in requests**: Clear, direct instructions about FHE operations yield better results than vague questions * **Break complex tasks into steps**: Iterative approaches often work better for complex FHE implementations * **Request explanations**: Ask the AI to explain generated code or FHE concepts you don't understand * **Use formatting for clarity**: Structure your prompts with clear sections and formatting * **Reference Fhenix documentation**: When working with FHE, share relevant documentation links or examples * **Test and validate**: Always review and test AI-generated FHE code before implementing in production * **Build on previous context**: Refer to earlier parts of your conversation when iterating on FHE implementations * **Provide feedback**: Let the AI know what worked and what didn't to improve future responses # Get Funded Source: https://cofhe-docs.fhenix.zone/get-started/builder-support/get-funded Information about funding opportunities for Fhenix builders ## Coming Soon We're working on exciting funding opportunities and grant programs to support developers building on Fhenix. Check back soon for updates on how to get funded for your FHE-enabled projects. Stay tuned for announcements about grants, funding programs, and builder support initiatives. Follow our [Discord](https://discord.gg/FuVgxrvJMY) and [Telegram](https://t.me/+237VUa7c6v1jZmFh) channels for the latest updates. # Compatibility Source: https://cofhe-docs.fhenix.zone/get-started/introduction/compatibility Essential version information and network compatibility for all CoFHE ecosystem components ## Overview This document provides detailed compatibility information for all components in the CoFHE ecosystem. Following these version requirements is crucial for maintaining a stable and secure development environment. Using incompatible versions of CoFHE components can lead to unexpected behavior, security vulnerabilities, or complete failure of your application. Always verify version compatibility before deployment. ## Core Components The following table lists all core CoFHE components with their current and minimum compatible versions: | Component | Current Version | Minimum Compatible Version | Notes | | ----------------------------------- | ------------------------------------------------------------------------- | -------------------------- | ------------------------------------------------------------------------------- | | **@fhenixprotocol/cofhe-contracts** | [`0.1.3`](https://github.com/FhenixProtocol/cofhe-contracts/tree/v0.1.3) | `0.1.3` | Solidity libraries and smart contracts for FHE operations | | **@cofhe/sdk** | [`0.5.2`](https://github.com/FhenixProtocol/cofhesdk/releases/tag/v0.5.2) | `0.5.2` | JavaScript library for interacting with FHE contracts and the CoFHE coprocessor | | **@cofhe/hardhat-plugin** | `0.5.2` | `0.5.2` | Hardhat plugin that deploys mock contracts and exposes utilities | | **@cofhe/hardhat-3-plugin** | `0.5.2` | `0.5.2` | Hardhat 3 plugin that deploys mock contracts and exposes utilities | | **@cofhe/foundry-plugin** | `0.5.2` | `0.5.2` | Foundry plugin that deploys mock contracts and exposes utilities | | **@cofhe/mock-contracts** | `0.5.2` | `0.5.2` | Mock contracts for local development and testing | ## Additional Packages These packages provide shared utilities and higher-level abstractions used across the ecosystem: | Component | Current Version | Minimum Compatible Version | Notes | | ------------------------------------------------- | --------------- | -------------------------- | ---------------------------------------------------------- | | **@fhenixprotocol/cofhe-errors** | `1.0.2` | `1.0.2` | Shared error definitions for CoFHE contracts | | **@fhenixprotocol/fhenix-confidential-contracts** | `0.2.1` | `0.2.1` | Pre-built confidential contract primitives (FHERC20, etc.) | ### Version Compatibility Guidelines When working with CoFHE components: * **Always use the latest stable versions**: Check GitHub releases for the most recent versions * **Verify compatibility**: Ensure all components are within their compatible version ranges * **Test thoroughly**: After updating versions, test your application in both mock and testnet environments * **Check release notes**: Review breaking changes and migration guides when upgrading For the best development experince, use the latest versions of all components. They are designed to work together seamlessly and include the latest features and security improvements. ## Network Compatibility CoFHE currently supports the following networks: | Network | Compatible | API Version | Notes | Plugin Name | | -------------------- | ---------- | ----------- | ------------ | -------------- | | **Sepolia** | ✅ | `v1` | Full support | `eth-sepolia` | | **Arbitrum Sepolia** | ✅ | `v1` | Full support | `arb-sepolia` | | **Base Sepolia** | ✅ | `v1` | Full support | `base-sepolia` | All supported networks use API version `v1`. The plugin names correspond to the network identifiers used in Hardhat configuration files. ## Troubleshooting Compatibility Issues If you encounter compatibility issues: 1. **Verify all versions**: Ensure all CoFHE components are using compatible versions 2. **Check network support**: Confirm your target network is supported 3. **Review error messages**: Look for version mismatch errors in your console 4. **Update dependencies**: Run `npm update` or `yarn upgrade` to get the latest compatible versions 5. **Check documentation**: Review the component-specific documentation for version requirements If you're experiencing issues after updating versions, check the release notes for breaking changes and migration guides. Some updates may require code changes in your contracts or client applications. ## Next Steps * Review the [Quick Start guide](/fhe-library/introduction/quick-start) to set up your development environment * Learn about [best practices](/fhe-library/introduction/best-practices) for developing with CoFHE * Explore the [API reference](/api-reference/introduction) for detailed component documentation # Fhenix Source: https://cofhe-docs.fhenix.zone/get-started/introduction/fhenix The missing infrastructure for Confidential DeFi ## **Introduction** Blockchains are great for transparency, security and trust, but that transparency comes at a cost—**everything is public**. Every transaction, smart contract interaction, and account balance is out in the open, which isn't ideal for things like finance, healthcare, or any use case that deal with sensitive data. **Fully Homomorphic Encryption (FHE) fixes this.** Instead of exposing raw data on-chain, FHE allows computations to happen **directly on encrypted data**. The blockchain never sees the actual inputs or outputs—only encrypted values—yet the results are still valid when decrypted by an authorized recipient. This means smart contracts can run just like they do now, but with **built-in confidentiality**—without compromising decentralization or security. *** ## **The Blockchain Transparency Problem** Blockchain is often praised for its **decentralization, immutability, and transparency**—but transparency is a double-edged sword. ### **Why Transparency is a Problem** In public blockchains like Ethereum, every transaction, smart contract interaction, and account balance is **completely visible** to anyone. This radical transparency, while crucial for establishing trust and enabling verification, creates significant privacy challenges. FHE solves this fundamental tradeoff by allowing data to remain **fully encrypted** while still maintaining the network's ability to verify its accuracy and authenticity. This means sensitive information can be processed and validated without ever being exposed, combining the best of both worlds - **bulletproof privacy with trustless verification**. **Real-world consequences of blockchain transparency:** \ ✅ **Front-running & MEV** – Traders can analyze mempools and exploit pending transactions before they are executed. \ ✅ **Confidentiality leaks** – Sensitive financial transactions, payroll information, or business logic are exposed. \ ✅ **Enterprise adoption hurdles** – Companies are reluctant to use public blockchains if competitors can access proprietary data. These challenges can all be mitigated by using FHE in your smart contracts. *** ## **What is FHE?** **FHE** is a cryptographic technique that allows computations to be performed on encrypted data **without decrypting it**. Most cryptographic techniques secure data only until it needs to be used—FHE keeps it hidden even while processing, preventing leaks at every step. ### **How FHE Works** 1. A user encrypts their data into ciphertext. 2. The blockchain performs computations directly on the ciphertext. 3. The result remains encrypted and can only be decrypted by the authorized user. **The Holy Grail of Cryptography** * Data remains private throughout the entire computation process. * Smart contracts can execute logic on encrypted inputs and return encrypted outputs. * Users control their data without relying on trusted third parties. # Support & Feedback Source: https://cofhe-docs.fhenix.zone/get-started/introduction/support-and-feedback Get help, report issues, and provide feedback for the CoFHE project ## Overview We believe in building together as a community. Our team is genuinely excited to hear from you - whether you've hit a roadblock, have brilliant ideas to share, or just want to chat about the future of FHE technology. No question is too small, no feedback too minor. Your experience matters to us, and your insights drive our innovation. Let's collaborate to make CoFHE the best it can be! Your feedback helps us build better tools and documentation for everyone. We appreciate your contributions to the CoFHE community! ## Support Channels ### Documentation If you find issues with our documentation, have suggestions for improvements, or want to request new content, visit our [Documentation Repository](https://github.com/FhenixProtocol/cofhe-docs/issues). Create a new issue describing the problem or enhancement: 1. Navigate to the [Documentation Repository Issues](https://github.com/FhenixProtocol/cofhe-docs/issues) 2. Click "New Issue" 3. Use labels like "documentation", "bug", or "enhancement" to help us categorize your feedback 4. Provide detailed information about what needs to be addressed If you'd like to contribute directly, we welcome pull requests: 1. Fork the repository 2. Make your changes 3. Submit a PR with a clear description of the improvements We appreciate detailed reports that help us understand exactly what needs to be addressed. Include examples, screenshots, or code snippets when relevant. ### Community Support Need technical assistance or general help? Our community channels are ready to support you. #### Discord Join our [Discord community](https://discord.gg/FuVgxrvJMY) where you'll find: **Developers Corner:** * `#dev-updates` - Stay informed about the latest development updates * `#dev-general` - General development discussions * `#verified-devs` - Channel for verified developers * `#tech-questions` - Technical support and questions **Support:** * `#feedback-and-suggestions` - Share your ideas and feedback * `#support-ticket` - Get dedicated support for specific issues * `#spam-and-bug-reports` - Report bugs or suspicious activity Connect with our active community of developers and team members ready to help you succeed with CoFHE! #### Telegram Connect with us on [Telegram](https://t.me/+237VUa7c6v1jZmFh) for: * **General** - General discussions * **Dev Help** - Technical support and questions Get quick answers to your questions and connect with the Fhenix community. ## How to Get Help When seeking help, provide as much context as possible: 1. **Describe your issue clearly**: What are you trying to accomplish? 2. **Include error messages**: Copy the full error message or stack trace 3. **Share relevant code**: Include code snippets that demonstrate the problem 4. **Specify your environment**: Mention versions of CoFHE components you're using 5. **Explain what you've tried**: Let us know what troubleshooting steps you've already taken The more information you provide, the faster we can help you resolve your issue. Don't hesitate to ask - we're here to help! ## Providing Feedback Your feedback is invaluable to us. Whether you have: * **Feature requests**: Ideas for new functionality * **Documentation improvements**: Suggestions for clearer explanations * **Bug reports**: Issues you've encountered * **General feedback**: Thoughts on your experience with CoFHE We want to hear from you! Use the appropriate channel (Discord, GitHub Issues, or Telegram) to share your thoughts. Thank you for being part of our community! Your feedback helps us build better tools and documentation for everyone. ## Next Steps * Join our [Discord community](https://discord.gg/FuVgxrvJMY) to connect with other developers * Check the [Compatibility](/get-started/introduction/compatibility) page if you're experiencing version-related issues * Review [Common Errors](/fhe-library/core-concepts/common-errors) for troubleshooting guidance * Explore our [documentation](/fhe-library/introduction/overview) to learn more about CoFHE # What is CoFHE? Source: https://cofhe-docs.fhenix.zone/get-started/introduction/what-is-cofhe A high-level introduction to CoFHE — Fhenix's Fully Homomorphic Encryption coprocessor [FHE](/get-started/introduction/fhenix) explains *why* confidential smart contracts matter. **CoFHE is how Fhenix makes them practical.** **CoFHE is an FHE coprocessor that lets any blockchain run computations on encrypted data.** This makes confidentiality just another Solidity feature. There's no migration to a specialized FHE chain, no new toolchain, and no cryptography to implement yourself. CoFHE handles the heavy FHE math offchain; your contract only ever touches lightweight *handles* to encrypted values, so code stays familiar. Values are encrypted — everything else feels like ordinary development. ## Why a coprocessor? The coprocessor model adds confidentiality without changing how applications are built — same Solidity, same chains, same tooling, with encrypted values as just another type to work with. * **No migration** — CoFHE attaches to existing blockchains. Confidential contracts deploy to the networks already in use, not a dedicated FHE L1. * **Familiar code** — contracts pass around lightweight *handles* (references to ciphertexts) rather than the ciphertexts themselves, so they read like normal Solidity. FHE operations add some gas overhead, but the onchain footprint stays small and predictable. * **No cryptography to implement** — the heavy FHE math runs offchain on the CoFHE server, which is built to do it efficiently. A contract calls `FHE.add`; CoFHE does the rest. * **Trust-minimized by default** — decryption is never in one party's hands. A Threshold Network performs it through multi-party computation. ## What CoFHE lets you build Because computation happens on encrypted values, you can build applications where sensitive data never appears in plaintext onchain: * **Confidential balances and transfers** — token amounts and balances stay hidden while transfers still settle correctly. * **Private state in contracts** — per-user values (counters, scores, bids, positions) that no one, not even the contract or CoFHE, can read in the clear. * **Sealed inputs** — users submit encrypted inputs (votes, bids, orders) that are computed on without ever being revealed. * **Selective disclosure** — results are decrypted only for authorized parties, gated by signed permits. ## How it works Every CoFHE application follows the same three-phase lifecycle: **encrypt → compute → decrypt.** The user's plaintext is encrypted in the client using the [`@cofhe/sdk`](/client-sdk/introduction/overview), bundled with a zero-knowledge proof that the input is well-formed, and submitted to CoFHE. The blockchain only ever receives an encrypted handle — never the raw value. The smart contract uses [`FHE.sol`](/fhe-library/introduction/overview) to operate on encrypted handles — adding, comparing, selecting — as if they were ordinary numbers. Each operation deterministically derives a new result handle and is recorded onchain; the CoFHE server independently computes the matching ciphertext offchain. Nothing returns to the contract, and plaintext is never exposed at any point. When an authorized user wants a result, they present a signed [permit](/client-sdk/guides/permits). The Threshold Network decrypts via multi-party computation — either re-encrypting the value so only that user can read it (for display), or returning a verifiable plaintext with a signature (for onchain use). ```mermaid theme={null} sequenceDiagram participant User participant SDK as "@cofhe/sdk" participant Contract as "Smart Contract (FHE.sol)" participant CoFHE as "CoFHE Coprocessor" Note over User,CoFHE: Encrypt User->>SDK: plaintext value SDK->>CoFHE: encrypted input + ZK proof CoFHE-->>SDK: signed encrypted handle SDK->>Contract: tx with handle Note over Contract,CoFHE: Compute Contract->>Contract: FHE.add / FHE.lt / FHE.select ... Contract->>Contract: derive result handle (deterministic) Note over Contract,CoFHE: operation emitted onchain — nothing returns to the contract CoFHE->>CoFHE: pick up operation, compute ciphertext for the same handle Note over User,CoFHE: Decrypt User->>SDK: read result (+ permit) SDK->>CoFHE: decrypt request CoFHE-->>SDK: re-encrypted / verifiable plaintext SDK-->>User: revealed value ``` ## The pieces Developers only interact directly with **two** parts of CoFHE; the rest runs behind the scenes. ### What you touch | Component | Where | Role | | ----------------------------------------------------- | ------- | ----------------------------------------------- | | **[`@cofhe/sdk`](/client-sdk/introduction/overview)** | Client | Encrypt inputs, manage permits, decrypt outputs | | **[`FHE.sol`](/fhe-library/introduction/overview)** | Onchain | Solidity API for operating on encrypted handles | ### What runs behind the scenes | Component | Role | | --------------------- | -------------------------------------------------------------------------------------------------- | | **Task Manager** | Onchain gateway that validates FHE requests and enforces access control | | **Slim Listener** | Watches onchain events and forwards operations to the offchain layer | | **FHEOS Server** | Executes the actual FHE computations and holds encrypted state | | **Result Processor** | Publishes verified results back onchain | | **Threshold Network** | Decrypts via multi-party computation — no single party holds the key | | **Registries** | Track ciphertexts and record result commitments so integrity can be verified before any decryption | For a component-by-component breakdown, see the [CoFHE Architecture deep dive](/deep-dive/cofhe-components/overview). ## How CoFHE keeps data safe * **Encrypted end-to-end** — values are encrypted client-side and stay encrypted through computation; only handles touch the chain. * **Verified inputs** — zero-knowledge proofs ensure every encrypted input is well-formed before it enters the system. * **Verified results** — the coprocessor commits to each result onchain, and the Threshold Network checks integrity before it will decrypt anything. * **No single point of trust for decryption** — decryption requires the Threshold Network's multi-party computation, gated by signed permits. ## Next steps Walk through encrypt → compute → decrypt with a concrete Counter example. The TypeScript SDK for encrypting inputs and decrypting outputs. The Solidity library for computing on encrypted data onchain. Every CoFHE component and data flow, in detail. # ACL Usage Examples Source: https://cofhe-docs.fhenix.zone/tutorials/acl-usage-examples Practical examples of using Access Control Lists (ACL) to manage permissions for encrypted data ## Overview This tutorial provides practical examples of using Access Control Lists (ACL) to manage permissions for encrypted data in your CoFHE contracts. See [ACL Mechanism](/fhe-library/core-concepts/access-control) for explanation of why the ACL mechanism is needed. ## Solidity API The following functions are available for managing access control: 1. `FHE.allowThis(CIPHERTEXT_HANDLE)` - allows the current contract access to the handle 2. `FHE.allow(CIPHERTEXT_HANDLE, ADDRESS)` - allows the specified address access to the handle 3. `FHE.allowTransient(CIPHERTEXT_HANDLE, ADDRESS)` - allows the specified address access to the handle for the duration of the transaction ## Automatic Transaction-Scoped Allowance The contract that creates the value for the first time will automatically get ownership of the ciphertext **for the duration of the transaction**, by using `ACL.allowTransient(this)` behind the scenes. ```solidity theme={null} // Contract A function doAdd(InEuint32 input1, InEuint32 input2) { euint32 handle1 = FHE.asEuint32(input1); // Contract A gets temporary ownership of handle1 euint32 handle2 = FHE.asEuint32(input2); // Contract A gets temporary ownership of handle2 euint32 result = FHE.add(handle1, handle2); // possible because Contract A has ownership of handle1 and handle2 } ``` This automatic allowance only lasts for the duration of the current transaction. To use encrypted values in future transactions, you must explicitly grant access. ## Persistent Allowance for This Contract To use the results in other transactions, explicit ownership must be granted with `FHE.allow(address)` or `FHE.allowThis()`. ```solidity theme={null} contract A { private euint32 result; private euint32 handle1; function doAdd(InEuint32 input1, InEuint32 input2) { handle1 = FHE.asEuint32(input1); // Contract A gets temporary ownership of handle1 euint32 handle2 = FHE.asEuint32(input2); // Contract A gets temporary ownership of handle2 result = FHE.add(handle1, handle2); // Contract A gets temporary ownership of result FHE.allowThis(result); // result is allowed for future transactions } function doSomethingWithResult() { FHE.allowPublic(result); // Allowed — marks result as publicly decryptable FHE.add(handle1, result); // ACLNotAllowed (handle1 is not owned persistently) } } ``` If you don't call `FHE.allowThis()` after modifying encrypted values, you won't be able to use them in future transactions. Always call `FHE.allowThis()` after operations that modify encrypted state variables. ## Allowance for Decryptions To decrypt a ciphertext off-chain via the decryption network, the issuer must be allowed on the ciphertext handle via `FHE.allow(userAddress)`. ```solidity theme={null} contract A { private mapping(address => euint32) balances; function transfer(InEuint32 _amount, address to) { euint32 amount = FHE.asEuint32(_amount); balances[msg.sender] = FHE.sub(balances[msg.sender], amount); balances[to] = FHE.add(balances[to], amount); FHE.allow(balances[msg.sender], msg.sender); // now the sender can decrypt her balance FHE.allow(balances[to], to); // now the receiver can decrypt his balance // enable balance manipulation for future transactions FHE.allowThis(balances[msg.sender]); FHE.allowThis(balances[to]); } } ``` When allowing users to decrypt their own encrypted values, use `FHE.allow()` to grant persistent access. This enables users to decrypt values off-chain using `decryptForView` without requiring additional transactions. ## Allow Other Contracts You can also allow other contracts to use your ciphertexts, either persistently or only for the course of this transaction via `FHE.allowTransient(handle, address)`. ```solidity theme={null} contract A { function doAdd(InEuint32 input1) { euint32 handle1 = FHE.asEuint32(input1); // Contract A gets temporary ownership of handle1 FHE.allowTransient(handle1, addressB); // Contract B is allowed to use handle1 in this transaction alone // or FHE.allow(handle1, addressB); // Contract B is allowed to use handle1 forever IContractB(addressB).doSomethingWithHandle1(handle1); } } ``` Use `FHE.allowTransient()` when you only need to grant access for a single transaction. Use `FHE.allow()` when you need persistent access across multiple transactions. ## Common Patterns ### Pattern 1: Allow Contract and User When modifying encrypted values that users need to access: ```solidity theme={null} function updateBalance(address user, InEuint32 amount) public { euint32 encryptedAmount = FHE.asEuint32(amount); balances[user] = FHE.add(balances[user], encryptedAmount); // Allow contract to use in future transactions FHE.allowThis(balances[user]); // Allow user to decrypt/seal their balance FHE.allow(balances[user], user); } ``` ### Pattern 2: Allow Sender A common pattern is to allow the message sender: ```solidity theme={null} function submitEncryptedData(InEuint32 data) public { euint32 encryptedData = FHE.asEuint32(data); storedData[msg.sender] = encryptedData; FHE.allowThis(storedData[msg.sender]); FHE.allowSender(storedData[msg.sender]); // Equivalent to FHE.allow(storedData[msg.sender], msg.sender) } ``` ### Pattern 3: Global Access For values that should be accessible to everyone: ```solidity theme={null} function setPublicValue(InEuint32 value) public onlyOwner { publicValue = FHE.asEuint32(value); FHE.allowPublic(publicValue); // Everyone can now access this value } ``` ## Best Practices After modifying any encrypted state variable, call `FHE.allowThis()` to ensure the contract can use it in future transactions. If users need to decrypt their own values off-chain, use `FHE.allow()` or `FHE.allowSender()` to grant them access. When passing encrypted values to other contracts for a single operation, use `FHE.allowTransient()` instead of `FHE.allow()`. ## Next Steps * Learn more about [Access Control](/fhe-library/core-concepts/access-control) mechanisms * Review [Your First FHE Contract](/tutorials/your-first-fhe-contract) for a complete example * Explore [Adding FHE to an Existing Contract](/tutorials/adding-fhe-to-existing-contract) for migration patterns # Adding FHE to an Existing Contract Source: https://cofhe-docs.fhenix.zone/tutorials/adding-fhe-to-existing-contract Step-by-step guide for integrating FHE capabilities into contracts you've already built ## Overview Let's start with an existing contract, and walk through the steps to migrate the contract to CoFHE. The contract is a very simple voting contract. ## Original Contract ```solidity theme={null} // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/ERC20.sol) pragma solidity ^0.8.25; contract VotingExample { struct Option { string name; uint64 votes; } struct Proposal { string name; uint256 deadline; Option[] options; mapping(address => bool) hasVoted; bool exists; uint8 winner; } address public owner; uint256 public proposalCount; mapping(uint256 => Proposal) public proposals; event ProposalCreated( uint256 indexed proposalId, string name, uint256 deadline ); event VoteCast( uint256 indexed proposalId, address indexed voter, uint256 optionIndex ); error NotOwner(); error InvalidOptionCount(); error ProposalNotFound(); error DeadlineExpired(); error AlreadyVoted(); error InvalidOptionIndex(); constructor() { owner = msg.sender; } modifier onlyOwner() { if (msg.sender != owner) revert NotOwner(); _; } function createProposal( string memory _name, string[] memory _options, uint256 _deadline ) external onlyOwner returns (uint256) { if (_options.length < 2 || _options.length > 4) revert InvalidOptionCount(); uint256 proposalId = proposalCount++; Proposal storage proposal = proposals[proposalId]; proposal.name = _name; proposal.deadline = _deadline; proposal.exists = true; for (uint i = 0; i < _options.length; i++) { proposal.options.push(Option({name: _options[i], votes: 0})); } emit ProposalCreated(proposalId, _name, _deadline); return proposalId; } function vote(uint256 _proposalId, uint256 _optionIndex) external { Proposal storage proposal = proposals[_proposalId]; if (!proposal.exists) revert ProposalNotFound(); if (block.timestamp >= proposal.deadline) revert DeadlineExpired(); if (proposal.hasVoted[msg.sender]) revert AlreadyVoted(); if (_optionIndex >= proposal.options.length) revert InvalidOptionIndex(); proposal.options[_optionIndex].votes++; proposal.hasVoted[msg.sender] = true; emit VoteCast(_proposalId, msg.sender, _optionIndex); } function getProposal( uint256 _proposalId ) external view returns ( string memory name, uint256 deadline, Option[] memory options, bool exists ) { Proposal storage proposal = proposals[_proposalId]; return ( proposal.name, proposal.deadline, proposal.options, proposal.exists ); } function hasVoted( uint256 _proposalId, address _voter ) external view returns (bool) { return proposals[_proposalId].hasVoted[_voter]; } } ``` In this contract, the owner can create a proposal with anywhere from 2-4 possible voting options. Users can then vote on this proposal by the deadline, at which point the proposal can be closed, and the result revealed. Unfortunately, the contract is completely public, and the vote tallies can be observed during the voting period. We will update this contract to interact with CoFHE, and use FHE encrypted variables to store the votes until the result is revealed. We will pay special attention to the following updates that need to be made: 1. **Constant time computation** 2. **Handling the `if/else` case** 3. **Handling the `require` case** 4. **Revealing the result with the decrypt-with-proof pattern** ## Migrating the Contract ### Step 1: Import `FHE.sol` The first thing that we need to do is import `FHE.sol` from the `cofhe-contracts` repo. This package allows smart contracts to start using FHE operations and encrypted variables. ```solidity theme={null} pragma solidity ^0.8.25; import {FHE, euint64, InEuint64} from "@fhenixprotocol/cofhe-contracts/FHE.sol"; contract VotingExample { ``` ### Step 2: Encrypt the vote counters with `euint64` The amount of votes cast for each option needs to be encrypted, so we can switch the variable type from a `uint64` to an `euint64` (`euint64` is included in the `FHE.sol` import). The vote counts will be represented by a `ctHash` which acts as a handle and pointer to the encrypted number of votes. ```solidity theme={null} struct Option { string name; euint64 votes; // Changed from uint64 } ``` ### Step 3: Initialize the `euint64` vote counts Now that the votes type has changed, it must be initialized by performing a `trivialEncrypt` of the starting value: ```solidity theme={null} function createProposal( string memory _name, string[] memory _options, uint256 _deadline ) external onlyOwner returns (uint256) { if (_options.length < 2 || _options.length > 4) revert InvalidOptionCount(); uint256 proposalId = proposalCount++; Proposal storage proposal = proposals[proposalId]; proposal.name = _name; proposal.deadline = _deadline; proposal.exists = true; for (uint i = 0; i < _options.length; i++) { proposal.options.push( Option({name: _options[i], votes: FHE.asEuint64(0)}) ); } emit ProposalCreated(proposalId, _name, _deadline); return proposalId; } ``` This will work, but it may make sense to prepare the trivially encrypted values in the constructor rather than in each transaction to save on gas and the number of FHE operations being executed. Let's see how that would look: ```solidity theme={null} contract VotingExample { ... euint64 private EUINT64_ZERO; euint64 private EUINT64_ONE; constructor() { owner = msg.sender; EUINT64_ZERO = FHE.asEuint64(0); EUINT64_ONE = FHE.asEuint64(1); } function createProposal( string memory _name, string[] memory _options, uint256 _deadline ) external onlyOwner returns (uint256) { ... for (uint i = 0; i < _options.length; i++) { proposal.options.push( Option({name: _options[i], votes: EUINT64_ZERO}) ); } ... } } ``` ### Step 4: Handle user votes with `InEuint8` We now need to handle the user's vote casting. The first thing that we need to do is hide which option the user is voting for. We can do this by replacing the `vote` function parameter `uint256 _optionIndex` with `InEuint8 memory _optionIndex`. `InEuint8` is an encrypted input type. We then need to convert the `InEuint8` to an `euint8` for use in computation. Encrypting inputs requires the use of the [**Client SDK**](/client-sdk/introduction/overview) (`@cofhe/sdk`). Read more about [**encrypted inputs**](/client-sdk/guides/encrypting-inputs). ```solidity theme={null} function vote(uint256 _proposalId, InEuint8 memory _optionIndex) external { euint8 optionIndex = FHE.asEuint8(_optionIndex); ``` ### Step 5: Constant time computation In order to preserve the confidentiality of the user's vote, we must make sure that we aren't leaking any information about the user's choice. If we only updated the voting option that the user has selected, then a user's vote could be deduced by simply watching which vote counter changes. Therefore, we must update *all* the vote counters to hide the user's true vote: ```solidity theme={null} function vote(uint256 _proposalId, InEuint8 memory _optionIndex) external { euint8 optionIndex = FHE.asEuint8(_optionIndex); Proposal storage proposal = proposals[_proposalId]; if (!proposal.exists) revert ProposalNotFound(); if (block.timestamp >= proposal.deadline) revert DeadlineExpired(); if (proposal.hasVoted[msg.sender]) revert AlreadyVoted(); for (uint8 i = 0; i < proposal.options.length; i++) { proposal.options[i].votes = FHE.add( proposal.options[i].votes, FHE.select( optionIndex.eq(FHE.asEuint8(i)), EUINT64_ONE, EUINT64_ZERO ) ); } proposal.hasVoted[msg.sender] = true; emit VoteCast(_proposalId, msg.sender, optionIndex); } ``` Let's break down how this works: * We iterate through each of the proposal options * We *always* perform an `FHE.add` on every options' vote count, this means that every options' vote count will change any time a user votes * We only want to increment the user's selected choice, so we use `FHE.select` * The API for select is `FHE.select(conditional, ifTrue, ifFalse)` * Without the FHE syntax, the logic is as follows: ```solidity theme={null} proposal.options[i].votes = proposal.options[i].votes + (_optionIndex == i ? 1 : 0); ``` ### Step 6: Handling `if/else` Branching based on encrypted variables is not allowed, so there is one more change to make to the `vote` function, which is to remove the **`if/else`** branch that relies on `_optionIndex`. **`require`** statements are also not allowed when working with encrypted variables for the same reason as above. ```solidity theme={null} // Remove this check - it cannot work with encrypted variables // if (_optionIndex >= proposal.options.length) // revert InvalidOptionIndex(); ``` It is important to *never* use an encrypted variable as part of an if/else branch, since the encrypted variable is always truthy. Instead, use `FHE.select` to replace the value with 0. ### Step 7: Update `VoteCast` event Currently the `VoteCast` event emits a `uint256 optionIndex`. Now that the user's vote is an encrypted variable (`euint8`), we need to update the event to emit the user's encrypted input: ```solidity theme={null} event VoteCast( uint256 indexed proposalId, address indexed voter, euint8 optionIndex // Changed from uint256 ); ``` And its invocation: ```solidity theme={null} emit VoteCast(_proposalId, msg.sender, optionIndex); ``` This event will be emitted with the encrypted `euint8 optionIndex`. This is important as in the future the FHE block explorer will be able to decrypt these variables and show the true event log, but only if we make one more change. ### Step 8: Granting access with `FHE.allow` By default, access and computation on an encrypted variable is blocked, and trying to use a variable before access has been granted will cause AccessControlList.sol (ACL) to revert with an `ACLNotAllowed` error. Access is granted using `FHE.allow` (and its variants `FHE.allowSender`, `FHE.allowThis`, and `FHE.allowPublic`). After access is granted, the encrypted variable may be used in a computation, or decrypted by any of the authorized users or contracts. **`FHE.allow` variants:** * `FHE.allow(ctHash, address)` - grants access to `address`. `ctHash` is any encrypted variable like `euint64` or `ebool` * `FHE.allowSender(ctHash)` - grants access to `msg.sender` * `FHE.allowThis(ctHash)` - grants access to the executing contract (`address(this)`) * `FHE.allowPublic(ctHash)` - grants access to everyone, useful for things like an encrypted totalSupply variable, which everyone should have access to ```solidity theme={null} function vote(uint256 _proposalId, InEuint8 memory _optionIndex) external { euint8 optionIndex = FHE.asEuint8(_optionIndex); Proposal storage proposal = proposals[_proposalId]; if (!proposal.exists) revert ProposalNotFound(); if (block.timestamp >= proposal.deadline) revert DeadlineExpired(); if (proposal.hasVoted[msg.sender]) revert AlreadyVoted(); for (uint8 i = 0; i < proposal.options.length; i++) { proposal.options[i].votes = FHE.add( proposal.options[i].votes, FHE.select( optionIndex.eq(FHE.asEuint8(i)), EUINT64_ONE, EUINT64_ZERO ) ); // Grant this contract access to each vote count // Without this, FHE.add(options[i].votes, ...) would revert FHE.allowThis(proposal.options[i].votes); } proposal.hasVoted[msg.sender] = true; // Grant msg.sender access to their votingIndex // Without this, the msg.sender would not be able to see their vote in the explorer (coming soon) FHE.allowSender(optionIndex); emit VoteCast(_proposalId, msg.sender, optionIndex); } ``` It is critical to ensure that `FHE.allowThis` is used on encrypted variables that need to be used later in the contract's lifecycle. Contracts must have access to variables in order to perform FHE operations on those variables. ### Step 9: Finalize the voting with `FHE.allowPublic` Because the votes are encrypted for the lifetime of the proposal, after the proposal has ended, we need to decrypt the results and reveal the winner. Let's start by adding a `finalizeVote` function that marks the vote counts as publicly decryptable: ```solidity theme={null} function finalizeVote(uint256 _proposalId) external { if (msg.sender != owner) revert NotOwner(); Proposal storage proposal = proposals[_proposalId]; if (!proposal.exists) revert ProposalNotFound(); if (block.timestamp < proposal.deadline) revert DeadlineNotReached(); for (uint8 i = 0; i < proposal.options.length; i++) { FHE.allowPublic(proposal.options[i].votes); } } ``` `FHE.allowPublic` marks each vote count as eligible for public decryption. Anyone can now request the plaintext values off-chain. ### Step 10: Reveal the results with `FHE.publishDecryptResult` After `finalizeVote` is called, the vote counts need to be decrypted off-chain and published on-chain with proof. We add a `revealResults` function that accepts the decrypted values and their Threshold Network signatures: ```solidity theme={null} function revealResults( uint256 _proposalId, uint64[] calldata _decryptedVotes, bytes[] calldata _signatures ) external { Proposal storage proposal = proposals[_proposalId]; if (!proposal.exists) revert ProposalNotFound(); require( _decryptedVotes.length == proposal.options.length, "Mismatched lengths" ); for (uint8 i = 0; i < proposal.options.length; i++) { FHE.publishDecryptResult( proposal.options[i].votes, _decryptedVotes[i], _signatures[i] ); } } ``` The client-side flow looks like this: ```typescript theme={null} // 1. Finalize the vote (owner only) await voting.finalizeVote(proposalId); // 2. Decrypt each option's vote count off-chain const proposal = await voting.getProposal(proposalId); const decryptedVotes = []; const signatures = []; for (const option of proposal.options) { const result = await client .decryptForTx(option.votes) .withoutPermit() .execute(); decryptedVotes.push(result.decryptedValue); signatures.push(result.signature); } // 3. Publish all results on-chain await voting.revealResults(proposalId, decryptedVotes, signatures); ``` ### Step 11: Checking the results with `FHE.getDecryptResultSafe` Finally, we can update our `getProposal` function to check the final state of the proposal. Once the results have been published, `FHE.getDecryptResultSafe` will return the plaintext values: ```solidity theme={null} function getProposal( uint256 _proposalId ) external view returns ( string memory name, uint256 deadline, bool exists, string[] memory options, uint256[] memory votes, bool finalized, uint8 winner ) { Proposal storage proposal = proposals[_proposalId]; // Plaintext values can be assigned directly name = proposal.name; deadline = proposal.deadline; exists = proposal.exists; options = new string[](proposal.options.length); for (uint8 i = 0; i < proposal.options.length; i++) { options[i] = proposal.options[i].name; } // Fetch the decrypted results with `FHE.getDecryptResultSafe`. // If any of the results have not yet been decrypted, set `finalized` to false. // Store the decrypted result counts in the `votes` list to be returned. votes = new uint256[](proposal.options.length); finalized = true; for (uint8 i = 0; i < proposal.options.length; i++) { (uint256 result, bool decrypted) = FHE.getDecryptResultSafe( proposal.options[i].votes ); votes[i] = decrypted ? result : 0; if (!decrypted) finalized = false; } // If all votes have been decrypted, determine the winning option. if (finalized) { uint256 maxVotes = 0; winner = 0; for (uint8 i = 0; i < proposal.options.length; i++) { if (votes[i] > maxVotes) { maxVotes = votes[i]; winner = i; } } } } ``` In this block you can see a few changes. The first is that we have split `options` and `votes` from the `getProposal` return type, which allows us to better handle the decryption results. We use `FHE.getDecryptResultSafe` to fetch the decryption result of each of the vote counts, which returns the result as well as a flag indicating whether the decryption has posted. Once all the decryptions have posted, the `finalized` flag will update to be `true`, and the `winner` determined based on the vote counts. ## Conclusions In this tutorial, we walked through migrating a simple voting contract to use CoFHE. The key changes were: 1. Changing the vote counts from plain `uint64` to encrypted values using `euint64` 2. Modifying the voting function to use encrypted addition instead of plain addition 3. Using `FHE.allowPublic` to mark values as decryptable after the voting deadline 4. Adding a `revealResults` function that publishes decrypted values on-chain with `FHE.publishDecryptResult` 5. Updating the getter function to handle decryption of results safely The resulting contract provides the same functionality as the original, but with the added privacy benefit that individual votes are not visible on-chain until the final tally is decrypted. This demonstrates how CoFHE can be used to add privacy to existing contracts with minimal changes to the core logic. ## Final `FHEVotingExample.sol` ```solidity theme={null} // SPDX-License-Identifier: MIT // OpenZeppelin Contracts (last updated v5.1.0) (token/ERC20/ERC20.sol) pragma solidity ^0.8.25; import {FHE, euint64, InEuint8} from "@fhenixprotocol/cofhe-contracts/FHE.sol"; contract FHEVotingExample { struct Option { string name; euint64 votes; } struct Proposal { string name; uint256 deadline; Option[] options; mapping(address => bool) hasVoted; bool exists; uint8 winner; } address public owner; uint256 public proposalCount; mapping(uint256 => Proposal) public proposals; euint64 private EUINT64_ZERO; euint64 private EUINT64_ONE; event ProposalCreated( uint256 indexed proposalId, string name, uint256 deadline ); event VoteCast( uint256 indexed proposalId, address indexed voter, euint8 optionIndex ); error NotOwner(); error InvalidOptionCount(); error ProposalNotFound(); error DeadlineExpired(); error AlreadyVoted(); error InvalidOptionIndex(); error DeadlineNotReached(); constructor() { owner = msg.sender; EUINT64_ZERO = FHE.asEuint64(0); EUINT64_ONE = FHE.asEuint64(1); } modifier onlyOwner() { if (msg.sender != owner) revert NotOwner(); _; } function createProposal( string memory _name, string[] memory _options, uint256 _deadline ) external onlyOwner returns (uint256) { if (_options.length < 2 || _options.length > 4) revert InvalidOptionCount(); uint256 proposalId = proposalCount++; Proposal storage proposal = proposals[proposalId]; proposal.name = _name; proposal.deadline = _deadline; proposal.exists = true; for (uint i = 0; i < _options.length; i++) { proposal.options.push( Option({name: _options[i], votes: EUINT64_ZERO}) ); } emit ProposalCreated(proposalId, _name, _deadline); return proposalId; } function vote(uint256 _proposalId, InEuint8 memory _optionIndex) external { euint8 optionIndex = FHE.asEuint8(_optionIndex); Proposal storage proposal = proposals[_proposalId]; if (!proposal.exists) revert ProposalNotFound(); if (block.timestamp >= proposal.deadline) revert DeadlineExpired(); if (proposal.hasVoted[msg.sender]) revert AlreadyVoted(); for (uint8 i = 0; i < proposal.options.length; i++) { proposal.options[i].votes = FHE.add( proposal.options[i].votes, FHE.select( optionIndex.eq(FHE.asEuint8(i)), EUINT64_ONE, EUINT64_ZERO ) ); FHE.allowThis(proposal.options[i].votes); } proposal.hasVoted[msg.sender] = true; FHE.allowSender(optionIndex); emit VoteCast(_proposalId, msg.sender, optionIndex); } function finalizeVote(uint256 _proposalId) external { if (msg.sender != owner) revert NotOwner(); Proposal storage proposal = proposals[_proposalId]; if (!proposal.exists) revert ProposalNotFound(); if (block.timestamp < proposal.deadline) revert DeadlineNotReached(); for (uint8 i = 0; i < proposal.options.length; i++) { FHE.allowPublic(proposal.options[i].votes); } } function revealResults( uint256 _proposalId, uint64[] calldata _decryptedVotes, bytes[] calldata _signatures ) external { Proposal storage proposal = proposals[_proposalId]; if (!proposal.exists) revert ProposalNotFound(); require( _decryptedVotes.length == proposal.options.length, "Mismatched lengths" ); for (uint8 i = 0; i < proposal.options.length; i++) { FHE.publishDecryptResult( proposal.options[i].votes, _decryptedVotes[i], _signatures[i] ); } } function getProposal( uint256 _proposalId ) external view returns ( string memory name, uint256 deadline, bool exists, string[] memory options, uint256[] memory votes, bool finalized, uint8 winner ) { Proposal storage proposal = proposals[_proposalId]; name = proposal.name; deadline = proposal.deadline; exists = proposal.exists; options = new string[](proposal.options.length); for (uint8 i = 0; i < proposal.options.length; i++) { options[i] = proposal.options[i].name; } votes = new uint256[](proposal.options.length); finalized = true; for (uint8 i = 0; i < proposal.options.length; i++) { (uint256 result, bool decrypted) = FHE.getDecryptResultSafe( proposal.options[i].votes ); votes[i] = decrypted ? result : 0; if (!decrypted) finalized = false; } if (finalized) { uint256 maxVotes = 0; winner = 0; for (uint8 i = 0; i < proposal.options.length; i++) { if (votes[i] > maxVotes) { maxVotes = votes[i]; winner = i; } } } } function hasVoted( uint256 _proposalId, address _voter ) external view returns (bool) { return proposals[_proposalId].hasVoted[_voter]; } } ``` ## Next Steps * Learn about [Migrating from FHE.decrypt](/tutorials/migrating-from-fhe-decrypt) to the new decryption flow * Explore [ACL Usage Examples](/tutorials/acl-usage-examples) for complex access control patterns * Review [Best Practices](/fhe-library/introduction/best-practices) for security considerations # Migrating from FHE.decrypt to the New Decryption Flow Source: https://cofhe-docs.fhenix.zone/tutorials/migrating-from-fhe-decrypt Step-by-step guide for migrating Solidity contracts from FHE.decrypt to the new allowPublic + publishDecryptResult pattern ## Overview The old decryption pattern used `FHE.decrypt(ctHash)` to trigger an asynchronous decryption, followed by `FHE.getDecryptResultSafe(ctHash)` to read the result once available. The new pattern replaces `FHE.decrypt` with an off-chain decryption step using the Client SDK, and uses `FHE.publishDecryptResult` to submit the result on-chain with a cryptographic proof. This guide walks through concrete before/after Solidity examples. ## What changed | | Old pattern | New pattern | | ------------------- | ---------------------------------- | -------------------------------------------------------------------------------- | | **Trigger decrypt** | `FHE.decrypt(ctHash)` (on-chain) | `FHE.allowPublic(ctHash)` (on-chain) + `client.decryptForTx(ctHash)` (off-chain) | | **Submit result** | Automatic (async, no proof) | `FHE.publishDecryptResult(ctHash, plaintext, signature)` | | **Verify only** | N/A | `FHE.verifyDecryptResult(ctHash, plaintext, signature)` | | **Read result** | `FHE.getDecryptResultSafe(ctHash)` | `FHE.getDecryptResultSafe(ctHash)` (same) | The key difference: `FHE.decrypt` triggered decryption without any proof. The new flow requires a Threshold Network signature, ensuring the plaintext is cryptographically verified before being used on-chain. *** ## The New Decryption Flow Instead of calling `FHE.decrypt()`, mark the value as decryptable: ```solidity theme={null} // Anyone can request decryption (replaces FHE.decrypt) FHE.allowPublic(encryptedValue); // Or restrict to specific address FHE.allow(encryptedValue, authorizedAddress); ``` The client requests decryption from the Threshold Network, which returns the plaintext and a signature: ```typescript theme={null} const result = await client .decryptForTx(ctHash) .withoutPermit() // use .withPermit() if FHE.allow was used instead of allowPublic .execute(); // result.decryptedValue — the plaintext (bigint) // result.signature — Threshold Network signature ``` The decrypted value and signature are submitted to your contract: ```solidity theme={null} // Publishes the result on-chain (readable via getDecryptResultSafe) FHE.publishDecryptResult(encryptedValue, plaintext, signature); // Or verify without storing FHE.verifyDecryptResult(encryptedValue, plaintext, signature); ``` *** ## Example 1: Simple counter A minimal example showing how the reveal pattern changes. ```solidity Before (FHE.decrypt) theme={null} contract Counter { euint64 public counter; function increment() external { counter = FHE.add(counter, FHE.asEuint64(1)); FHE.allowThis(counter); } // Old: trigger async decryption on-chain function request_reveal() external { FHE.decrypt(counter); } // Old: read the result once available function get_counter_value() external view returns (uint256) { (uint256 value, bool decrypted) = FHE.getDecryptResultSafe(counter); if (!decrypted) revert("Value is not ready"); return value; } } ``` ```solidity After (new flow) theme={null} contract Counter { euint64 public counter; function increment() external { counter = FHE.add(counter, FHE.asEuint64(1)); FHE.allowThis(counter); } // New: mark as publicly decryptable function allow_counter_publicly() external { FHE.allowPublic(counter); } // New: accept decrypted value with Threshold Network proof function reveal_counter(uint64 _decrypted, bytes memory _signature) external { FHE.publishDecryptResult(counter, _decrypted, _signature); } // Same: read the published result function get_counter_value() external view returns (uint256) { (uint256 value, bool decrypted) = FHE.getDecryptResultSafe(counter); if (!decrypted) revert("Value is not ready"); return value; } } ``` **Client-side flow (new):** ```typescript theme={null} // 1. Allow public decryption await counter.allow_counter_publicly(); // 2. Decrypt off-chain const ctHash = await counter.counter(); const result = await client .decryptForTx(ctHash) .withoutPermit() .execute(); // 3. Publish on-chain with proof await counter.reveal_counter(result.decryptedValue, result.signature); // 4. Read the result const value = await counter.get_counter_value(); ``` *** ## Example 2: Token unshield (FHERC20Wrapper) The unshield flow is where `FHE.decrypt` was most commonly used. It already followed a two-step pattern (unshield + claim), which maps naturally to the new flow. ```solidity Before (FHE.decrypt) theme={null} function unshield(address to, uint64 value) public { if (to == address(0)) to = msg.sender; euint64 burned = _burn(msg.sender, value); // Old: trigger async decryption on-chain FHE.decrypt(burned); _createClaim(to, value, burned); emit UnshieldedERC20(msg.sender, to, value); } function claimUnshielded(bytes32 ctHash) public { Claim memory claim = _claims[ctHash]; if (claim.to == address(0)) revert ClaimNotFound(); if (claim.claimed) revert AlreadyClaimed(); // Old: read the async decryption result (uint256 amount, bool decrypted) = FHE.getDecryptResultSafe(euint64.wrap(ctHash)); if (!decrypted) revert("Not yet decrypted"); claim.claimed = true; _erc20.safeTransfer(claim.to, amount); emit ClaimedUnshieldedERC20(msg.sender, claim.to, amount); } ``` ```solidity After (new flow) theme={null} function unshield(address to, uint64 value) public { if (to == address(0)) to = msg.sender; euint64 burned = _burn(msg.sender, value); // New: mark as publicly decryptable (replaces FHE.decrypt) FHE.allowPublic(burned); _createClaim(to, value, burned); emit UnshieldedERC20(msg.sender, to, value); } function claimUnshielded( bytes32 ctHash, uint64 decryptedAmount, bytes memory decryptionSignature ) public { // New: verify and publish the decryption proof FHE.publishDecryptResult( euint64.wrap(ctHash), decryptedAmount, decryptionSignature ); Claim memory claim = _claims[ctHash]; if (claim.to == address(0)) revert ClaimNotFound(); if (claim.claimed) revert AlreadyClaimed(); claim.claimed = true; _erc20.safeTransfer(claim.to, decryptedAmount); emit ClaimedUnshieldedERC20(msg.sender, claim.to, decryptedAmount); } ``` **Key differences:** * `FHE.decrypt(burned)` → `FHE.allowPublic(burned)` — no on-chain decryption is triggered, just a permission grant * `claimUnshielded(bytes32 ctHash)` → `claimUnshielded(bytes32 ctHash, uint64 decryptedAmount, bytes signature)` — the caller now provides the decrypted value + proof * `FHE.getDecryptResultSafe` → `FHE.publishDecryptResult` — the contract verifies the Threshold Network signature instead of polling for a result **Client-side flow (new):** ```typescript theme={null} // 1. Request unshield await token.unshield(recipientAddress, 100n); // 2. Get the ctHash of the burned amount (from the event or storage) const ctHash = /* ... from UnshieldedERC20 event ... */; // 3. Decrypt off-chain const result = await client .decryptForTx(ctHash) .withoutPermit() .execute(); // 4. Claim with proof await token.claimUnshielded( result.ctHash, result.decryptedValue, result.signature ); ``` *** ## Example 3: Revealing a vote result A simple pattern: revealing a single encrypted vote count after a deadline. ```solidity Before (FHE.decrypt) theme={null} euint64 public totalVotes; function closeVoting() external onlyOwner { require(block.timestamp >= deadline, "Not ended"); // Old: trigger async decryption on-chain FHE.decrypt(totalVotes); } function getResult() external view returns (uint256) { // Old: poll for the async result (uint256 result, bool decrypted) = FHE.getDecryptResultSafe(totalVotes); if (!decrypted) revert("Not yet decrypted"); return result; } ``` ```solidity After (new flow) theme={null} euint64 public totalVotes; function closeVoting() external onlyOwner { require(block.timestamp >= deadline, "Not ended"); // New: mark as publicly decryptable FHE.allowPublic(totalVotes); } function revealResult(uint64 plaintext, bytes calldata signature) external { // New: publish with Threshold Network proof FHE.publishDecryptResult(totalVotes, plaintext, signature); } function getResult() external view returns (uint256) { // Same: read the published result (uint256 result, bool decrypted) = FHE.getDecryptResultSafe(totalVotes); if (!decrypted) revert("Not yet decrypted"); return result; } ``` *** ## `publishDecryptResult` vs `verifyDecryptResult` | Method | Stores result on-chain | Others can read it | Use case | | ---------------------- | ---------------------- | ------------------------------- | ------------------------------------------------------ | | `publishDecryptResult` | Yes | Yes, via `getDecryptResultSafe` | Revealing results publicly (auctions, votes, counters) | | `verifyDecryptResult` | No | No | One-time verification (transfers, burns) | Use `verifyDecryptResult` when you only need to confirm the plaintext is authentic and don't need other contracts or future calls to read it: ```solidity theme={null} function transferToPublic( bytes32 ctHash, uint32 plaintext, bytes calldata signature ) external { // Verify authenticity without publishing require( FHE.verifyDecryptResult(euint32.wrap(ctHash), plaintext, signature), "Invalid decrypt proof" ); _transfer(msg.sender, recipient, plaintext); } ``` *** ## Migration checklist Search your contracts for `FHE.decrypt(`. Each call needs to be replaced. In the function that previously called `FHE.decrypt(ctHash)`, replace it with `FHE.allowPublic(ctHash)`. Create a new function that accepts `(plaintext, signature)` parameters and calls `FHE.publishDecryptResult` or `FHE.verifyDecryptResult`. Add the off-chain decryption step using `client.decryptForTx()` between the two on-chain calls. ## Next Steps * Read about [Decryption Operations](/fhe-library/core-concepts/decryption-operations) for the full reference * See [Adding FHE to an Existing Contract](/tutorials/adding-fhe-to-existing-contract) for a complete contract migration * Learn about [Access Control](/fhe-library/core-concepts/access-control) for managing decrypt permissions * Check the [Client SDK decrypt guide](/client-sdk/guides/decrypt-to-tx) for the full client-side API # CoFHE Tutorials Overview Source: https://cofhe-docs.fhenix.zone/tutorials/overview A comprehensive guide to all available tutorials for learning and mastering CoFHE ## Welcome Welcome to the CoFHE tutorials hub! This page provides a comprehensive overview of all available tutorials to help you learn and master Fully Homomorphic Encryption (FHE) development on the blockchain. Our tutorials are organized into progressive learning paths, from beginner-friendly introductions to advanced implementation techniques. ## Quick Start Tutorials Start your CoFHE journey with these foundational tutorials designed to get you up and running quickly: | Tutorial | Description | | ---------------------------------------------------------- | ----------------------------------------------------------------------------- | | [Quick Start](/fhe-library/introduction/quick-start) | Set up your environment for FHE enabled development and testing | | [Best Practices](/fhe-library/introduction/best-practices) | Discover security and performance optimization techniques for FHE development | ## Advanced Tutorials Once you've mastered the basics, dive deeper with these specialized tutorials: | Tutorial | Description | | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | | [Your First FHE Contract](/tutorials/your-first-fhe-contract) | Build a simple encrypted counter contract to understand fundamental FHE concepts | | [Adding FHE to an Existing Contract](/tutorials/adding-fhe-to-existing-contract) | Learn how to transform traditional smart contracts into privacy-preserving ones | | [Migrating from FHE.decrypt](/tutorials/migrating-from-fhe-decrypt) | Migrate from the old `FHE.decrypt` pattern to the new `allowPublic` + `publishDecryptResult` flow | | [ACL Usage Examples](/tutorials/acl-usage-examples) | Implement common access control mechanisms for encrypted data | Ready to start your privacy-preserving blockchain development journey? Choose a tutorial from the table above and dive in! # Your First FHE Contract Source: https://cofhe-docs.fhenix.zone/tutorials/your-first-fhe-contract Step-by-step tutorial for building your first FHE-enabled smart contract from scratch ## Overview Let's take a look at a simple contract that uses FHE to encrypt a counter, and break it down into its components. ## Complete Contract Example ```solidity theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.18; import {FHE, euint64, InEuint64} from "@fhenixprotocol/cofhe-contracts/FHE.sol"; contract SimpleCounter { address owner; euint64 counter; euint64 delta; modifier onlyOwner() { require(msg.sender == owner, "Only the owner can access that function"); _; } constructor(uint64 initial_value) { owner = msg.sender; counter = FHE.asEuint64(initial_value); FHE.allowThis(counter); // Encrypt the value 1 only once instead of every value change delta = FHE.asEuint64(1); FHE.allowThis(delta); } function increment_counter() external onlyOwner { counter = FHE.add(counter, delta); FHE.allowThis(counter); } function decrement_counter() external onlyOwner { counter = FHE.sub(counter, delta); FHE.allowThis(counter); } function reset_counter(InEuint64 calldata value) external onlyOwner { counter = FHE.asEuint64(value); FHE.allowThis(counter); } function allow_counter_publicly() external onlyOwner { FHE.allowPublic(counter); } function reveal_counter(uint64 _decrypted, bytes memory _signature) external { FHE.publishDecryptResult(counter, _decrypted, _signature); } function get_counter_value() external view returns(uint256) { (uint256 value, bool decrypted) = FHE.getDecryptResultSafe(counter); if (!decrypted) revert("Value is not ready"); return value; } function get_encrypted_counter_value() external view returns(euint64) { return counter; } } ``` ## Breaking Down the Contract ### Importing the FHE Library To start using FHE, we need to import the FHE library. In this example, we're importing the types `euint64` and `InEuint64` from the [FHE library](/fhe-library/reference/fhe-sol/overview). ```solidity theme={null} import {FHE, euint64, InEuint64} from "@fhenixprotocol/cofhe-contracts/FHE.sol"; ``` We want to keep the counter encrypted at all times, so we'll use the `euint64` type. ### State Variables Next, we define some state variables for the contract: ```solidity theme={null} euint64 counter; euint64 delta; ``` ### Constructor Initialization In the constructor, we initialize the `counter` and `delta` variables. We encrypt the `delta` here to avoid calculating the same encrypted value every time we increment or decrement the counter. ```solidity theme={null} counter = FHE.asEuint64(initial_value); delta = FHE.asEuint64(1); ``` We wanted the example contract to be as simple as possible, so readers can plug-and-play it into their preferred environment. There are some privacy improvements that could be made to this contract. When we initialize the `delta` and `counter` variables, we use **trivial encryption**. **Trivial encryption** produces a ciphertext from a public value, but this variable, even though represented as a ciphertext handle, is not really confidential because everyone can see what is the plaintext value that went into it. To make it completely private, we need to initialize these variables with an `InEuint` from the calldata. More about trivial encryption [here](/fhe-library/core-concepts/trivial-encryption). ### Access Control For every encrypted variable, we need to call `FHE.allowThis()` to allow the contract to access it. **Allowing access to encrypted variables** is an important concept in FHE-enabled contracts. Without it, the contract could not continue to use this encrypted variable in future transactions. You can read more about this in the [ACL Mechanism](/fhe-library/core-concepts/access-control) page. ```solidity theme={null} FHE.allowThis(counter); FHE.allowThis(delta); ``` ### Increment and Decrement Functions In the `increment_counter` and `decrement_counter` functions, we use the `FHE.add` and `FHE.sub` functions to increment and decrement the counter, respectively. And we also call `FHE.allowThis()` to allow the contract to access the new counter value. ```solidity theme={null} counter = FHE.add(counter, delta); FHE.allowThis(counter); ``` ### Reset Function In the `reset_counter` function, we receive an `InEuint64` value, which is a type that represents an encrypted value that can be used to reset the counter. This value is an encrypted value that we created client-side using the SDK (read more about it [here](/client-sdk/guides/encrypting-inputs)). ### Decryption: Allow Public and Reveal Decryption follows a two-step on-chain pattern, with an off-chain step in between. **Step 1: Allow public decryption (on-chain)** The owner calls `allow_counter_publicly` to mark the counter as eligible for public decryption: ```solidity theme={null} function allow_counter_publicly() external onlyOwner { FHE.allowPublic(counter); } ``` **Step 2: Decrypt off-chain** Anyone can now request decryption off-chain using `decryptForTx`, which returns the plaintext value and a Threshold Network signature: ```typescript theme={null} const countCtHash = await counter.counter(); const result = await client .decryptForTx(countCtHash) .withoutPermit() .execute(); ``` **Step 3: Publish on-chain with proof** The decrypted value and signature are submitted on-chain. `FHE.publishDecryptResult` verifies the signature and stores the plaintext — if the signature is invalid, the transaction reverts: ```solidity theme={null} function reveal_counter(uint64 _decrypted, bytes memory _signature) external { FHE.publishDecryptResult(counter, _decrypted, _signature); } ``` ### Reading the Decrypted Value Once the result has been published, anyone can read the counter's value using `get_counter_value`. This function uses `FHE.getDecryptResultSafe` to check if a published result is available: ```solidity theme={null} function get_counter_value() external view returns(uint256) { (uint256 value, bool decrypted) = FHE.getDecryptResultSafe(counter); if (!decrypted) revert("Value is not ready"); return value; } ``` If the result has not been published yet, the function reverts. Otherwise, it returns the plaintext value. ## Privacy Considerations In this contract, only the owner can allow public decryption. Once `reveal_counter` is called, the plaintext value is published on-chain and visible to everyone. What if we want to allow the owner to privately read the value without revealing it publicly? For that, we need to add a call for `FHE.allow(counter, owner)` or `FHE.allowSender(counter)` every time that we change the counter's value. This will allow the owner to read the encrypted counter's value using the `get_encrypted_counter_value` function and decrypt it privately off-chain using `decryptForView`: ```solidity theme={null} function increment_counter() external onlyOwner { counter = FHE.add(counter, delta); FHE.allowThis(counter); FHE.allowSender(counter); } function get_encrypted_counter_value() external view returns(euint64) { return counter; } ``` ```typescript theme={null} // Decrypt privately off-chain (no on-chain transaction, no public reveal) const countCtHash = await counter.get_encrypted_counter_value(); const result = await client .decryptForView(countCtHash) .withPermit() .execute(); console.log(`Counter value (private): ${result.decryptedValue}`); ``` ## Next Steps * Explore [Adding FHE to an Existing Contract](/tutorials/adding-fhe-to-existing-contract) * Review [ACL Usage Examples](/tutorials/acl-usage-examples) for more access control patterns * Understand [Decryption Operations](/fhe-library/core-concepts/decryption-operations) in detail