This EIP defines a permissionless interface for fungible tokens that operate in two modes: transparent mode (fully compatible with ERC-20) and privacy mode (using ERC-8086 privacy primitives). Token holders can convert balances between modes. The transparent mode uses account-based balances, while the privacy mode uses the standardized IZRC20 interface from ERC-8086. Total supply is maintained as the sum of both modes.
Permissionless Nature: Anyone can implement and deploy dual-mode tokens using this standard without intermediaries, governance approval, or restrictions.
When launching a new token, projects face a fundamental choice:
This creates real-world problems:
Existing solutions require trade-offs that limit adoption.
Mechanism: Wrap existing tokens (DAI, ETH) into a privacy pool contract.
DAI (public) → deposit → Privacy Pool → withdraw → DAI (public)
Strengths:
Limitations for New Token Projects:
Best suited for: Adding privacy to existing deployed tokens (DAI, USDC, etc.)
This standard provides a alternative option specifically designed for new token deployments that want privacy as a core feature from day one.
Target Use Case: Projects launching new tokens (governance tokens, protocol tokens, app tokens) that need both DeFi integration and optional privacy.
Mechanism: Single Token Contract ↓ Public Mode (ERC-20) ←→ Privacy Mode (ZK-SNARK) ↓ ↓ DeFi/DEX Trading Private Holdings
Key Advantages:
Unified Token Economics
Seamless Mode Switching
toPrivate()toPublic()Full ERC-20 Compatibility
totalSupply() accounting tracks both modesTransparent Supply Tracking
totalSupply() includes both public and privacy mode balancestotalPrivacySupply() reveals aggregate privacy supply (no individual balances)Permissionless Application-Layer Deployment
This standard is not a universal solution. Key constraints:
New Tokens Only
Privacy-to-DeFi Requires Conversion
toPublic() before DeFi operationsPublic Mode:
Privacy Mode:
Public Mode:
Privacy Mode:
Public Mode:
Privacy Mode:
This standard embraces a core principle: "Privacy is a mode, not a separate token."
Rather than forcing users to choose between incompatible assets (Token A vs. Privacy Token B), we enable contextual privacy within a single fungible token. Users select the appropriate mode for each use case, maintaining capital efficiency and unified liquidity.
This approach acknowledges that privacy and composability serve different purposes, and most users need both at different times—not a forced choice between them.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
balanceOf()The proofType parameter in toPrivate, toPublic, and privacyTransfer functions allows implementations to support multiple proof strategies.
Purpose: Different proof types may be needed for:
Implementations MUST implement the ERC-20 interface. All ERC-20 functions operate exclusively on transparent mode balances:
balanceOf(account) MUST return the transparent mode balance only
transfer(to, amount) MUST transfer transparent balance onlyapprove(spender, amount) MUST approve transparent balance spendingtransferFrom(from, to, amount) MUST transfer transparent balance with allowancetotalSupply() MUST return the sum of all public balances plus totalPrivacySupply()
Implementations MUST emit standard ERC-20 Transfer events for transparent mode operations.
For mode conversions:
toPrivate(): MUST emit Transfer(account, address(0), amount)toPublic(): MUST emit Transfer(address(0), recipient, amount)Implementations MUST maintain the following invariant at all times:
Where:
totalSupply(): Inherited from ERC-20, represents total token supply across both modessum(all balanceOf(account)): Sum of all transparent mode balancestotalPrivacySupply(): Aggregate privacy mode supply, tracked by:
toPrivate() (public → private conversion)toPublic() (private → public conversion)Note: The public mode supply can be derived as totalSupply() - totalPrivacySupply() if needed, eliminating the need for a separate totalPublicSupply() function.
BURN_ADDRESS Requirement for toPublicProblem: When converting privacy-to-transparent, the ZK circuit enforces value conservation:
input_amount = output_amount
But we need to "convert" value from privacy mode to public mode. The circuit doesn't know that the contract will create public balance, so we must ensure the converted value doesn't remain spendable in privacy mode.
Solution: Force the first output to an unspendable address (BURN_ADDRESS):
Input: Note A (100) Output: Note B → BURN_ADDRESS (50) ← Provably unspendable Note C → User (50, change) ← Remains private
Contract: Creates 50 public balance for user
This ensures:
This standard is fully backward compatible with ERC-20 and ERC-8086:
IZRC20) functions and events are supported for privacy modeERC-8085 Reference Implementation
Attack Vector: If the contract does not verify BURN_ADDRESS, an attacker can:
Mitigation: Implementations MUST ensure the converted value cannot be spent in privacy mode. For BURN_ADDRESS approach:
This verification is critical—failure to prevent double-spending across modes would allow minting tokens out of thin air.
Transparent Mode: Standard ERC-20 balance checking prevents double-spending.
Privacy Mode: Nullifier uniqueness enforced on-chain:
Each commitment can only be spent once, as nullifiers are deterministically derived from commitments and private keys.
Attack: Malicious proof claiming incorrect values.
Mitigation: ZK circuits enforce value conservation. Verifier contracts validate proofs on-chain before state changes. The invariant totalSupply() == sum(balanceOf) + totalPrivacySupply() must hold after every operation.
Copyright and related rights waived via CC0.