Shade is a confidential payments protocol built on Zama's fhEVM. Every balance and transaction amount is an encrypted euint64 ciphertext stored on-chain. Senders, receivers, timestamps, and finality remain public — only the numbers are hidden.
Trust model: FHE runs natively inside the EVM. There is no off-chain coprocessor in the trust model — the math is the trust.
Live on Sepolia · Launch App · Docs
Five UUPS-upgradeable contracts, all deployed on Sepolia testnet.
| Contract | Address | What it does |
|---|---|---|
ConfidentialUSDC |
0xc2A8ed637BCBFC01CF703CF5F5e7ECf74Fe52D06 |
Encrypted ERC-20 wrapping Circle's Sepolia USDC. Balances, allowances, and total supply are euint64 ciphertexts. Includes compliance controls (freeze/allowlist) and a two-step async unshield via KMS public decryption. |
PayrollVault |
0xdcba2eB04B8443fee9AD9E77cEcDb435fCDc0030 |
Employer creates reusable employee rosters (templates) and payroll runs with per-employee encrypted salaries. No employee can see another's salary. |
PrivateEscrow |
0x66bDF650d3E3F0F31Be202a379Aeb91ACD822320 |
Six-state escrow where the locked amount stays encrypted throughout its entire lifecycle — even the arbiter never sees the plaintext. |
BalanceProver |
0xB11e05B15F7f8249A31d87F623483e6d8Dd67eBA |
Publishes a verifiable on-chain boolean: "balance ≥ threshold" — without revealing the balance or the threshold. Powered by async KMS public decryption. |
StealthSend |
0x4a66204aDa1F5F7a25DaE0c17267EfeDB9572907 |
Recipient address is encrypted as eaddress. Only the intended recipient can claim. Senders can grant selective view access to auditors via grantViewAccess. |
Underlying USDC (Sepolia): 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238
npx hardhat test
BalanceProver ......... 4 passing
ConfidentialUSDC ...... 8 passing
PayrollVault .......... 6 passing
PrivateEscrow ......... 6 passing
StealthSend ........... 4 passing
28 passing
Feature contracts never hold cUSDC directly. They move it by passing internal euint64 handles into ConfidentialUSDC.transfer / transferFrom, first granting FHE.allowTransient(handle, address(cUSDC)). BalanceProver additionally calls cUSDC.authorizeBalanceRead(prover) so the prover contract has ACL access to compare a user's balance.
Two flows cannot complete in a single transaction because they require a plaintext value produced by the Zama KMS off-chain:
Unshield (ConfidentialUSDC)
requestUnshield(handle, proof)— validates, burns the encrypted amount, marks it publicly decryptablefinalizeUnshield(requestId, abiEncodedClearValues, decryptionProof)— anyone submits the KMS-signed plaintext; the contract verifies withFHE.checkSignaturesand releases USDC
Balance proof (BalanceProver)
proveAbove(handle, proof)— stores the comparison result as an encrypted handle- Off-chain:
publicDecrypt([handle])via the fhEVM SDK → KMS returnsabiEncodedClearValues+decryptionProof publishProof(user, abiEncodedClearValues, decryptionProof)— verifies and stores the boolean result on-chain
ConfidentialUSDC includes a lightweight compliance layer without breaking the encryption model:
- Freeze —
freeze(address)/unfreeze(address)block an account from sending or receiving. State is a plaintextmapping(address => bool)checked before any FHE operation. - Allowlist —
setAllowlist(address, bool)andsetAllowlistEnabled(bool)restrict transfers to a permitted set when active. Same plaintext-before-FHE guard pattern. - Role delegation — the contract owner can assign a
complianceAdminwho can manage freeze and allowlist without holding contract ownership.
All guards run before any FHE computation, so no ciphertext is ever touched for a non-compliant account.
function grantViewAccess(uint256 id, address viewer) external {
require(msg.sender == _transfers[id].sender, "not sender");
FHE.allow(_transfers[id].amount, viewer);
FHE.allow(_transfers[id].recipient, viewer);
emit ViewAccessGranted(id, viewer);
}ACL grants are permanent in fhEVM v0.11. Once granted, a viewer can request decryption of the amount and recipient address via the Zama gateway. The UI surfaces this as a per-transfer "Grant View Access" flow with an explicit permanence warning.
The coprocessor config lives in proxy storage (ERC-7201 namespaced slot), so FHE.setCoprocessor(ZamaConfig.getEthereumCoprocessorConfig()) is called inside initialize(), not in a constructor.
| Legacy API (spec snippets) | This repo (v0.11) |
|---|---|
TFHE.* |
FHE.* |
einput |
externalEuint64 / externalEaddress |
TFHE.asEuint64(einput, proof) |
FHE.fromExternal(ext, proof) |
TFHE.allow(x, address(this)) |
FHE.allowThis(x) |
TFHE.gte(...) |
FHE.ge(...) |
GatewayContract + callback |
FHE.makePubliclyDecryptable → relayer → FHE.checkSignatures |
| Public on-chain | Encrypted on-chain |
|---|---|
| Sender / receiver address (standard send) | Transaction amount (euint64) |
| That a transfer occurred | All balances and allowances |
| Timestamp, transaction hash | Total supply |
| Gas paid | Receiver address (stealth send only, eaddress) |
| Freeze / allowlist status | — |
Rules enforced in code: no synchronous decryption in state-changing functions; insufficient balance handled via FHE.select (silent, no revert, no information leak); every ciphertext gets explicit ACL grants; events never carry amounts.
npm install
cp .env.example .env # fill PRIVATE_KEY and SEPOLIA_RPC_URL
npx hardhat test # run full suite against fhEVM mock
npx hardhat run scripts/deploy.ts --network sepoliaDeployment writes deployments/sepolia.json with all proxy addresses.
See frontend/README.md for full setup instructions.
contracts/ Solidity source (5 contracts + MockUSDC)
test/ Hardhat tests (fhEVM mock)
scripts/ Deploy script
deployments/ JSON address files per network
frontend/ Next.js 14 app — see frontend/README.md