# Fhevm

> fhEVM Confidential Smart Contract Development

- Skill: `0xe1337/fhevm` (Agent Skill, multi-file: 25 files)
- Install (CLI): `npx skillmds@latest add 0xe1337/fhevm`
- Raw SKILL.md: https://api.skillmd.com/api/skills/0xe1337/fhevm/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: 0xe1337 (https://skillmd.com/u/0xe1337)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/0xe1337/fhevm

---


# fhEVM Confidential Smart Contract Development

Build Solidity contracts where on-chain data stays **encrypted** using Fully Homomorphic Encryption.

> **Validation:** This skill has been A/B tested against 15 prompts (7 demo + 8 adversarial traps), with one live verification run on 2026-05-09. Without this skill, vanilla LLM output triggers **47 distinct anti-pattern occurrences across 14 of the 20 patterns** in the catalog (predicted), plus **9 anti-pattern occurrences in a single live run** (verified — full transcript at `validation/transcripts/2026-05-09-vanilla-erc20.md`); only 1/15 vanilla outputs both compile and have no privacy leak. With this skill loaded, all of these move to 0 and 15/15 respectively. Full report and reproducibility protocol: `validation/agent-effectiveness.md`.

## When to Use

Use this skill when **any** of these apply:

- Writing or modifying a Solidity contract that imports `@fhevm/solidity`
- Adding encrypted state (`euint`, `ebool`, `eaddress`) to an existing contract
- Reviewing fhEVM code (your own or AI-generated) for correctness or privacy leaks
- Building or modifying a frontend that uses `@zama-fhe/relayer-sdk` for client-side encryption or async decryption
- Migrating a regular Solidity contract to fhEVM
- Writing or debugging Hardhat tests that use `fhevm.createEncryptedInput`
- Designing data flow for any application where on-chain values must stay encrypted under threshold MPC

**Symptoms in chat or code that should trigger this skill:**

- "encrypted balance / score / vote / bid"
- API references: `FHE.add`, `FHE.select`, `FHE.fromExternal`, `FHE.allowThis`, `FHE.makePubliclyDecryptable`, `ZamaEthereumConfig`, `externalEuintXX`
- File patterns: `@fhevm/solidity` imports, `*.sol` files declaring `euint*` state, test files importing `fhevm` from hardhat, `relayer-sdk` in frontend deps
- Error patterns: ACL access denied, "ebool cannot be evaluated", `FHE.decrypt is not a function`, decryption returning garbage

## When NOT to Use

This skill is **overkill or off-topic** for:

- Regular Solidity contracts with **no encrypted state** — vanilla Solidity guidance is enough; loading this skill adds noise.
- ZK-SNARK / ZK-STARK circuits that aren't fhEVM-related — different paradigm (zero-knowledge ≠ fully homomorphic).
- Concrete ML / TFHE-rs Rust applications — those use Python/Rust APIs and a different deployment model; this skill is Solidity-only.
- Generic Ethereum security audits where FHE is not present — use general security review skills, not this one.
- Contracts that only *call* fhEVM contracts but hold no encrypted state themselves — usually only the `references/access-control.md` cross-contract section is needed; full skill is unnecessary.

## Development Philosophy

**Think in ciphertexts, not plaintexts.** This is the fundamental shift from regular Solidity.

You are not writing "if this, then that" logic anymore. You are writing "compute both branches, select the correct result homomorphically." Every `if/else` becomes `FHE.select`. Every `require` becomes a guard that silently clamps to zero on failure. Your contract always succeeds at the EVM level — the question is what the encrypted result contains.

**Four-step approach to every fhEVM task:**

1. **Understand the goal** — What data must stay private? What can be public? Define the privacy boundary before writing code.
2. **Choose the right pattern** — Use the decision tables below. Don't branch on encrypted values. Don't try to decrypt synchronously. Don't forget ACL.
3. **Verify at each step** — After every FHE operation, ask: "Did I re-grant ACL on the new ciphertext? Did I use `FHE.select` instead of `if`? Did I validate the input with `FHE.fromExternal`?"
4. **Test with mock mode** — Run `npx hardhat test` locally. Mock FHE simulates all operations without a real coprocessor. Verify the logic works before deploying.

