The full story behind building Onyx with FHE

Steven Klinger
OnyxFHEZamaDeFizKorp
The full story behind building Onyx with FHE

What is Onyx?

Onyx is a confidential payment streaming protocol where balances and flow rates remain encrypted onchain.

We built it during the Zama Builder Villa (March 30 to April 2, 2026), an invitation-only retreat focused on fully homomorphic encryption in production-grade EVM systems.

Our thesis was simple: payment streams are one of the most useful primitives in DeFi, but today every salary stream, vesting schedule, and grant disbursement is publicly visible by default.

Onyx keeps composability and verifiability while adding real confidentiality for institutions, teams, and individuals.

Why FHE? The privacy gap in streaming protocols

Most existing privacy approaches in DeFi fall into two families:

  • ·ZK-based systems: strong for validity proofs and unlinkability, but shared mutable private state is harder to model for long-lived apps.
  • ·Offchain confidential compute: useful, but trust often depends on enclave operators and attestation assumptions.

FHE gives us a different model: smart contracts can compute directly on encrypted values without exposing plaintext during execution.

For a streaming protocol, it means Onyx can keep deposits and withdrawals encrypted while still deriving stream state from public timing data.

Architecture overview

Onyx architecture overview

Onyx is built around three layers: confidential token transfer, stream accounting logic, and a frontend encryption flow.

Plaintext is never exposed onchain. Authorized users can decrypt relevant values locally in the browser.

Core smart contract: the stream

Each stream stores timing fields in cleartext, while financial state stays encrypted as euint values.

Instead of storing a visible flow rate, the contract computes streamed amounts over time from encrypted deposit plus public timestamps.

solidity
pragma solidity ^0.8.27;

import {FHE, euint64, euint128, externalEuint64} from "@fhevm/solidity/lib/FHE.sol";
import {IERC7984} from "@openzeppelin/confidential-contracts/interfaces/IERC7984.sol";

contract OnyxStream {
    struct Stream {
        address sender;
        address recipient;
        address token;
        uint64 startTime;
        uint64 endTime;
        euint64 eDeposit;
        euint64 eWithdrawn;
    }

    mapping(uint256 => Stream) private streams;
    uint256 public nextStreamId;
}

The frontend sends an encrypted deposit and an input proof. The stream contract stores encrypted accounting values, while the ERC-7984 token handles confidential transfer.

Computing the withdrawable amount

The key computation is encrypted all the way through: streamed amount minus already withdrawn amount.

solidity
function _computeStreamed(Stream storage s) internal returns (euint64) {
    if (block.timestamp < s.startTime) return FHE.asEuint64(0);
    if (block.timestamp >= s.endTime) return s.eDeposit;

    euint128 deposit128 = FHE.asEuint128(s.eDeposit);
    uint128 elapsed = uint128(block.timestamp - s.startTime);
    uint128 duration = uint128(s.endTime - s.startTime);

    return FHE.asEuint64(FHE.div(FHE.mul(deposit128, elapsed), duration));
}

function _computeWithdrawable(Stream storage s) internal returns (euint64) {
    euint64 streamed = _computeStreamed(s);
    return FHE.sub(streamed, s.eWithdrawn);
}

Design note: time remains public while amounts remain encrypted. That constraint keeps computation cheaper and compatible with current FHEVM division rules.

Frontend encryption flow

Onyx uses Zama's React SDK wrapper to encrypt deposit amounts in the browser before any transaction is sent.

typescript
import { useEncrypt } from "@zama-fhe/react-sdk";

const encrypt = useEncrypt();

const encrypted = await encrypt.mutateAsync({
  values: [{ value: depositAmount, type: "euint64" }],
  contractAddress: STREAM_ADDRESS,
  userAddress,
});

const eDeposit = toHex(encrypted.handles[0]);
const inputProof = toHex(encrypted.inputProof);

The proof binds ciphertext input to the user wallet and target contract, which prevents replay in another contract context.

What can slow down this kind of project

1. Arithmetic constraints can dictate architecture

Not all FHE operations have the same cost profile. Ciphertext-vs-ciphertext operations are generally more expensive, and division has stricter constraints.

2. ACL complexity on encrypted handles

Permissions live on ciphertext handles, and new handles are produced after FHE operations. Permissions do not carry over automatically.

That creates a subtle failure mode: math can be correct but a resulting handle can still be unusable if ACL grants are missing.

3. End-to-end testing remains harder than standard Solidity

Local tooling has improved, but real E2E still requires validating the full encrypted pipeline: frontend encryption, relayer behavior, reencryption flow, signing, and testnet conditions.

Why this matters for DeFi

Implications of confidential streaming

Confidentiality and composability are not mutually exclusive in the FHEVM model. Contracts can still compose around encrypted handles without decrypting values.

Technical overview

Onyx technical overview

Know more about Onyx

If you want to go deeper, read the original build thread and check the visual recap below.

Onyx recap visuals

We are continuing to iterate on confidential payments. If you have ideas, use-cases, or integrations to discuss, reach out.

zKorp team message
Read the original X thread