ERC‑8065: Zero Knowledge Token Wrapper

Thanks for your interest in ERC-8605 — I’m also excited about the recent surge of related work, including this thread (which is quite similar to what you’re doing): zERC20 : Cross-Chain Private ERC-20 Based on ZK Proof-of-Burn

Regarding the UX concerns introduced by wrappers, I think the ZWToken-unaware workflow can significantly improve the situation. Users don’t necessarily need to be aware that a wrapper exists at all.

I still believe that even if new tokens are issued, wrappers remain the only viable path. Asset issuers generally prefer to avoid regulatory exposure (which is essential for the survival of their projects), so privacy inevitably has to be handled by a permissionless wrapper — even if that means we’ll need some additional UX improvements.

I think changing uint256 relayerFee to bytes calldata relayerData would make it more general and able to support various forms of fees.

To clarify my previous comment:
I realized that what I’m exploring is not a wrapper-based model and not a dual-mode token.
It is actually a different token primitive, which I’m calling a ZW-Token (Zero-Wrapped Token) for now.

This primitive is based on three invariants:

1. Irreversible, deterministic burn
Supply strictly decreases over time.
Burned units can never re-enter circulation (no unwrap, no remint, no mirrored states).

2. Single-state supply (no private balances, no parallel ledgers)
Privacy does not hold or represent supply.
There is only one global supply counter:

totalSupply = initialSupply – irreversibleBurns

3. Unlinkable transfer layer on top of a single supply
Transfers can be made unlinkable (no A→B traceability)
without creating wrapped supply or dual representations of the asset.

This produces a different category of token:
supply-bounded, irreversible-burn assets with privacy that does not fragment supply.

It’s not a modification of ERC-8060;
it’s orthogonal to it — a separate primitive that could exist alongside it.

Happy to expand further if useful.

2 Likes

Great proposal. Before native account abstraction arrives, relayers/bundlers are still a necessary component, and the ERC should be able to support various types of relayers.

Additionally, should we add a bytes calldata minterData to correspond to bytes calldata relayerData, so that more scenarios can be supported?

But at the moment, I still can’t think of any scenarios that would require minterData.

    function remint(
        bytes calldata proof,
        bytes32 commitment,
        bytes32 nullifier,
        address to,
        uint256 id,
        uint256 amount,
        bool withdrawUnderlying,
        bytes calldata relayerData
    ) external;

I think we can use bytes32[] nullifiers instead of bytes32 nullifier to support batch reminting.

2 Likes

Great idea — it would be even better if the multiple nullifiers provided do not leak privacy to each other.

ERC-8065 Update [2025.12.2] — Major Improvements to the remint Interface

We’ve updated the remint interface to make it more extensible, consistent, and aligned with future use cases.

Previous interface:

function remint(
    bytes calldata proof,
    bytes32 commitment,
    bytes32 nullifier,
    address to,
    uint256 id,
    uint256 amount,
    bool withdrawUnderlying,
    uint256 relayerFee
) external;

New interface:

/// @notice Encapsulates all data required for remint operations
/// @param commitment The commitment (Merkle root) corresponding to the provided proof
/// @param nullifiers Array of unique nullifiers used to prevent double-remint
/// @param proverData Generic data for prover
/// @param relayerData Generic data for relayer, can contain fee information. Hash is used in ZK proof.
/// @param proof Zero-knowledge proof bytes verifying ownership of the provable burn address
struct RemintData {
    bytes32 commitment;
    bytes32[] nullifiers;
    bytes proverData;
    bytes relayerData;
    bytes proof;
}

/// @notice Remint ZWToken using a zero-knowledge proof to unlink the source of funds
/// @param to Recipient address that will receive the reminted ZWToken or the underlying token
/// @param id The token identifier. For fungible tokens without IDs (e.g., ERC-20), this MUST be set to `0`.
/// @param amount Amount of ZWToken burned from the provable burn address for reminting
/// @param withdrawUnderlying If true, withdraw the equivalent underlying token instead of reminting ZWToken
/// @param data Encapsulated remint data including commitment, nullifiers, proof, and relayer information
function remint(
    address to,
    uint256 id,
    uint256 amount,
    bool withdrawUnderlying,
    RemintData calldata data
) external;

Key changes

  • Introduced RemintData to align the remint pattern with deposit and withdraw, and avoid the “stack too deep” issue.
  • Replaced relayerFee with relayerData to support generic, more flexible relayer payloads.
  • Added proverData to support richer prover-side data structures.
  • Replaced nullifier with an array nullifiers enabling batch remint capabilities.

Additional updates

  • Renamed depositTo → deposit and withdrawTo → withdraw for a cleaner, more streamlined API, since the to parameter already captures the destination semantics.

Notably, we will soon open-source our implementation of ERC-8065, along with a PoC demonstrating our new ZWToken-unawareness workflow.

2 Likes

ERC-8065 Update [2025.12.8]:

1 Like

Thanks for considering my proposal for improving ERC-8065! Let me know if you have any questions regarding my stack!

1 Like