## How fhEVM Works (Architecture)

Understanding the architecture prevents entire classes of bugs:

```
User encrypts value (client-side, ZKPoK generated)
    ↓
Transaction carries encrypted handle (bytes32) + proof
    ↓
Contract calls FHE.fromExternal() → validates ZKPoK
    ↓
FHE.add(a, b) → FHE.sol → Impl.sol → FHEVMExecutor (on-chain)
    ↓
FHEVMExecutor does SYMBOLIC execution only:
  - Generates new handle via keccak256(op, operands, chainId, blockhash...)
  - Grants transient ACL permission for result
  - Emits event (e.g., FheAdd)
  - Meters HCU cost
    ↓
Off-chain coprocessor watches events, performs REAL TFHE computation
    ↓
Result ciphertext stored off-chain, indexed by the same handle
```

**Key insight: The on-chain system is entirely symbolic.** Handles (`bytes32`) are NOT ciphertexts — they are deterministic identifiers computed via `keccak256`. The actual encrypted data lives in 5 off-chain coprocessor nodes. Decryption requires 9 of 13 KMS MPC nodes (run by Etherscan, Fireblocks, Ledger, OpenZeppelin, etc. in AWS Nitro Enclaves). This is why:

- **Every operation creates a new handle** → FHE math produces a *new* ciphertext (different noise/randomness), so you must re-grant ACL permissions on the new handle
- **Handles cannot be evaluated as booleans** → no node has the secret key; the EVM literally cannot evaluate `if (eboolHandle)`
- **Decryption requires the off-chain KMS** → 9/13 threshold MPC, always async, no `FHE.decrypt()`
- **Uninitialized handles are `bytes32(0)`** → not encrypted zero, just an invalid pointer
- **Operations are expensive** → each FHE op involves noise management on large polynomial structures; bootstrapping refreshes noise but costs HCU

> See `references/architecture.md` for the full 6-component system, TFHE principles, handle format, and HCU metering details.

## Quick Start

```bash
git clone https://github.com/zama-ai/fhevm-hardhat-template.git my-fhevm-project
cd my-fhevm-project && npm install
npm test        # Run tests in mock mode
npm run compile # Compile contracts
```

**Minimal contract:**

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import { FHE, euint64, externalEuint64 } from "@fhevm/solidity/lib/FHE.sol";
import { ZamaEthereumConfig } from "@fhevm/solidity/config/ZamaConfig.sol";

