agentsclimarketplace

Fhevm

Skill 0xE1337/fhevm-skill/skills/fhevm

๐Ÿ† Zama Bounty S2 winner. AI coding-agent skill for Zama fhEVM โ€” Claude Code, Cursor, Windsurf. 20 anti-patterns + 12-rule linter; cut agent fhEVM mistakes 15โ†’0 in A/B tests. MIT. ยท ๐Ÿ† Zama ่ต้‡‘่ต›้“ S2 ่Žทๅฅ–ใ€‚Zama fhEVM ็š„ AI agent skill โ€” Claude Codeใ€Cursorใ€Windsurf๏ผŒ20 ๆก anti-pattern + 12 ่ง„ๅˆ™ linter๏ผŒA/B ๅฎžๆต‹้”™่ฏฏ 15โ†’0ใ€‚MITใ€‚

Install
npx -y skills add 0xE1337/fhevm-skill --skill fhevm

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

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 FHE.fromExternal, calls FHE.add/sub/mul/select, FHE.allowThis, FHE.makePubliclyDecryptable, ZamaEthereumConfig, fhevm.createEncryptedInput. Use also when frontend integrates relayer-sdk for client-side encryption or async decryption.

The file declares its own license as MIT. That is the authorโ€™s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

27.1 KB, as published. Nobody here has run it

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

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:

// 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

NeedTypeWhy
Token balances, amountseuint64Fits up to ~18.4 quintillion; use 6 decimals not 18
Scores, counters, vote tallieseuint32Cheaper ops than euint64, sufficient range
Tiers, flags, small enumseuint82-10x cheaper than euint64
Boolean conditionseboolResult of all comparisons
Hash values, bitfieldseuint256No arithmetic โ€” bitwise and equality only
Encrypted addresseseaddressOnly eq, ne, select โ€” very limited
Large financial valueseuint128Expensive (mul costs ~1,686K HCU) โ€” use sparingly

Solidity pattern โ†’ fhEVM equivalent

Regular SolidityfhEVM 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 -= amountbalance = FHE.sub(balance, amount); FHE.allowThis(balance);
return balanceReturn handle; user decrypts off-chain
Revert on errorSilent 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

SituationLoad
Need full type details, casting, initializationreferences/encrypted-types.md
Need exact operation signatures or HCU costsreferences/fhe-operations.md
Writing ACL logic, cross-contract permissions, AA bundlesreferences/access-control.md
Handling user-submitted encrypted inputsreferences/input-validation.md
Implementing reveal/result publication, decryption callbacksreferences/decryption-patterns.md
Setting up or writing Hardhat testsreferences/testing-guide.md
Building frontend encryption/decryptionreferences/frontend-integration.md
Reviewing contract for common mistakes (20 patterns)anti-patterns/ANTI-PATTERNS.md
Writing/grading agent-generated code against trap casesvalidation/prompts.md
Reviewing the validation evidencevalidation/agent-effectiveness.md

Encrypted Types (Summary)

TypeArithmeticComparisonBitwiseSelect
eboolNoeq, neand, or, xor, notYes
euint8โ€“euint128FullFullFullYes
euint256Noeq, ne onlyFullYes
eaddressNoeq, ne onlyNoYes

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:

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
FunctionScopeWhen
FHE.allowThis(ct)PersistentAlways โ€” contract must access its own state
FHE.allow(ct, addr)PersistentUser/protocol needs long-term decrypt access
FHE.allowTransient(ct, addr)Single txHelper contract, cheaper gas
FHE.makePubliclyDecryptable(ct)Anyone, foreverPublishing results (vote tallies)
FHE.isSenderAllowed(ct)CheckVerify caller permission
FHE.cleanTransientStorage()CleanupBetween AA bundled ops (prevent cross-op leaks)

Input Validation

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

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 fhevmjsnpm 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

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)

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

npm install @openzeppelin/confidential-contracts

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

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):

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):

// 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-PatternWhy It's WrongFix
1if (FHE.lt(a,b))ebool is bytes32, not boolFHE.select(FHE.lt(a,b), x, y)
2Missing ACL after opNew handle has 0 permissionsFHE.allowThis() + FHE.allow()
3FHE.decrypt(x)Function doesn't existFHE.makePubliclyDecryptable(x) + async
4FHE.div(enc, enc)Encrypted divisor unsupportedFHE.div(enc, plaintext)
5FHE.add on euint256No arithmetic for 256-bitUse euint128 or smaller
6Uninitialized euint64Default is invalid, not zeroFHE.asEuint64(0) + allowThis
7fn(euint64 amount)Skips ZKPoK validationfn(externalEuint64, bytes proof)
8Assume tx success = transfer successSilent failure transfers 0Check effective result
15FHE.asEuint(plaintextParam)Trivial encryption โ€” value is in calldataexternalEuintXX + fromExternal
16emit Transfer(from, to, amount)Events are public โ€” leaks encrypted valueemit Transfer(from, to, type(uint256).max)
17Cross-contract pass without allowTransientCallee has no ACLallowTransient(ct, callee) before call
18Trust callback cleartext directlyCallback is public โ€” anyone forges valuesFHE.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

ContractKey PatternsFile
EncryptedCounterInput validation, ACL, basic opsexamples/EncryptedCounter.sol
ConfidentialERC20Silent failure, allowances, ERC-7984examples/ConfidentialERC20.sol
ConfidentialVotingEncrypted booleans, homomorphic tallyexamples/ConfidentialVoting.sol
BlindAuctionFHE.select for max, async revealexamples/BlindAuction.sol
AgentRegistryMulti-input, threshold checksexamples/AgentRegistry.sol
ConfidentialTreasuryFull lifecycle, guardian voting, balance checkexamples/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

PathLoad when
references/encrypted-types.mdNeed type details, casting, ranges
references/fhe-operations.mdNeed exact signatures or HCU costs
references/access-control.mdWriting ACL, cross-contract, security, AA bundles
references/input-validation.mdHandling user encrypted inputs
references/decryption-patterns.mdImplementing reveal, callbacks, checkSignatures
references/testing-guide.mdSetting up or writing Hardhat tests
references/testing-foundry.mdFoundry (forge-fhevm) testing
references/frontend-integration.mdBuilding frontend encryption/decryption
anti-patterns/ANTI-PATTERNS.md20 anti-patterns: logic / operational / privacy boundary
templates/contract-template.solStarting a new contract
templates/hardhat.config.tsSetting up Hardhat
templates/test-template.tsStarting a new test file
examples/*.solWorking reference implementations (6 contracts)
examples/ConfidentialTreasury.test.tsE2E-verified test pattern (15/15 passing)
validation/prompts.md7 demo prompts + 8 adversarial trap prompts for testing agent error-prevention
validation/agent-effectiveness.mdA/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.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.