Raycash Docs
Getting Started

Key Concepts

Core concepts behind the Raycash protocol — FHE, confidential tokens, two-phase operations, and hooks.

Fully Homomorphic Encryption (FHE)

Raycash uses Fully Homomorphic Encryption via the Zama FHEVM to encrypt all token balances and transfer amounts on-chain.

  • Balances are stored as euint64 — encrypted 64-bit unsigned integers
  • Transfer amounts are encrypted — no one can see how much you send
  • Arithmetic operations (add, subtract, compare) happen on encrypted values without decrypting them
  • Only the account holder (and contracts with explicit ACL grants) can decrypt their balance

This means validators, block explorers, and other users cannot see your balance or transaction amounts. The entire token ledger is encrypted.

Confidential Transfers

In a normal ERC-20, a transfer with insufficient balance reverts — revealing to the world that the sender doesn't have enough tokens. This leaks information.

In Raycash, transfers never revert on insufficient balance. Instead, the protocol uses FHE.select to conditionally zero the transfer amount:

canTransfer = FHE.le(amount, balance)
transferValue = FHE.select(canTransfer, amount, 0)

An observer cannot distinguish a successful transfer from an insufficient-balance no-op. Both emit the same Transfer event with the same encrypted handle. This is a fundamental privacy property.

Two-Phase Operations

Both wrapping (cleartext to confidential) and unwrapping (confidential to cleartext) are two-phase operations. This is necessary because FHE decryption happens off-chain.

Wrapping (Two Phases)

  1. initWrap — The wrapper operator calls initWrap(DeployParams) with an FHE-encrypted recipient, input proof, and expected amount. The wrapper deploys a RaycashDepositor via CREATE2, sweeps the tokens from the depositor address, and records the deposit.
  2. finalizeWrap — Anyone calls finalize with a batch of deposit indices (including decoys) and the recipient address. The wrapper uses FHE.eq to match the recipient to their deposits and mints the corresponding confidential tokens.

The decoy mechanism (minDecoys) requires the caller to include extra deposit indices that don't belong to the recipient. Since matching happens via FHE, an observer cannot tell which deposits were claimed.

Unwrapping (Two Phases)

  1. initUnwrap — User burns confidential tokens and marks the amount for public decryption. The destination address is locked at this point.
  2. finalizeUnwrap — After off-chain decryption produces a proof, anyone (typically a relayer) can finalize by submitting the decrypted amount + proof. The wrapper verifies and sends cleartext tokens to the locked destination.

The destination is locked at request time to prevent front-running — no MEV bot can redirect your funds at finalization.

Hooks

The hook system separates custody (the immutable wrapper that holds tokens) from policy (configurable rules about who can interact).

Three hook interfaces cover the token lifecycle:

HookWhen CalledCan Block?
TransferMint / P2P transferYes
Unwrap InitiatedinitUnwrapNo (try/catch)
Unwrap FinalizedfinalizeUnwrapNo (try/catch)

The transfer hook — if the hook reverts, the wrapper re-reverts with TransferHookReverted. This effectively blocks the operation, which is how KYC enforcement works. The transfer hook fires on both mints (from finalizeWrap) and P2P transfers, so it gates entry into the confidential domain.

Exit hooks (unwrap requested, unwrap finalized) — use try/catch. On catch, the wrapper emits an event and continues. The operation is never blocked. This ensures the "exit always callable" invariant: users can always leave the encrypted domain, even if hook contracts are broken or their KYC is revoked.

Counterfactual Deposits

Users don't need to interact with any smart contract to deposit. Instead:

  1. The backend encrypts the recipient address using the Zama FHE SDK, producing an encrypted handle + input proof
  2. The encrypted handle serves as the CREATE2 salt — the backend computes the deterministic depositor address from it
  3. The user sends cleartext ERC-20 tokens to that address (a standard transfer)
  4. The wrapper operator calls initWrap({encryptedRecipient, inputProof, amount}), which deploys the depositor via CREATE2, sweeps the tokens, and records the deposit
  5. Anyone calls finalizeWrap(depositIndices, recipient) to mint confidential tokens

This pattern enables seamless on-ramp flows — the user just sends tokens to an address, like sending to any wallet.

Rage Quit

If the operator has not yet called initWrap() for a funded depositor address, the intended recipient can call withdraw(depositorAddr, secret, recipient, token, handle) to recover their tokens in a single transaction. The system uses a hash-preimage scheme -- the caller provides a secret whose hash matches the committed hash for that depositor, and tokens are swept directly to the recipient without touching the wrapper's balance.

Was this page helpful?

On this page