# Writing Fhevm Contracts

> Use when writing, modifying, or reviewing any Solidity contract that uses Zama fhEVM encrypted types (euint, ebool, eaddress) — confidential DeFi, private voting, sealed-bid auctions, encrypted ERC20 tokens, encrypted balances, or any application where on-chain data must remain encrypted under fully homomorphic encryption. Symptoms in code: imports from @fhevm/solidity, uses externalEuintXX or FH…

- **Type:** Skill
- **Install:** `agentstack add skill-0xe1337-fhevm-skill-fhevm`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [0xE1337](https://agentstack.voostack.com/s/0xe1337)
- **Installs:** 0
- **Category:** [Security](https://agentstack.voostack.com/c/security)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [0xE1337](https://github.com/0xE1337)
- **Source:** https://github.com/0xE1337/fhevm-skill/tree/main/skills/fhevm

## Install

```sh
agentstack add skill-0xe1337-fhevm-skill-fhevm
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# 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: `ERC7

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [0xE1337](https://github.com/0xE1337)
- **Source:** [0xE1337/fhevm-skill](https://github.com/0xE1337/fhevm-skill)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-0xe1337-fhevm-skill-fhevm
- Seller: https://agentstack.voostack.com/s/0xe1337
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