contract EncryptedCounter is ZamaEthereumConfig {
    euint64 private _count;

    function increment(externalEuint64 encValue, bytes calldata proof) external {
        euint64 value = FHE.fromExternal(encValue, proof);
        _count = FHE.add(_count, value);
        FHE.allowThis(_count);
        FHE.allow(_count, msg.sender);
    }
}
```

## Decision Tables

### When to use which encrypted type

| Need | Type | Why |
|------|------|-----|
| Token balances, amounts | `euint64` | Fits up to ~18.4 quintillion; use 6 decimals not 18 |
| Scores, counters, vote tallies | `euint32` | Cheaper ops than euint64, sufficient range |
| Tiers, flags, small enums | `euint8` | 2-10x cheaper than euint64 |
| Boolean conditions | `ebool` | Result of all comparisons |
| Hash values, bitfields | `euint256` | No arithmetic — bitwise and equality only |
| Encrypted addresses | `eaddress` | Only eq, ne, select — very limited |
| Large financial values | `euint128` | Expensive (`mul` costs ~1,686K HCU) — use sparingly |

### Solidity pattern → fhEVM equivalent

| Regular Solidity | fhEVM Equivalent |
|-----------------|-------------------|
| `if (a > b) { x = a; } else { x = b; }` | `x = FHE.select(FHE.gt(a, b), a, b);` |
| `require(balance >= amount)` | `ebool ok = FHE.ge(balance, amount);` + silent failure |
| `balance -= amount` | `balance = FHE.sub(balance, amount); FHE.allowThis(balance);` |
| `return balance` | Return handle; user decrypts off-chain |
| Revert on error | Silent failure: transfer 0, bid 0 |
| `a / b` (both variables) | **Impossible** if both encrypted; restructure math |
| `mapping(addr => uint)` | `mapping(address => euint64)` + ACL on every update |

### When to load reference files

| Situation | Load |
|-----------|------|
| Need full type details, casting, initialization | `references/encrypted-types.md` |
| Need exact operation signatures or HCU costs | `references/fhe-operations.md` |
| Writing ACL logic, cross-contract permissions, AA bundles | `references/access-control.md` |
| Handling user-submitted encrypted inputs | `references/input-validation.md` |
| Implementing reveal/result publication, decryption callbacks | `references/decryption-patterns.md` |
| Setting up or writing Hardhat tests | `references/testing-guide.md` |
| Building frontend encryption/decryption | `references/frontend-integration.md` |
| Reviewing contract for common mistakes (20 patterns) | `anti-patterns/ANTI-PATTERNS.md` |
| Writing/grading agent-generated code against trap cases | `validation/prompts.md` |
| Reviewing the validation evidence | `validation/agent-effectiveness.md` |

## Encrypted Types (Summary)

| Type | Arithmetic | Comparison | Bitwise | Select |
|------|-----------|------------|---------|--------|
| `ebool` | No | `eq, ne` | `and, or, xor, not` | Yes |
| `euint8`–`euint128` | Full | Full | Full | Yes |
| `euint256` | **No** | `eq, ne` only | Full | Yes |
| `eaddress` | **No** | `eq, ne` only | **No** | Yes |

Import only what you use: `import { FHE, euint64, ebool, externalEuint64 } from "@fhevm/solidity/lib/FHE.sol";`

## FHE Operations (Summary)

**Arithmetic** (euint8–128): `FHE.add`, `FHE.sub`, `FHE.mul`, `FHE.div`\*, `FHE.rem`\*, `FHE.min`, `FHE.max`, `FHE.neg`
\*`div`/`rem` require **plaintext** right operand — encrypted divisor is NOT supported.

**Comparison** (→ `ebool`): `FHE.eq`, `FHE.ne`, `FHE.ge`, `FHE.gt`, `FHE.le`, `FHE.lt`

**Branching**: `FHE.select(ebool, ifTrue, ifFalse)` — the ONLY way to do conditional logic.

**Random**: `FHE.randEuint8()` – `FHE.randEuint256()`. Bounded: `FHE.randEuint16(upperBound)` (power-of-2). Transactions only, not view functions.

**Type casting**: `FHE.asEuint32(plaintext)` to encrypt. `FHE.asEuint64(euint32Value)` to upcast.

Scalar operations (one plaintext operand) are significantly cheaper. Prefer `FHE.add(enc, 5)` over `FHE.add(enc1, enc2)` when possible.

**Operator overloading:** With `using FHE for *`, you can write `a + b` instead of `FHE.add(a, b)`. Supports `+`, `-`, `*`, `&`, `|`, `^`, `~`.

## Access Control (ACL) — The #1 Source of Bugs

**Every FHE operation creates a NEW ciphertext handle with ZERO permissions.** Re-grant after EVERY operation:

```solidity
euint64 newBalance = FHE.add(balance, amount);
_balances[to] = newBalance;
FHE.allowThis(newBalance);      // Contract can use it in future txs
FHE.allow(newBalance, to);      // Owner can request decryption
```

| Function | Scope | When |
|----------|-------|------|
| `FHE.allowThis(ct)` | Persistent | **Always** — contract must access its own state |
| `FHE.allow(ct, addr)` | Persistent | User/protocol needs long-term decrypt access |
| `FHE.allowTransient(ct, addr)` | Single tx | Helper contract, cheaper gas |
| `FHE.makePubliclyDecryptable(ct)` | Anyone, forever | Publishing results (vote tallies) |
| `FHE.isSenderAllowed(ct)` | Check | Verify caller permission |
| `FHE.cleanTransientStorage()` | Cleanup | Between AA bundled ops (prevent cross-op leaks) |

## Input Validation

Users encrypt values client-side. Contracts validate via `externalEuintXX` + `FHE.fromExternal`:

```solidity
function deposit(externalEuint64 encAmount, bytes calldata inputProof) external {
    euint64 amount = FHE.fromExternal(encAmount, inputProof);  // Validates ZKPoK
    _balances[msg.sender] = FHE.add(_balances[msg.sender], amount);
    FHE.allowThis(_balances[msg.sender]);
    FHE.allow(_balances[msg.sender], msg.sender);
}
```

**Never accept raw `euintXX` as function parameters** — always use `externalEuintXX` + proof.

## Decryption (Always Async)

There is **no** `FHE.decrypt()`. Decryption goes through the off-chain Gateway → KMS threshold MPC:

1. On-chain: `FHE.makePubliclyDecryptable(ct)` — mark for decryption
2. Off-chain: `relayer-sdk.publicDecrypt(handles)` — get cleartext + proof
3. On-chain (optional): `FHE.checkSignatures(handles, clearValues, proof)` — verify

### Frontend SDK: `fhevmjs` is renamed to `@zama-fhe/relayer-sdk`

If you are reading older Zama tutorials, blog posts, or third-party guides, you will see the package name **`fhevmjs`**. This is the **previous name** for the official frontend SDK. The current package is `@zama-fhe/relayer-sdk` (sometimes called "relayer-sdk"). Same intent, refreshed API, native ESM, and handles client-side encryption + async decryption end-to-end.

| Old (deprecated) | New (use this) |
|---|---|
| `npm install fhevmjs` | `npm install @zama-fhe/relayer-sdk` |
| `import { initFhevm, createInstance } from "fhevmjs"` | `import { initSDK, createInstance, SepoliaConfig } from "@zama-fhe/relayer-sdk"` |
| `instance.createEncryptedInput(addr, user)` | `fhevm.createEncryptedInput(addr, user)` (same shape) |
| `instance.reencrypt(...)` (legacy) | `fhevm.userDecrypt([handles], eip712Signature)` |
| `instance.publicDecrypt(...)` (legacy) | `fhevm.publicDecrypt([handles])` |

If a search engine sends an agent to a `fhevmjs` snippet, **mentally rename it to `@zama-fhe/relayer-sdk`** and use the API table in `references/frontend-integration.md`. Don't `npm install fhevmjs` on a new project — the package is no longer maintained for current Zama Protocol releases.

## Configuration

```solidity
import { ZamaEthereumConfig } from "@fhevm/solidity/config/ZamaConfig.sol";
contract MyContract is ZamaEthereumConfig { ... }  // Auto-detects chainId
```

Hardhat: must set `evmVersion: "cancun"` in solidity settings.

## Testing (Hardhat Mock)

```typescript
import { ethers, fhevm } from "hardhat";

