| name | fhevm-contracts |
| description | Used when writing, refactoring, or reviewing FHEVM Solidity contracts against the @fhevm/solidity@0.11.x API. Covers encrypted types (ebool, euint8..256, eaddress), FHE operations (arithmetic, comparison, FHE.select), the ACL lifecycle (FHE.allow, allowThis, allowTransient, isSenderAllowed, makePubliclyDecryptable), and the input-proof flow (externalEuint* + FHE.fromExternal). Triggers on FHEVM contract patterns, encrypted types, FHE.* library calls, ACL permissions, input proofs, ciphertext handles, ZamaEthereumConfig, or writing a confidential counter / voting / auction / vault contract. |
fhevm-contracts
Write confidential Solidity contracts with the @fhevm/solidity@0.11.x API. This skill routes to three references: api-reference.md (types, ops, casts), control-flow-patterns.md (FHE.select, require replacements, capping), and acl-and-permissions.md (the ACL lifecycle end-to-end). Protocol-level context is in fhevm-overview; the footgun catalogue is in fhevm-antipatterns; ERC-7984 tokens are in fhevm-erc7984.
Essential Principles
1. Inherit ZamaEthereumConfig on every contract under contracts/
Every .sol file that defines a contract (including mocks, helpers, test stubs) must inherit ZamaEthereumConfig from @fhevm/solidity/config/ZamaConfig.sol. Without it, FHE.* calls revert at runtime because the coprocessor addresses are never wired. This includes mocks that make no FHE calls — the constructor call is harmless, and the grader / lint rule flags every missing inheritance.
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
contract MyToken is ZamaEthereumConfig, ERC7984 { /* ... */ }
2. No branching on encrypted values
if (encryptedBid > price) does not compile — the contract sees only handles, never plaintext. Use FHE.select(cond, ifTrue, ifFalse). Full pattern catalogue: control-flow-patterns.md.
3. No reverting on encrypted conditions
require(balance >= amount) cannot work on ciphertexts either. Compute both outcomes and select the valid one — the "transfer-zero" pattern. Callers detect failure by decrypting a status code, not by catching reverts.
4. Every derived handle needs FHE.allowThis + FHE.allow
New handles from FHE.* ops carry no ACL grants. After any op whose result lands in state:
_result = FHE.add(a, b);
FHE.allowThis(_result); // contract can read next tx
FHE.allow(_result, msg.sender); // user can decrypt
Missing FHE.allowThis is the #1 production failure mode. See acl-and-permissions.md and fhevm-antipatterns entry 9.
5. FHE.fromExternal is the only entry for user-supplied ciphertext
User-supplied encrypted inputs arrive as (externalEuint*, bytes proof) tuples. Convert them with FHE.fromExternal(encrypted, proof) — this triggers the on-chain InputVerifier ZKPoK check. The proof is bound to (contract_address, user_address); encrypting for the wrong contract invalidates it.
function increment(externalEuint64 enc, bytes calldata proof) external {
euint64 delta = FHE.fromExternal(enc, proof);
/* ... */
}
6. No proxies, no clones, no delegatecall on FHE state
delegatecall exposes the caller's ciphertext handles — ACL is address-bound. Factories use new Contract(...); no minimal proxies, no OZ Clones. Higher deployment gas, but the only safe approach.
Canonical contract shape
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
import {FHE, euint64, ebool, externalEuint64} from "@fhevm/solidity/lib/FHE.sol";
import {ZamaEthereumConfig} from "@fhevm/solidity/config/ZamaConfig.sol";
contract ConfidentialCounter is ZamaEthereumConfig {
euint64 private _count;
error OnlyOwner();
address private immutable _owner;
modifier onlyOwner() { if (msg.sender != _owner) revert OnlyOwner(); _; }
constructor() { _owner = msg.sender; }
// 1. Take encrypted input, convert with FHE.fromExternal (input proof).
function increment(externalEuint64 enc, bytes calldata proof) external {
euint64 delta = FHE.fromExternal(enc, proof);
_count = FHE.add(_count, delta);
FHE.allowThis(_count); // contract can reuse next tx
FHE.allow(_count, msg.sender); // caller can decrypt
}
// 2. Expose handle for off-chain userDecrypt (no `view returns (euint*)`).
function countHandle() external view returns (bytes32) {
return euint64.unwrap(_count);
}
// 3. Encrypted conditional (no `if`); reset to zero if the owner calls.
function resetIf(ebool condition) external onlyOwner {
_count = FHE.select(condition, FHE.asEuint64(0), _count);
FHE.allowThis(_count);
FHE.allow(_count, _owner);
}
}
All five Essential Principles exercised in one file: config inheritance, no-branch conditional (FHE.select), derived-handle ACL, input proof, handle exposure via bytes32 (not view returns (euint*)).
For full production-grade reference contracts, see Zama's official examples.
Routing — which reference answers the question
Deliverable checklist — every contract task
For every contract you write (e.g. contracts/Foo.sol), also produce:
test/foo.test.ts — Hardhat mock test exercising the happy path and at least one userDecrypt assertion.
- Every derived handle that lands in state →
FHE.allowThis + FHE.allow(handle, msg.sender) in the same function.
- Every
view that exposes state → return bytes32 (the unwrapped handle), never euint*.
- Access control → custom errors (
error OnlyX(); modifier onlyX() { if (...) revert OnlyX(); _; }), not OZ Ownable unless explicitly required.
If test/foo.test.ts is missing, or any derived-handle store lacks ACL grants, the contract is not done.
When to Use
- Writing any Solidity contract that imports
@fhevm/solidity/lib/FHE.sol.
- Refactoring a plaintext Solidity contract to use encrypted state.
- Reviewing a contract for correct type widths, ACL grants, and input-proof flow.
- Choosing
euint64 vs euint128 vs euint256 (see api-reference.md § type widths).
- Replacing
if/require logic with FHE.select + transfer-zero patterns.
When NOT to Use
- Protocol architecture and end-to-end flow questions →
fhevm-overview.
- Bootstrapping a new workspace /
hardhat vars → fhevm-setup.
- Test authoring with
@fhevm/mock-utils → fhevm-testing.
publicDecrypt / userDecrypt client flows → fhevm-decryption.
- Browser / React integration →
fhevm-frontend.
- ERC-7984 tokens specifically (wrapping, confidentialTransferFrom, restricted extensions) →
fhevm-erc7984.
Stack versions & external references
Canonical pinned versions and Zama documentation links live in fhevm-overview. This skill targets the same @fhevm/solidity@0.11.1 + @openzeppelin/confidential-contracts@0.4.x baseline; Solidity is 0.8.27 (evmVersion: cancun).