Confidential Tokens
FHE-encrypted ERC-20 token base — ERC7984, and amount scaling via ERC7984ERC20Wrapper.
Raycash uses Confidential Tokens — an FHE-encrypted wrapper that can be applied to any ERC-20. The underlying standard is OpenZeppelin's ERC7984, which defines confidential ERC-20 tokens with FHE-encrypted balances. See the OpenZeppelin ERC7984 documentation for the canonical reference.
The RaycashWrapper inherits from OpenZeppelin's ERC7984. The codebase also defines a ConfidentialERC20 interface and abstract contract that specifies the dual-overload API surface.
Privacy Invariant
Transfers never revert on insufficient balance. Instead, FHE.select conditionally zeros 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. This is a fundamental privacy property — in a traditional ERC-20, a revert reveals that the sender lacks funds.
Architecture
State
| Field | Type | Description |
|---|---|---|
_totalSupply | uint64 | Cleartext total supply |
_balances | mapping(address => euint64) | Encrypted balances |
_allowances | mapping(address => mapping(address => euint64)) | Encrypted allowances |
The total supply is intentionally cleartext — it represents the total amount of underlying tokens locked in the wrapper, which is already publicly visible on-chain.
Dual-Overload Pattern
The IConfidentialERC20 interface defines two overloads for each function:
transfer(address to, externalEuint64 encryptedAmount, bytes inputProof)— For external callers who encrypt amounts off-chaintransfer(address to, euint64 amount)— For contract-to-contract calls where the encrypted handle already exists in the FHE domain
The first overload calls FHE.fromExternal to verify the encrypted input proof, then delegates to the second.
Amount Scaling
The wrapper inherits from OZ's ERC7984ERC20Wrapper, which provides a rate() function to convert between the underlying ERC-20 amount (arbitrary decimals) and the confidential uint64 domain.
The rate() and decimals() functions from ERC7984ERC20Wrapper determine the scaling factor. For 6-decimal stablecoins (e.g. USDC), the rate is 1 — compress and expand become identity operations with no precision loss. For 18-decimal tokens, the rate is set to trade precision for range so the amount fits in uint64.
View Functions
| Function | Returns | Description |
|---|---|---|
balanceOf(account) | euint64 | Encrypted balance handle |
allowance(owner, spender) | euint64 | Encrypted allowance handle |
totalSupply() | uint64 | Cleartext total supply |
decimals() | uint8 | Returns 6 (overridable) |
name() | string | Token name |
symbol() | string | Token symbol |
Design Decisions
- ERC7984 base — The wrapper inherits from OpenZeppelin's
ERC7984. TheConfidentialERC20abstract contract in the codebase defines theIConfidentialERC20interface. Derived contracts define their own supply management. _PLACEHOLDERin Approval events — EIP-20 requires anApprovalevent with auint256value. Since the actual allowance is encrypted,type(uint256).maxserves as a placeholder.rate()from ERC7984ERC20Wrapper — Amount scaling is handled by OZ'sERC7984ERC20Wrapperbase contract, keeping conversion logic standardized.
Was this page helpful?