it("should work", async function () {
    if (!fhevm.isMock) { this.skip(); }
    const enc = await fhevm.createEncryptedInput(contractAddr, alice.address)
        .add64(1000).encrypt();
    await contract.connect(alice).deposit(enc.handles[0], enc.inputProof);
});
```

Input methods: `.addBool`, `.add8`, `.add16`, `.add32`, `.add64`, `.add128`, `.add256`, `.addAddress`

**Foundry alternative:** `forge-fhevm` (early-stage) supports pure Solidity tests with native fuzz testing. See `references/testing-foundry.md`.

## Confidential Token Design (ERC-7984)

When building encrypted ERC20 tokens, these rules prevent critical bugs:

- **Decimals MUST be 6, not 18.** `euint64` max ≈ 18.4 quintillion. With 18 decimals → max ~18.4 tokens. With 6 decimals → max ~18.4 trillion tokens.
- **Transfers never revert** on insufficient balance — they silently transfer 0 (privacy preservation).
- **Events emit `type(uint256).max`** as placeholder amount — not the real encrypted value.
- **`balanceOf()` takes no arguments** — returns caller's own encrypted balance handle.
- **`transferFrom` checks both** balance AND allowance: `FHE.and(hasBalance, hasAllowance)`.
- **Composability trap**: When contract A calls B's confidential transfer and it silently returns 0, A must check the effective result before updating its own state:
  ```solidity
  euint64 transferred = token.confidentialTransfer(to, amount);
  ebool didTransfer = FHE.gt(transferred, FHE.asEuint64(0));
  // Only update state if transfer actually happened
  ```

### OpenZeppelin Confidential Contracts

For production-grade ERC-7984 tokens, prefer the audited base contracts from `@openzeppelin/confidential-contracts` (verified against `0.4.0`) over rolling your own:

```bash
npm install @openzeppelin/confidential-contracts
```

> Note: The earlier `@openzeppelin/contracts-confidential` package is **deprecated and renamed**. Use `@openzeppelin/confidential-contracts` for any new project.

```solidity
import { ERC7984 } from "@openzeppelin/confidential-contracts/token/ERC7984/ERC7984.sol";
import { ZamaEthereumConfig } from "@fhevm/solidity/config/ZamaConfig.sol";

