Hidden Token Balances: How Can a Freeze Still Work?
Say Alice holds 100 tokens, 30 of them frozen, and neither number is public. How does the token stop her from sending 80?
IN 30 SECONDS
A confidential balance is a token balance kept encrypted, so outsiders can’t read the amount. For a permissioned token, that changes how a freeze rule runs and what a transfer’s result shows. This stage walks through one real freeze check, step by step.
KEY TAKEAWAYS · 3
- Hiding balances changes what a transfer’s result shows and who can learn amounts. It doesn’t make every identity or compliance rule impossible.
- OpenZeppelin ERC7984Freezable v0.5.0 moves an encrypted zero when the unfrozen balance is too low. That’s one implementation’s choice: the ERC-7984 draft also allows a revert.
- On that version, a successful transaction doesn’t prove the requested amount moved. Read the actual amount returned.
On this page
The problem: a freeze needs numbers nobody should see
IN SHORT With public balances, anyone can follow a freeze check. With hidden balances, the contract must apply it without showing anyone the numbers.
WORDS TO KNOW
- Permissioned token
- A token that only approved holders can receive, with issuer controls such as freezes. ERC-3643, a Final standard, is one way to build it.
- Freeze
- An issuer control that locks part or all of a holder’s tokens, so that part can’t be transferred.
- Confidential balance
- A balance kept encrypted. Anyone can see that a transfer happened, but not the amount.
- FHE
- Fully homomorphic encryption: a way to add and compare encrypted numbers without decrypting them.
- Encrypted selection
- Picking one of two encrypted values from an encrypted yes or no, without revealing which one was picked.
A Permissioned token A token that only approved holders can receive, with issuer controls such as freezes. ERC-3643, a Final standard, is one way to build it. See the glossary only lets approved holders receive it. Its issuer can also put a Freeze An issuer control that locks part or all of a holder’s tokens, so that part can’t be transferred. See the glossary on part of a holder’s tokens, so that part can’t be sent.
Public balances
- Balance 100, frozen 30, so 70 can move.
- A transfer of 80 is rejected, and everyone can see why.
Confidential balances
- Balance, frozen amount and transfer amount are all encrypted.
- The contract must still apply the freeze, without revealing the numbers.
A Confidential balance A balance kept encrypted. Anyone can see that a transfer happened, but not the amount. See the glossary is kept encrypted. The contract can’t just read 100 and take away 30. In ERC-7984, a Draft Ethereum interface for these tokens, each amount is a confidential pointer to hidden data.
ADAM says: Picture a sealed envelope of cash handed over in front of witnesses: everyone sees the handover, nobody sees the bills. The image breaks because a real envelope can be weighed, or opened by whoever grabs it.
Here, only accounts the protocol allows can decrypt. A value can also be made public on purpose. That rests on code, cryptography and security assumptions, and each needs its own review.
IN PLAIN WORDS
Hiding balances changes what a transfer’s result shows and who can learn amounts. It doesn’t make every identity or compliance rule impossible. The freeze still matters.
This post is a source-based analysis of public specs and one pinned library version. It is not a production case study.
Coming up next: So how does a contract compare numbers it can’t read?
How an encrypted freeze check works, step by step
IN SHORT In OpenZeppelin’s ERC7984Freezable v0.5.0, a transfer bigger than the unfrozen balance doesn’t fail on that check: it moves an encrypted zero.
OpenZeppelin’s version relies on FHE Fully homomorphic encryption: a way to add and compare encrypted numbers without decrypting them. See the glossary, fully homomorphic encryption. It lets code add and compare numbers while they stay encrypted. The comparison and the Encrypted selection Picking one of two encrypted values from an encrypted yes or no, without revealing which one was picked. See the glossary below both work this way.
STEP BY STEP
Step 1. Request. A holder asks to send an encrypted amount.
- Request A holder asks to send an encrypted amount.
- Available The contract works out balance minus frozen amount, never below zero. The result is encrypted.
- Compare It checks whether the request fits in what’s available. The yes or no is encrypted too.
- Select It keeps the requested amount on yes and zero on no. The public success status doesn’t say which.
- Update Balances move by the selected amount, and the transaction succeeds in both cases. Other errors can still revert.
- Result The function returns the amount actually moved, still encrypted. Authorized accounts can decrypt it.
This is one library’s choice, for one case, in one pinned version. The ERC-7984 draft also allows a revert, a public failure, when the balance is too low.
Read the full code of that version before you rely on how it handles minting (creating new tokens), transfers, access and failures.
Coming up next: So what does that look like with real numbers?
A worked example: one freeze, two transfers
IN SHORT If the request fits in the unfrozen balance, all of it moves; if not, zero moves. The public success status is the same for both.
A WORKED EXAMPLE
Illustrative numbers
PASSAlice holds 100 tokens, 30 of them frozen, and sends 50
INPUT
- Balance
- 100 (encrypted)
- Frozen
- 30 (encrypted)
- Request
- 50 (encrypted)
WHAT HAPPENS
- Available: 100 minus 30 leaves 70.
- 50 fits in 70, so the encrypted answer is yes.
- The selection keeps 50.
OUTPUT
- Transaction
- succeeds
- Actual amount moved
- 50 (encrypted)
ZERO MOVEDSame starting balance, she asks to send 80 instead
INPUT
- Request
- 80 (encrypted)
WHAT HAPPENS
- 80 doesn’t fit in 70, so the encrypted answer is no.
- The selection keeps zero.
OUTPUT
- Transaction
- succeeds
- Actual amount moved
- 0 (encrypted)
A wallet that shows “payment completed” after Alice’s second, empty transfer is wrong.
Encryption hides the amount from the public. It doesn’t stop the holder from learning what happened, if you give them an authorized way to decrypt the result or get a proof.
ITEM GET
Read the result, not the status
Sources: ERC-7984 (Draft), ERC-3643, OpenZeppelin ERC7984Freezable v0.5.0.
STAGE CLEAR
What you now know
- How a contract can apply a freeze to balances that aren’t publicly readable.
- Why, on OpenZeppelin v0.5.0, a successful transfer can still move nothing.
Check yourself
Tap or click a question to see the answer.
Illustrative numbers: on OpenZeppelin ERC7984Freezable v0.5.0, Bob holds 100 tokens with 40 frozen. He asks to send 70. Does the transaction fail?
Not for that reason. Only 60 are available, so the selection keeps zero. Unless another error occurs, the transaction succeeds and moves nothing. The actual amount, decrypted or proven by an authorized party, shows it.
Web3 x AI engineer, former CTO of Integra. Named author (1 of 5) of ERC-3643, first author of ERC-6960, co-author of ERC-7410, and co-author of ERC-8203, which is still a draft.
Have a project to ship or a role to fill?
Discuss your project or roleMy work at the web3 x AI intersectionOpen-source tools I build
Follow the engineering notes
Get new articles on production AI and smart contracts by email.

