Encrypted balances
Every account’s balance is stored as aeuint64 — an encrypted 64-bit unsigned integer — rather than a plaintext uint256. The ciphertext lives in the _balances mapping on the HideMeToken contract.
Transfer amount privacy
When you calltransfer(), the amount you pass is encrypted client-side using the TFHE WASM library running in your browser before the transaction is broadcast. The FHE.fromExternal() call inside the contract verifies the encryption proof and produces an euint64 that never appears in plaintext anywhere on-chain.
Transfer event always emits amount = 0:
Encrypted arithmetic
Checking whether you have sufficient balance is itself a private operation. HideMe usesFHE.le() to compare two ciphertexts without revealing either value, and FHE.select() to choose the actual transfer value without branching in plaintext:
transferValue silently becomes 0 — the transaction still succeeds, but nothing is moved. This is intentional: a revert would let an observer probe your balance by repeatedly adjusting amounts until they find the boundary. Silent failure removes that attack surface entirely.
The same pattern applies to burns and transferFrom operations.
Decrypting your balance
To read your own balance, the frontend callsuserDecrypt from the Zama Relayer SDK. The flow uses EIP-712 structured data so that the Zama KMS can verify you own the address before releasing the plaintext:
Generate a keypair
The SDK generates a temporary asymmetric keypair in your browser. The public key is bound to the contract address and a time window.
Sign the request
Your wallet signs an EIP-712 typed message —
UserDecryptRequestVerification — that commits to the public key, the contract addresses, the start timestamp, and the duration.KMS decryption
The signed request is sent to the Zama KMS. The threshold network verifies the signature, confirms you are the authorized holder, and returns the plaintext encrypted under your temporary public key.
/api/decrypt/user-decrypt proxies KMS requests for environments where a direct connection is not available, but it never sees the decrypted value — results are encrypted under your ephemeral public key.
Observer model
Token owners can designate observer addresses when creating a token, or add them later withaddObserver(). Observers are typically compliance addresses — auditors, regulators, or internal treasury wallets — that need visibility into all balances without disrupting normal transfers.
_allowObservers() to grant each observer address FHE access to the new ciphertext:
Encrypted allowances
approve() and transferFrom() use encrypted allowances (euint64) rather than plaintext uint256 values:
transferFrom(), the contract checks both the allowance and the sender’s balance using encrypted comparisons. Neither value is revealed during the check. If either condition fails, the transfer silently sends 0.
What is visible on-chain
Some information is necessarily public on Ethereum:| Data | Visibility |
|---|---|
| Sender and receiver addresses | Public — visible in Transfer event topics |
| Transfer amount | Always 0 in events |
| Total supply | Public — plaintext uint64 |
| Observer addresses | Public — readable from observers[] array |
| Token name, symbol, decimals | Public |
| Block timestamps | Public |
| Whether a transfer occurred | Public (the event is emitted) |
| Balance values | Private — stored as euint64 ciphertexts |
| Allowance values | Private — stored as euint64 ciphertexts |
Can the relayer see my balance?
Can the relayer see my balance?
No. The relayer only submits KMS proofs to finalize unwrap and payment transactions. For user balance decryption, the SDK routes the request through
/api/decrypt/user-decrypt, but the decryption result is returned encrypted under your ephemeral public key. The server never receives the plaintext — only your browser, which holds the matching private key, can decrypt it.What if I lose access to my wallet?
What if I lose access to my wallet?
Encrypted balances are cryptographically tied to your Ethereum address. The KMS requires a valid EIP-712 signature from that address before it will authorize decryption. Without your wallet’s private key, you cannot generate the signature, and the KMS will not release the plaintext. There is no recovery mechanism — treat your wallet key like your balance.
Are observer addresses public?
Are observer addresses public?
Yes. The
observers[] array and isObserver mapping are public state variables on each HideMeToken contract. Anyone can read them directly from chain storage or by calling getObservers(). If you are creating a token, choose observer addresses with this in mind.