contract MyConfidentialToken is ERC7984, ZamaEthereumConfig {
    constructor() ERC7984("My Token", "MTK", "https://example.com/uri") {}
}
```

(Constructor signature: `ERC7984(string name_, string symbol_, string contractURI_)`.)

What `ERC7984` already gives you — so you don't reinvent it:
- Encrypted `balanceOf(address)` with proper ACL re-grants on every transfer
- `confidentialTransfer(to, externalEuint64, proof)` + `confidentialTransferFrom(...)` with `FHE.and(hasBalance, hasAllowance)` checks
- Silent-fail-on-insufficient-balance pattern (returns the actually-transferred amount as `euint64`)
- `Transfer` / `Approval` events that emit `type(uint256).max` as the value placeholder (anti-pattern #16 prevention)

Override only domain-specific logic (mint/burn rules, transfer hooks). **Do not re-implement ACL re-grants by hand** — the base contract already does it correctly.

Standard extensions in `@openzeppelin/confidential-contracts/token/ERC7984/extensions/` (v0.4.0):
- `ERC7984ERC20Wrapper` — bidirectional wrap/unwrap with a plaintext ERC-20 (see next section)
- `ERC7984Votes` — `votes`-style governance hooks for confidential supply
- `ERC7984Freezable` — admin-controlled per-account freezes
- `ERC7984Restricted` — allowlist / denylist gating
- `ERC7984Omnibus` — multi-account omnibus accounting
- `ERC7984Rwa` — real-world-asset hooks (admin-pause, force-transfer)
- `ERC7984ObserverAccess` — third-party observer ACL grants

Our `examples/ConfidentialERC20.sol` is a from-scratch reference implementation for didactic purposes; **for new dApps subclass `ERC7984` instead**.

### Wrap / Unwrap: ERC-7984 ↔ ERC-20

ERC-7984 tokens commonly need to bridge in/out of plaintext ERC-20 liquidity. The pattern:

1. **Wrap** — user deposits N plaintext ERC-20, receives encrypted balance of N in the ERC-7984 wrapper. The deposit amount is **public** (visible in the ERC-20 `Transfer` event) — privacy starts *after* wrapping.
2. **Unwrap** — user burns an encrypted ERC-7984 balance, an async decryption resolves the cleartext, and the underlying ERC-20 is released to the recipient. The unwrap amount **becomes public on settlement** (it has to, to release ERC-20).

`@openzeppelin/confidential-contracts` ships `ERC7984ERC20Wrapper` (abstract — you subclass it):

```solidity
import { ERC7984 } from "@openzeppelin/confidential-contracts/token/ERC7984/ERC7984.sol";
import { ERC7984ERC20Wrapper } from "@openzeppelin/confidential-contracts/token/ERC7984/extensions/ERC7984ERC20Wrapper.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ZamaEthereumConfig } from "@fhevm/solidity/config/ZamaConfig.sol";

contract WrappedUSDC is ERC7984ERC20Wrapper, ZamaEthereumConfig {
    constructor(IERC20 underlying)
        ERC7984ERC20Wrapper(underlying)
        ERC7984("Confidential USDC", "cUSDC", "")
    {}
}
```

Real wrapper API (v0.4.0, verified):
- `wrap(address to, uint256 amount) returns (euint64)` — pulls plaintext ERC-20 via `safeTransferFrom`, mints encrypted token; returns the *encrypted* amount actually wrapped (after rate division).
- `unwrap(address from, address to, euint64 amount) returns (bytes32 requestId)` — burns encrypted balance and triggers async KMS decryption to release plaintext ERC-20 to `to`.
- `unwrap(address from, address to, externalEuint64 encryptedAmount, bytes calldata inputProof) returns (bytes32 requestId)` — variant that takes a fresh `externalEuint64` + ZK proof.
- `unwrapAmount(bytes32 requestId) returns (euint64)` — view to read the encrypted amount handle for a pending unwrap.
- ERC-1363 `onTransferReceived` is supported, so ERC-1363 tokens can `transferAndCall` directly into the wrapper (one-tx wrap).

Frontend flow (relayer-sdk):
```typescript
// 1. Wrap: amount is plaintext, public on-chain (the privacy boundary starts AFTER this).
await usdc.approve(wrapper.address, 1_000_000n);                  // 1 USDC (6 decimals)
const wrappedHandle = await wrapper.wrap(user.address, 1_000_000n);
// wrappedHandle is an euint64 — the actually-wrapped amount, encrypted.

// 2. Encrypted balance now lives inside the wrapper; only the user can decrypt off-chain.
const handle = await wrapper.balanceOf(user.address);
const cleartext = await fhevm.userDecrypt([handle], sig);          // off-chain only

// 3. Encrypted confidential transfer to another wrapper user — fully private.
const enc = fhevm.createEncryptedInput(wrapper.address, user.address);
enc.add64(50_000n);
const { handles, inputProof } = await enc.encrypt();
await wrapper.confidentialTransfer(recipient, handles[0], inputProof);

// 4. Unwrap: returns a requestId; KMS async-decrypts and releases plaintext ERC-20.
//    The unwrap amount IS public on settlement (necessary to move ERC-20).
const requestId = await wrapper.unwrap(user.address, recipient, handles[0], inputProof);
// Listen for the wrapper's settlement event; ERC-20 transfers to `recipient` once KMS finalises.
```

**Privacy boundary reminder**: wrap/unwrap *amounts* are public by necessity (they cross a plaintext boundary). What stays private is **balances and intra-system transfers between wraps**. Document this in your dApp UI so users don't assume wrapping is fully confidential.

## Anti-Patterns — STOP and Read Before Writing Code

20 anti-patterns are catalogued in `anti-patterns/ANTI-PATTERNS.md`, organized in three layers:

- **Logic correctness (#1–9)** — won't run / won't compile / undefined behavior.
- **Operational correctness (#10–14)** — works in isolation, breaks under realistic conditions.
- **Privacy boundary (#15–20)** — works *and* tests pass, but state still leaks via off-chain side channels (calldata, events, ordering, mempool, AA bundles).

Top 12 quick reference (full list + production-grade #15–20 in the catalog):

| # | Anti-Pattern | Why It's Wrong | Fix |
|---|-------------|---------------|-----|
| 1 | `if (FHE.lt(a,b))` | ebool is bytes32, not bool | `FHE.select(FHE.lt(a,b), x, y)` |
| 2 | Missing ACL after op | New handle has 0 permissions | `FHE.allowThis()` + `FHE.allow()` |
| 3 | `FHE.decrypt(x)` | Function doesn't exist | `FHE.makePubliclyDecryptable(x)` + async |
| 4 | `FHE.div(enc, enc)` | Encrypted divisor unsupported | `FHE.div(enc, plaintext)` |
| 5 | `FHE.add` on euint256 | No arithmetic for 256-bit | Use euint128 or smaller |
| 6 | Uninitialized `euint64` | Default is invalid, not zero | `FHE.asEuint64(0)` + `allowThis` |
| 7 | `fn(euint64 amount)` | Skips ZKPoK validation | `fn(externalEuint64, bytes proof)` |
| 8 | Assume tx success = transfer success | Silent failure transfers 0 | Check effective result |
| **15** | **`FHE.asEuint(plaintextParam)`** | **Trivial encryption — value is in calldata** | **`externalEuintXX` + `fromExternal`** |
| **16** | **`emit Transfer(from, to, amount)`** | **Events are public — leaks encrypted value** | **`emit Transfer(from, to, type(uint256).max)`** |
| **17** | **Cross-contract pass without `allowTransient`** | **Callee has no ACL** | **`allowTransient(ct, callee)` before call** |
| **18** | **Trust callback cleartext directly** | **Callback is public — anyone forges values** | **`FHE.checkSignatures(handles, vals, sigs)`** |

## Gas Budget (HCU)

Transaction limit: **20M HCU total**, **5M HCU depth**. Key costs (non-scalar, euint64):
- `add/sub`: 162K — max ~123/tx
- `mul`: 596K — max ~33/tx
- `select`: 55K (constant) — cheap
- `trivialEncrypt/cast`: 32 — free

**Rule: use the smallest type that fits. Prefer scalar ops. Avoid `rem`.**

## Examples

| Contract | Key Patterns | File |
|----------|-------------|------|
| EncryptedCounter | Input validation, ACL, basic ops | `examples/EncryptedCounter.sol` |
| ConfidentialERC20 | Silent failure, allowances, ERC-7984 | `examples/ConfidentialERC20.sol` |
| ConfidentialVoting | Encrypted booleans, homomorphic tally | `examples/ConfidentialVoting.sol` |
| BlindAuction | FHE.select for max, async reveal | `examples/BlindAuction.sol` |
| AgentRegistry | Multi-input, threshold checks | `examples/AgentRegistry.sol` |
| ConfidentialTreasury | Full lifecycle, guardian voting, balance check | `examples/ConfidentialTreasury.sol` |

`examples/ConfidentialTreasury.test.ts` — Complete test file (15 tests, verified passing) demonstrating all SKILL.md testing patterns.

## Pre-Deployment Checklist

- [ ] No `if/else/require` on encrypted values — all use `FHE.select` + silent failure
- [ ] `FHE.allowThis()` after every operation storing a new ciphertext
- [ ] `FHE.allow(ct, user)` for every user needing decrypt access
- [ ] All inputs use `externalEuintXX` + `FHE.fromExternal()` + proof
- [ ] No encrypted divisors, no arithmetic on euint256/eaddress
- [ ] Encrypted state explicitly initialized (not relying on default)
- [ ] Contract inherits `ZamaEthereumConfig`
- [ ] HCU budget checked for loops and complex operations
- [ ] Decryption is async — no `FHE.decrypt()` calls
- [ ] Test signers match `createEncryptedInput` addresses
- [ ] **Run `node scripts/fhevm-lint.js path/to/Contract.sol`** — automated check of 12 of the 20 anti-patterns (regex-based, zero dependencies, <100ms)

## File Index

| Path | Load when |
|------|-----------|
| `references/encrypted-types.md` | Need type details, casting, ranges |
| `references/fhe-operations.md` | Need exact signatures or HCU costs |
| `references/access-control.md` | Writing ACL, cross-contract, security, AA bundles |
| `references/input-validation.md` | Handling user encrypted inputs |
| `references/decryption-patterns.md` | Implementing reveal, callbacks, `checkSignatures` |
| `references/testing-guide.md` | Setting up or writing Hardhat tests |
| `references/testing-foundry.md` | Foundry (forge-fhevm) testing |
| `references/frontend-integration.md` | Building frontend encryption/decryption |
| `anti-patterns/ANTI-PATTERNS.md` | 20 anti-patterns: logic / operational / privacy boundary |
| `templates/contract-template.sol` | Starting a new contract |
| `templates/hardhat.config.ts` | Setting up Hardhat |
| `templates/test-template.ts` | Starting a new test file |
| `examples/*.sol` | Working reference implementations (6 contracts) |
| `examples/ConfidentialTreasury.test.ts` | E2E-verified test pattern (15/15 passing) |
| `validation/prompts.md` | 7 demo prompts + **8 adversarial trap prompts** for testing agent error-prevention |
| `validation/agent-effectiveness.md` | A/B validation report: 47 anti-patterns prevented, reproducibility protocol |
| `../../scripts/fhevm-lint.js` (from repo root) | **Static checker** (12 rules, 0 deps) — run `node scripts/fhevm-lint.js path/to/Contract.sol` from the repo root. Catches the layer-3 privacy patterns Slither/Solhint/Mythril cannot. |

